diff --git a/.cirrus.yml b/.cirrus.yml index 0a04ad6cd..3f2182f9c 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -1,17 +1,19 @@ task: name: freebsd-build freebsd_instance: - image_family: freebsd-12-2 + matrix: + - image_family: freebsd-12-2 + - image_family: freebsd-13-0 prepare_script: - - pkg install -yq git cmake jpeg-turbo mysql80-client ffmpeg libvncserver libjwt catch p5-DBI p5-DBD-mysql p5-Date-Manip p5-Test-LWP-UserAgent p5-Sys-Mmap + - pkg install -yq git cmake pkgconf jpeg-turbo mysql80-client ffmpeg libvncserver libjwt catch p5-DBI p5-DBD-mysql p5-Date-Manip p5-Test-LWP-UserAgent p5-Sys-Mmap v4l_compat configure_script: - git submodule update --init --recursive - mkdir build - cd build - cmake --version - - cmake ../ -DBUILD_MAN=0 -DBUILD_TEST_SUITE=1 -DENABLE_WERROR=1 + - cmake ../ -DBUILD_MAN=0 -DBUILD_TEST_SUITE=1 -DENABLE_WERROR=1 -DCMAKE_C_FLAGS="-Wno-deprecated-declarations" -DCMAKE_CXX_FLAGS="-Wno-deprecated-declarations" build_script: - cd build diff --git a/.github/workflows/ci-bionic.yml b/.github/workflows/ci-bionic.yml new file mode 100644 index 000000000..dda5c5cd3 --- /dev/null +++ b/.github/workflows/ci-bionic.yml @@ -0,0 +1,51 @@ +name: CI Ubuntu Bionic (18.04) + +on: + push: + branches: + - '*' + pull_request: + branches: [ master ] + +jobs: + build: + defaults: + run: + shell: bash + strategy: + matrix: + crypto_backend: [ openssl ] + jwt_backend: [ libjwt, jwt_cpp ] + include: + - crypto_backend: openssl + crypto_package: libssl-dev + jwt_package: libjwt-dev + runs-on: ubuntu-latest + container: ubuntu:bionic + + steps: + - name: Update packages + run: apt-get -qq update && apt-get -qq upgrade && apt-get -qq install software-properties-common + - name: Install git + run: | + add-apt-repository ppa:git-core/ppa + apt-get -qq update + apt-get -qq install git + - uses: actions/checkout@v2 + with: + submodules: recursive + - name: Install dependencies + run: > + apt-get -qq install make cmake g++ + default-libmysqlclient-dev + libavcodec-dev libavformat-dev libavutil-dev libswresample-dev libswscale-dev + libcurl4-gnutls-dev libvlc-dev libvncserver-dev + libdate-manip-perl libdbd-mysql-perl libsys-mmap-perl libwww-perl + libpolkit-gobject-1-dev + ${{ matrix.crypto_package }} ${{ matrix.jwt_package }} + - name: Prepare + run: mkdir build + - name: Configure + run: cd build && cmake --version && cmake .. -DBUILD_MAN=0 -DENABLE_WERROR=1 -DZM_CRYPTO_BACKEND=${{ matrix.crypto_backend }} -DZM_JWT_BACKEND=${{ matrix.jwt_backend }} + - name: Build + run: cd build && make -j3 | grep --line-buffered -Ev '^(cp lib\/|Installing.+\.pm)' && (exit ${PIPESTATUS[0]}) diff --git a/.github/workflows/ci-bullseye.yml b/.github/workflows/ci-bullseye.yml new file mode 100644 index 000000000..7d1882844 --- /dev/null +++ b/.github/workflows/ci-bullseye.yml @@ -0,0 +1,57 @@ +name: CI Debian Bullseye + +on: + push: + branches: + - '*' + pull_request: + branches: [ master ] + +jobs: + build: + defaults: + run: + shell: bash + strategy: + matrix: + crypto_backend: [ gnutls, openssl ] + jwt_backend: [ libjwt, jwt_cpp ] + include: + - crypto_backend: gnutls + crypto_package: libgnutls28-dev + jwt_package: libjwt-gnutls-dev + - crypto_backend: openssl + crypto_package: libssl-dev + jwt_package: libjwt-dev + exclude: + - crypto_backend: gnutls + jwt_backend: jwt_cpp + runs-on: ubuntu-latest + container: debian:bullseye + + steps: + - name: Update packages + run: apt-get -qq update && apt-get -qq upgrade + - name: Install git + run: apt-get -qq install git + - uses: actions/checkout@v2 + with: + submodules: recursive + - name: Install dependencies + run: > + apt-get -qq install make cmake g++ + default-libmysqlclient-dev + libavcodec-dev libavformat-dev libavutil-dev libswresample-dev libswscale-dev + libcurl4-gnutls-dev libvlc-dev libvncserver-dev + libdate-manip-perl libdbd-mysql-perl libsys-mmap-perl libwww-perl + libpolkit-gobject-1-dev + catch2 + ${{ matrix.crypto_package }} ${{ matrix.jwt_package }} + - name: Prepare + run: mkdir build + - name: Configure + run: cd build && cmake --version && cmake .. -DBUILD_MAN=0 -DBUILD_TEST_SUITE=1 -DENABLE_WERROR=1 -DZM_CRYPTO_BACKEND=${{ matrix.crypto_backend }} -DZM_JWT_BACKEND=${{ matrix.jwt_backend }} + - name: Build + run: cd build && make -j3 | grep --line-buffered -Ev '^(cp lib\/|Installing.+\.pm)' && (exit ${PIPESTATUS[0]}) + - name: Run tests + run: cd build/tests && ./tests "~[notCI]" diff --git a/.github/workflows/ci-buster.yml b/.github/workflows/ci-buster.yml new file mode 100644 index 000000000..7411366dd --- /dev/null +++ b/.github/workflows/ci-buster.yml @@ -0,0 +1,54 @@ +name: CI Debian Buster + +on: + push: + branches: + - '*' + pull_request: + branches: [ master ] + +jobs: + build: + defaults: + run: + shell: bash + strategy: + matrix: + crypto_backend: [ gnutls, openssl ] + jwt_backend: [ libjwt, jwt_cpp ] + include: + - crypto_backend: gnutls + crypto_package: libgnutls28-dev + jwt_package: libjwt-gnutls-dev + - crypto_backend: openssl + crypto_package: libssl-dev + jwt_package: libjwt-dev + exclude: + - crypto_backend: gnutls + jwt_backend: jwt_cpp + runs-on: ubuntu-latest + container: debian:buster + + steps: + - name: Update packages + run: apt-get -qq update && apt-get -qq upgrade + - name: Install git + run: apt-get -qq install git + - uses: actions/checkout@v2 + with: + submodules: recursive + - name: Install dependencies + run: > + apt-get -qq install make cmake g++ + default-libmysqlclient-dev + libavcodec-dev libavformat-dev libavutil-dev libswresample-dev libswscale-dev + libcurl4-gnutls-dev libvlc-dev libvncserver-dev + libdate-manip-perl libdbd-mysql-perl libsys-mmap-perl libwww-perl + libpolkit-gobject-1-dev + ${{ matrix.crypto_package }} ${{ matrix.jwt_package }} + - name: Prepare + run: mkdir build + - name: Configure + run: cd build && cmake --version && cmake .. -DBUILD_MAN=0 -DENABLE_WERROR=1 -DZM_CRYPTO_BACKEND=${{ matrix.crypto_backend }} -DZM_JWT_BACKEND=${{ matrix.jwt_backend }} + - name: Build + run: cd build && make -j3 | grep --line-buffered -Ev '^(cp lib\/|Installing.+\.pm)' && (exit ${PIPESTATUS[0]}) diff --git a/.github/workflows/ci-centos-7.yml b/.github/workflows/ci-centos-7.yml new file mode 100644 index 000000000..986b7815b --- /dev/null +++ b/.github/workflows/ci-centos-7.yml @@ -0,0 +1,39 @@ +name: CI CentOS 7 + +on: + push: + branches: + - '*' + pull_request: + branches: [ master ] + +jobs: + build: + strategy: + matrix: + crypto_backend: [ gnutls, openssl ] + jwt_backend: [ libjwt, jwt_cpp ] + exclude: + - crypto_backend: gnutls + jwt_backend: jwt_cpp + - crypto_backend: gnutls + jwt_backend: libjwt + runs-on: ubuntu-latest + container: centos:7 + + steps: + - name: Enable RPMFusion and EPEL + run: yum -y install https://mirrors.rpmfusion.org/free/el/rpmfusion-free-release-7.noarch.rpm https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm + - name: Install git + run: yum -y install https://repo.ius.io/ius-release-el7.rpm && yum -y install git224 + - uses: actions/checkout@v2 + with: + submodules: recursive + - name: Install dependencies + run: yum -y update && yum -y install make cmake3 gcc-c++ mariadb-devel ffmpeg-devel libcurl-devel vlc-devel libvncserver-devel libjpeg-turbo-devel "perl(Date::Manip)" "perl(DBD::mysql)" "perl(ExtUtils::MakeMaker)" "perl(Sys::Mmap)" "perl(Sys::Syslog)" "perl(LWP::UserAgent)" polkit-devel libjwt-devel + - name: Prepare + run: mkdir build + - name: Configure + run: cd build && cmake3 --version && cmake3 .. -DBUILD_MAN=0 -DENABLE_WERROR=1 -DZM_CRYPTO_BACKEND=${{ matrix.crypto_backend }} -DZM_JWT_BACKEND=${{ matrix.jwt_backend }} + - name: Build + run: cd build && make -j3 | grep --line-buffered -Ev '^(cp lib\/|Installing.+\.pm)' && (exit ${PIPESTATUS[0]}) diff --git a/.github/workflows/ci-centos-8.yml b/.github/workflows/ci-centos-8.yml new file mode 100644 index 000000000..f01f9929b --- /dev/null +++ b/.github/workflows/ci-centos-8.yml @@ -0,0 +1,41 @@ +name: CI CentOS 8 + +on: + push: + branches: + - '*' + pull_request: + branches: [ master ] + +jobs: + build: + strategy: + matrix: + crypto_backend: [ gnutls, openssl ] + jwt_backend: [ libjwt, jwt_cpp ] + exclude: + - crypto_backend: gnutls + jwt_backend: jwt_cpp + - crypto_backend: gnutls + jwt_backend: libjwt + runs-on: ubuntu-latest + container: centos:8 + + steps: + - name: Enable RPMFusion, EPEL and PowerTools + run: yum -y install "dnf-command(config-manager)" https://mirrors.rpmfusion.org/free/el/rpmfusion-free-release-8.noarch.rpm https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm && yum config-manager --set-enabled powertools + - name: Install git + run: yum -y install git + - uses: actions/checkout@v2 + with: + submodules: recursive + - name: Install dependencies + run: yum -y update && yum -y install make cmake gcc-c++ catch-devel mariadb-devel ffmpeg-devel libcurl-devel vlc-devel libvncserver-devel libjpeg-turbo-devel "perl(Date::Manip)" "perl(DBD::mysql)" "perl(ExtUtils::MakeMaker)" "perl(Sys::Mmap)" "perl(Sys::Syslog)" "perl(LWP::UserAgent)" polkit-devel libjwt-devel + - name: Prepare + run: mkdir build + - name: Configure + run: cd build && cmake --version && cmake .. -DBUILD_MAN=0 -DBUILD_TEST_SUITE=1 -DENABLE_WERROR=1 -DZM_CRYPTO_BACKEND=${{ matrix.crypto_backend }} -DZM_JWT_BACKEND=${{ matrix.jwt_backend }} + - name: Build + run: cd build && make -j3 | grep --line-buffered -Ev '^(cp lib\/|Installing.+\.pm)' && (exit ${PIPESTATUS[0]}) + - name: Run tests + run: cd build/tests && ./tests "~[notCI]" diff --git a/.github/workflows/ci-eslint.yml b/.github/workflows/ci-eslint.yml new file mode 100644 index 000000000..7a5c4337f --- /dev/null +++ b/.github/workflows/ci-eslint.yml @@ -0,0 +1,21 @@ +name: CI ESLint + +on: + push: + branches: + - '*' + pull_request: + branches: [ master ] + +jobs: + eslint: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + 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 + - name: Run ESLint + run: npx eslint --ext .php,.js . diff --git a/.github/workflows/ci-stretch.yml b/.github/workflows/ci-stretch.yml new file mode 100644 index 000000000..e42d4db3d --- /dev/null +++ b/.github/workflows/ci-stretch.yml @@ -0,0 +1,40 @@ +name: CI Debian Stretch + +on: + push: + branches: + - '*' + pull_request: + branches: [ master ] + +jobs: + build: + defaults: + run: + shell: bash + runs-on: ubuntu-latest + container: debian:stretch-backports + + steps: + - name: Update packages + run: apt-get -qq update && apt-get -qq upgrade + - name: Install git + run: apt-get -qq install git/stretch-backports git-man/stretch-backports + - uses: actions/checkout@v2 + with: + submodules: recursive + - name: Install dependencies + run: > + apt-get -qq install make cmake g++ + default-libmysqlclient-dev + libavcodec-dev libavformat-dev libavutil-dev libswresample-dev libswscale-dev + libcurl4-gnutls-dev libvlc-dev libvncserver-dev + libdate-manip-perl libdbd-mysql-perl libsys-mmap-perl libwww-perl + libpolkit-gobject-1-dev + libssl-dev + - name: Prepare + run: mkdir build + - name: Configure + run: cd build && cmake --version && cmake .. -DBUILD_MAN=0 -DENABLE_WERROR=1 + - name: Build + run: cd build && make -j3 | grep --line-buffered -Ev '^(cp lib\/|Installing.+\.pm)' && (exit ${PIPESTATUS[0]}) diff --git a/.github/workflows/ci-xenial.yml b/.github/workflows/ci-xenial.yml deleted file mode 100644 index 4ea605267..000000000 --- a/.github/workflows/ci-xenial.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: CI Xenial - -on: - push: - branches: - - '*' - pull_request: - branches: [ master ] - -jobs: - build: - runs-on: ubuntu-16.04 - - steps: - - uses: actions/checkout@v2 - with: - submodules: recursive - - name: Install dependencies - run: sudo apt-get update && sudo apt-get install libavdevice-dev libcurl4-gnutls-dev libvlc-dev libvncserver-dev libdate-manip-perl libdbd-mysql-perl libsys-mmap-perl libpolkit-gobject-1-dev - - name: Prepare - run: mkdir build - - name: Configure - run: cd build && cmake --version && cmake .. -DBUILD_MAN=0 -DENABLE_WERROR=1 - - name: Build - run: cd build && make -j3 | grep --line-buffered -Ev '^(cp lib\/|Installing.+\.pm)' && (exit ${PIPESTATUS[0]}) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 8b071a662..d25aa3710 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -52,8 +52,8 @@ jobs: git submodule init git submodule update --init --recursive sudo apt-get update - sudo apt-get install libavdevice-dev libavcodec-dev libavformat-dev libavutil-dev libswresample-dev libswscale-dev libjwt-gnutls-dev - sudo apt-get install libbz2-dev libgcrypt20-dev libcurl4-gnutls-dev libjpeg-turbo8-dev libturbojpeg0-dev + sudo apt-get install libavcodec-dev libavformat-dev libavutil-dev libswresample-dev libswscale-dev libjwt-gnutls-dev + sudo apt-get install libbz2-dev libcurl4-gnutls-dev libjpeg-turbo8-dev libturbojpeg0-dev sudo apt-get install default-libmysqlclient-dev libpcre3-dev libpolkit-gobject-1-dev libv4l-dev libvlc-dev sudo apt-get install libdate-manip-perl libdbd-mysql-perl libphp-serialization-perl libsys-mmap-perl sudo apt-get install libwww-perl libdata-uuid-perl libssl-dev libcrypt-eksblowfish-perl libdata-entropy-perl diff --git a/.github/workflows/create-packages.yml b/.github/workflows/create-packages.yml new file mode 100644 index 000000000..6a76da322 --- /dev/null +++ b/.github/workflows/create-packages.yml @@ -0,0 +1,31 @@ +name: Create packages + +on: + push: + branches: + - '*' + pull_request: + branches: [ master ] + +jobs: + package: + strategy: + matrix: + os_dist: + - os: debian + dist: buster + - os: debian + dist: bullseye + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + - name: Run packpack + env: + SMPFLAGS: -j4 + OS: ${{ matrix.os_dist.os }} + DIST: ${{ matrix.os_dist.dist }} + DOCKER_REPO: iconzm/packpack + run: utils/packpack/startpackpack.sh diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 000000000..ab8411b23 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,18 @@ +default: + image: + name: ubuntu:latest + before_script: + - apt-get update -yq + - DEBIAN_FRONTEND=noninteractive apt-get install -yq devscripts sudo + +deb: + stage: build + tags: + - docker + script: + - yes "" | ./utils/do_debian_package.sh --snapshot=stable --type=binary --interactive=no --dput=no --debbuild-extra=--no-sign || true + timeout: 2h + artifacts: + paths: + - '*.deb' + expire_in: 1 week diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index c370e9318..000000000 --- a/.travis.yml +++ /dev/null @@ -1,58 +0,0 @@ -language: cpp -sudo: required -dist: bionic -git: - depth: 9999999 -notifications: - irc: chat.freenode.net#zoneminder-dev -branches: - except: - - modern -cache: ccache -addons: - ssh_known_hosts: zmrepo.zoneminder.com - apt: - sources: - - sourceline: ppa:iconnor/zoneminder-master - - key_url: http://keyserver.ubuntu.com:11371/pks/lookup?op=get&search=0x4D0BF748776FFB04 - packages: - - gdebi - - yum-utils - - patch - - git - - curl - - sshfs - - sed - - binfmt-support - - qemu - - qemu-user-static - - dnsutils - - traceroute -install: - - update-binfmts --enable qemu-arm - -env: - - SMPFLAGS=-j4 OS=eslint DIST=eslint - - SMPFLAGS=-j4 OS=ubuntu DIST=bionic DOCKER_REPO=iconzm/packpack - -compiler: -- gcc -services: -- mysql -- docker - -script: -- utils/packpack/startpackpack.sh - -before_deploy: -- openssl aes-256-cbc -K $encrypted_62a62750aa73_key -iv $encrypted_62a62750aa73_iv -in ./utils/packpack/deploy_rsa.enc -out /tmp/deploy_rsa -d -- eval "$(ssh-agent -s)" -- chmod 600 /tmp/deploy_rsa -- ssh-add /tmp/deploy_rsa - -deploy: - provider: script - skip_cleanup: true - script: utils/packpack/rsync_xfer.sh - on: - branch: master diff --git a/CMakeLists.txt b/CMakeLists.txt index 700c80a20..c34a9f808 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,9 +69,7 @@ include(GNUInstallDirs) include(CheckIncludeFile) include(CheckIncludeFiles) include(CheckFunctionExists) -include(CheckPrototypeDefinition_fixed) include(CheckTypeSize) -include(CheckStructHasMember) include(CheckSendfile) # Configuration options @@ -189,8 +187,32 @@ set(ZM_MANPAGE_DEST_PREFIX "share/man" CACHE PATH "Relative path used to install ZoneMinder's Man pages into a non-standard folder. Most Linux users will not need to change this. BSD users may need to set this.") +set(ZM_CAKEPHP_CACHE "Apc" CACHE STRING + "Set the CakePHP cache engine, default: Apc") + +# Supported crypto backends. Using OpenSSL by default to be compatible with jwt-cpp. +set(ZM_CRYPTO_BACKEND_OPTIONS gnutls openssl) +set(ZM_CRYPTO_BACKEND openssl CACHE STRING "Determines which crypto backend should be used.") +set_property(CACHE ZM_CRYPTO_BACKEND PROPERTY STRINGS ${ZM_CRYPTO_BACKEND_OPTIONS}) + +if(NOT ZM_CRYPTO_BACKEND IN_LIST ZM_CRYPTO_BACKEND_OPTIONS) + message(FATAL_ERROR "Invalid value for ZM_CRYPTO_BACKEND. Possible options: ${ZM_CRYPTO_BACKEND_OPTIONS}") +endif() + +# Supported JWT backends. Using jwt-cpp as default. +set(ZM_JWT_BACKEND_OPTIONS libjwt jwt_cpp) +set(ZM_JWT_BACKEND jwt_cpp CACHE STRING "Determines which JWT backend should be used.") +set_property(CACHE ZM_JWT_BACKEND PROPERTY STRINGS ${ZM_JWT_BACKEND_OPTIONS}) + +if(NOT ZM_JWT_BACKEND IN_LIST ZM_JWT_BACKEND_OPTIONS) + message(FATAL_ERROR "Invalid value for ZM_JWT_BACKEND. Possible options: ${ZM_JWT_BACKEND_OPTIONS}") +endif() # Reassign some variables if a target distro has been specified +if(ZM_TARGET_DISTRO MATCHES "^fc") + set(ZM_CAKEPHP_CACHE "Memcached") +endif() + if((ZM_TARGET_DISTRO MATCHES "^el") OR (ZM_TARGET_DISTRO MATCHES "^fc")) set(ZM_RUNDIR "/var/run/zoneminder") set(ZM_SOCKDIR "/var/lib/zoneminder/sock") @@ -246,11 +268,6 @@ if(ZM_SYSTEMD OR (IS_DIRECTORY /usr/lib/systemd/system) OR (IS_DIRECTORY /lib/sy endif() # System checks -check_include_file("libv4l1-videodev.h" HAVE_LIBV4L1_VIDEODEV_H) -if(NOT HAVE_LIBV4L1_VIDEODEV_H) - check_include_file("linux/videodev.h" HAVE_LINUX_VIDEODEV_H) -endif() -check_include_file("linux/videodev2.h" HAVE_LINUX_VIDEODEV2_H) check_include_file("execinfo.h" HAVE_EXECINFO_H) if(HAVE_EXECINFO_H) check_function_exists("backtrace" HAVE_DECL_BACKTRACE) @@ -330,19 +347,20 @@ else() "ZoneMinder requires jpeg but it was not found on your system") endif() -# LIBJWT -find_package(LibJWT) -if(LIBJWT_FOUND) - set(HAVE_LIBJWT 1) - set(optlibsfound "${optlibsfound} LIBJWT") - list(APPEND ZM_BIN_LIBS "${LIBJWT_LIBRARY}") -else() - set(optlibsnotfound "${optlibsnotfound} LIBJWT") +# libjwt +if (${ZM_JWT_BACKEND} STREQUAL "libjwt") + find_package(LibJWT REQUIRED COMPONENTS ${ZM_CRYPTO_BACKEND}) + if(LIBJWT_FOUND) + set(HAVE_LIBJWT 1) + set(optlibsfound "${optlibsfound} LIBJWT") + else() + set(optlibsnotfound "${optlibsnotfound} LIBJWT") + endif() endif() -# gnutls (using find_library and find_path) -if(HAVE_LIBJWT) - find_library(GNUTLS_LIBRARIES gnutls) +# GnuTLS +if (${ZM_CRYPTO_BACKEND} STREQUAL "gnutls") + find_library(GNUTLS_LIBRARIES gnutls REQUIRED) if(GNUTLS_LIBRARIES) set(HAVE_LIBGNUTLS 1) list(APPEND ZM_BIN_LIBS "${GNUTLS_LIBRARIES}") @@ -352,23 +370,18 @@ if(HAVE_LIBJWT) set(CMAKE_REQUIRED_INCLUDES "${GNUTLS_INCLUDE_DIR}") endif() mark_as_advanced(FORCE GNUTLS_LIBRARIES GNUTLS_INCLUDE_DIR) - check_include_file("gnutls/gnutls.h" HAVE_GNUTLS_GNUTLS_H) set(optlibsfound "${optlibsfound} GnuTLS") else() set(optlibsnotfound "${optlibsnotfound} GnuTLS") endif() -endif() - # OpenSSL -if(NOT HAVE_LIBGNUTLS OR NOT HAVE_LIBJWT) - find_package(OpenSSL) +elseif (${ZM_CRYPTO_BACKEND} STREQUAL "openssl") + find_package(OpenSSL REQUIRED) if(OPENSSL_FOUND) set(HAVE_LIBOPENSSL 1) - set(HAVE_LIBCRYPTO 1) list(APPEND ZM_BIN_LIBS "${OPENSSL_LIBRARIES}") include_directories("${OPENSSL_INCLUDE_DIR}") set(CMAKE_REQUIRED_INCLUDES "${OPENSSL_INCLUDE_DIR}") - check_include_file("openssl/md5.h" HAVE_OPENSSL_MD5_H) set(optlibsfound "${optlibsfound} OpenSSL") else() set(optlibsnotfound "${optlibsnotfound} OpenSSL") @@ -411,23 +424,6 @@ else() set(optlibsnotfound "${optlibsnotfound} PCRE") endif() -# gcrypt (using find_library and find_path) -find_library(GCRYPT_LIBRARIES gcrypt) -if(GCRYPT_LIBRARIES) - set(HAVE_LIBGCRYPT 1) - list(APPEND ZM_BIN_LIBS "${GCRYPT_LIBRARIES}") - find_path(GCRYPT_INCLUDE_DIR gcrypt.h) - if(GCRYPT_INCLUDE_DIR) - include_directories("${GCRYPT_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${GCRYPT_INCLUDE_DIR}") - endif() - mark_as_advanced(FORCE GCRYPT_LIBRARIES GCRYPT_INCLUDE_DIR) - check_include_file("gcrypt.h" HAVE_GCRYPT_H) - set(optlibsfound "${optlibsfound} GCrypt") -else() - set(optlibsnotfound "${optlibsnotfound} GCrypt") -endif() - # mysqlclient (using find_library and find_path) find_library(MYSQLCLIENT_LIBRARIES mysqlclient PATH_SUFFIXES mysql) if(MYSQLCLIENT_LIBRARIES) @@ -447,129 +443,19 @@ else() message(FATAL_ERROR "ZoneMinder requires mysqlclient but it was not found on your system") endif() +find_package(FFMPEG 55.34.100 REQUIRED + COMPONENTS + avcodec + avformat + avutil + swresample + swscale) + +set(CMAKE_REQUIRED_INCLUDES ${FFMPEG_avutil_INCLUDE_DIRS}) +check_include_file("libavutil/hwcontext.h" HAVE_LIBAVUTIL_HWCONTEXT_H) + set(PATH_FFMPEG "") set(OPT_FFMPEG "no") -# avformat (using find_library and find_path) -find_library(AVFORMAT_LIBRARIES avformat) -if(AVFORMAT_LIBRARIES) - set(HAVE_LIBAVFORMAT 1) - list(APPEND ZM_BIN_LIBS "${AVFORMAT_LIBRARIES}") - find_path(AVFORMAT_INCLUDE_DIR "libavformat/avformat.h" /usr/include/ffmpeg) - if(AVFORMAT_INCLUDE_DIR) - include_directories("${AVFORMAT_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${AVFORMAT_INCLUDE_DIR}") - endif() - mark_as_advanced(FORCE AVFORMAT_LIBRARIES AVFORMAT_INCLUDE_DIR) - check_include_file("libavformat/avformat.h" HAVE_LIBAVFORMAT_AVFORMAT_H) - set(optlibsfound "${optlibsfound} AVFormat") -else() - set(optlibsnotfound "${optlibsnotfound} AVFormat") -endif() - -# avcodec (using find_library and find_path) -find_library(AVCODEC_LIBRARIES avcodec) -if(AVCODEC_LIBRARIES) - set(HAVE_LIBAVCODEC 1) - list(APPEND ZM_BIN_LIBS "${AVCODEC_LIBRARIES}") - find_path(AVCODEC_INCLUDE_DIR "libavcodec/avcodec.h" /usr/include/ffmpeg) - if(AVCODEC_INCLUDE_DIR) - include_directories("${AVCODEC_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${AVCODEC_INCLUDE_DIR}") - endif() - mark_as_advanced(FORCE AVCODEC_LIBRARIES AVCODEC_INCLUDE_DIR) - check_include_file("libavcodec/avcodec.h" HAVE_LIBAVCODEC_AVCODEC_H) - set(optlibsfound "${optlibsfound} AVCodec") -else() - message(WARNING "\nWhile it should be possible to build ZM without AVCODEC the result will pretty useless.") - set(optlibsnotfound "${optlibsnotfound} AVCodec") -endif() - -# avdevice (using find_library and find_path) -find_library(AVDEVICE_LIBRARIES avdevice) -if(AVDEVICE_LIBRARIES) - set(HAVE_LIBAVDEVICE 1) - list(APPEND ZM_BIN_LIBS "${AVDEVICE_LIBRARIES}") - find_path(AVDEVICE_INCLUDE_DIR "libavdevice/avdevice.h" /usr/include/ffmpeg) - if(AVDEVICE_INCLUDE_DIR) - include_directories("${AVDEVICE_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${AVDEVICE_INCLUDE_DIR}") - endif() - mark_as_advanced(FORCE AVDEVICE_LIBRARIES AVDEVICE_INCLUDE_DIR) - check_include_file("libavdevice/avdevice.h" HAVE_LIBAVDEVICE_AVDEVICE_H) - set(optlibsfound "${optlibsfound} AVDevice") -else() - set(optlibsnotfound "${optlibsnotfound} AVDevice") -endif() - -# avutil (using find_library and find_path) -find_library(AVUTIL_LIBRARIES avutil) -if(AVUTIL_LIBRARIES) - set(HAVE_LIBAVUTIL 1) - list(APPEND ZM_BIN_LIBS "${AVUTIL_LIBRARIES}") - find_path(AVUTIL_INCLUDE_DIR "libavutil/avutil.h" /usr/include/ffmpeg) - if(AVUTIL_INCLUDE_DIR) - include_directories("${AVUTIL_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${AVUTIL_INCLUDE_DIR}") - endif() - mark_as_advanced(FORCE AVUTIL_LIBRARIES AVUTIL_INCLUDE_DIR) - check_include_file("libavutil/avutil.h" HAVE_LIBAVUTIL_AVUTIL_H) - check_include_file("libavutil/mathematics.h" HAVE_LIBAVUTIL_MATHEMATICS_H) - check_include_file("libavutil/hwcontext.h" HAVE_LIBAVUTIL_HWCONTEXT_H) - set(optlibsfound "${optlibsfound} AVUtil") -else() - set(optlibsnotfound "${optlibsnotfound} AVUtil") -endif() - -# swscale (using find_library and find_path) -find_library(SWSCALE_LIBRARIES swscale) -if(SWSCALE_LIBRARIES) - set(HAVE_LIBSWSCALE 1) - list(APPEND ZM_BIN_LIBS "${SWSCALE_LIBRARIES}") - find_path(SWSCALE_INCLUDE_DIR "libswscale/swscale.h" /usr/include/ffmpeg) - if(SWSCALE_INCLUDE_DIR) - include_directories("${SWSCALE_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${SWSCALE_INCLUDE_DIR}") - endif() - mark_as_advanced(FORCE SWSCALE_LIBRARIES SWSCALE_INCLUDE_DIR) - check_include_file("libswscale/swscale.h" HAVE_LIBSWSCALE_SWSCALE_H) - set(optlibsfound "${optlibsfound} SWScale") -else() - set(optlibsnotfound "${optlibsnotfound} SWScale") -endif() - -# SWresample (using find_library and find_path) -find_library(SWRESAMPLE_LIBRARIES swresample) -if(SWRESAMPLE_LIBRARIES) - set(HAVE_LIBSWRESAMPLE 1) - list(APPEND ZM_BIN_LIBS "${SWRESAMPLE_LIBRARIES}") - find_path(SWRESAMPLE_INCLUDE_DIR "libswresample/swresample.h" /usr/include/ffmpeg) - if(SWRESAMPLE_INCLUDE_DIR) - include_directories("${SWRESAMPLE_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${SWRESAMPLE_INCLUDE_DIR}") - endif() - mark_as_advanced(FORCE SWRESAMPLE_LIBRARIES SWRESAMPLE_INCLUDE_DIR) - check_include_file("libswresample/swresample.h" HAVE_LIBSWRESAMPLE_SWRESAMPLE_H) - set(optlibsfound "${optlibsfound} SWResample") -else() - set(optlibsnotfound "${optlibsnotfound} SWResample") - - # AVresample (using find_library and find_path) - find_library(AVRESAMPLE_LIBRARIES avresample) - if(AVRESAMPLE_LIBRARIES) - set(HAVE_LIBAVRESAMPLE 1) - list(APPEND ZM_BIN_LIBS "${AVRESAMPLE_LIBRARIES}") - find_path(AVRESAMPLE_INCLUDE_DIR "libavresample/avresample.h" /usr/include/ffmpeg) - if(AVRESAMPLE_INCLUDE_DIR) - include_directories("${AVRESAMPLE_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${AVRESAMPLE_INCLUDE_DIR}") - endif() - mark_as_advanced(FORCE AVRESAMPLE_LIBRARIES AVRESAMPLE_INCLUDE_DIR) - check_include_file("libavresample/avresample.h" HAVE_LIBAVRESAMPLE_AVRESAMPLE_H) - set(optlibsfound "${optlibsfound} AVResample") - else() - set(optlibsnotfound "${optlibsnotfound} AVResample") - endif() -endif() # Find the path to the ffmpeg executable find_program(FFMPEG_EXECUTABLE @@ -636,29 +522,19 @@ endif() # # *** END OF LIBRARY CHECKS *** -# Check for gnutls or crypto -if((NOT HAVE_LIBCRYPTO) AND (NOT HAVE_LIBGNUTLS)) - message(FATAL_ERROR "ZoneMinder requires crypto or gnutls but none were found on your system") +# If libjwt is not present we fall back to jwt-cpp which requires OpenSSL +if((NOT HAVE_LIBJWT) AND (NOT HAVE_LIBOPENSSL)) + message(FATAL_ERROR "Using the jwt-cpp backend requires OpenSSL as crypto backend.") endif() -# Check for V4L header files and enable ZM_HAS_V4L, ZM_HAS_V4L1, ZM_HAS_V4L2 accordingly -# Setting to zeros first is required because ZM uses #define for these -set(ZM_HAS_V4L 0) -set(ZM_HAS_V4L1 0) -set(ZM_HAS_V4L2 0) -if(HAVE_LINUX_VIDEODEV_H OR HAVE_LIBV4L1_VIDEODEV_H) - set(ZM_HAS_V4L 1) - set(ZM_HAS_V4L1 1) -endif() -if(HAVE_LINUX_VIDEODEV2_H) - set(ZM_HAS_V4L 1) +find_package(V4L2) +if(TARGET V4L2::videodev2) set(ZM_HAS_V4L2 1) -endif() -if((NOT HAVE_LINUX_VIDEODEV_H) - AND (NOT HAVE_LIBV4L1_VIDEODEV_H) - AND (NOT HAVE_LINUX_VIDEODEV2_H)) +else() + set(ZM_HAS_V4L2 0) message(AUTHOR_WARNING "Video 4 Linux headers weren't found - Analog and USB camera support will not be available") endif() + # Check for PCRE and enable ZM_PCRE accordingly set(ZM_PCRE 0) if(HAVE_LIBPCRE AND HAVE_PCRE_H) @@ -679,42 +555,6 @@ if(ZM_ONVIF) set(ZM_HAS_ONVIF 1) endif() -# Check for authentication functions -if(HAVE_OPENSSL_MD5_H) - set(CMAKE_REQUIRED_LIBRARIES "${OPENSSL_LIBRARIES}") - set(CMAKE_REQUIRED_INCLUDES "${OPENSSL_INCLUDE_DIR}") - check_prototype_definition( - MD5 - "unsigned char *MD5(const unsigned char *d, size_t n, unsigned char *md)" "NULL" "openssl/md5.h" - HAVE_MD5_OPENSSL) -endif() - -if(HAVE_GNUTLS_GNUTLS_H) - set(CMAKE_REQUIRED_LIBRARIES "${GNUTLS_LIBRARIES}") - set(CMAKE_REQUIRED_INCLUDES "${GNUTLS_INCLUDE_DIR}") - check_prototype_definition( - gnutls_fingerprint - "int gnutls_fingerprint (gnutls_digest_algorithm_t algo, const gnutls_datum_t * data, void *result, size_t * result_size)" "0" "stdlib.h;gnutls/gnutls.h" - HAVE_DECL_GNUTLS_FINGERPRINT) -endif() - -if(NOT HAVE_DECL_GNUTLS_FINGERPRINT AND HAVE_MD5_OPENSSL) - set(HAVE_DECL_MD5 1) -endif() - -if((NOT HAVE_MD5_OPENSSL) AND (NOT HAVE_DECL_GNUTLS_FINGERPRINT)) - message(AUTHOR_WARNING - "ZoneMinder requires a working MD5 function for hashed authentication but - none were found - hashed authentication will not be available") -endif() - -# Dirty fix for zm_user only using openssl's md5 if gnutls and gcrypt are not available. -# This needs to be fixed in zm_user.[h,cpp] but such fix will also require changes to configure.ac -if(HAVE_LIBCRYPTO AND HAVE_OPENSSL_MD5_H AND HAVE_MD5_OPENSSL) - set(HAVE_GCRYPT_H 0) - set(HAVE_GNUTLS_OPENSSL_H 0) -endif() - # Check for Perl find_package(Perl) if(NOT PERL_FOUND) @@ -852,6 +692,9 @@ endif() message(STATUS "Optional libraries found:${optlibsfound}") message(STATUS "Optional libraries not found:${optlibsnotfound}") +message(STATUS "Enabled crypto backend: ${ZM_CRYPTO_BACKEND}") +message(STATUS "Enabled JWT backend: ${ZM_JWT_BACKEND}") + # Run ZM configuration generator message(STATUS "Running ZoneMinder configuration generator") execute_process(COMMAND perl ${CMAKE_CURRENT_BINARY_DIR}/zmconfgen.pl RESULT_VARIABLE ZMCONFGEN_RESULT) diff --git a/README.md b/README.md index 20cce499c..2249f1752 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ This is the recommended method to install ZoneMinder onto your system. ZoneMinde - Debian from their [default repository](https://packages.debian.org/search?searchon=names&keywords=zoneminder) - RHEL/CentOS and clones via [RPM Fusion](http://rpmfusion.org) - Fedora via [RPM Fusion](http://rpmfusion.org) -- OpenSuse via [third party repository](http://www.zoneminder.com/wiki/index.php/Installing_using_ZoneMinder_RPMs_for_SuSE) +- OpenSuse via [third party repository](https://wiki.zoneminder.com/Installing_using_ZoneMinder_RPMs_for_SuSE) - Mageia from their default repository - Arch via the [AUR](https://aur.archlinux.org/packages/zoneminder/) - Gentoo via [Portage Overlays](http://gpo.zugaina.org/www-misc/zoneminder) diff --git a/cmake/Modules/CheckPrototypeDefinition.c.in b/cmake/Modules/CheckPrototypeDefinition.c.in deleted file mode 100644 index a97344ac3..000000000 --- a/cmake/Modules/CheckPrototypeDefinition.c.in +++ /dev/null @@ -1,29 +0,0 @@ -@CHECK_PROTOTYPE_DEFINITION_HEADER@ - -static void cmakeRequireSymbol(int dummy, ...) { - (void) dummy; -} - -static void checkSymbol(void) { -#ifndef @CHECK_PROTOTYPE_DEFINITION_SYMBOL@ - cmakeRequireSymbol(0, &@CHECK_PROTOTYPE_DEFINITION_SYMBOL@); -#endif -} - -@CHECK_PROTOTYPE_DEFINITION_PROTO@ { - return @CHECK_PROTOTYPE_DEFINITION_RETURN@; -} - -#ifdef __CLASSIC_C__ -int main() { - int ac; - char*av[]; -#else -int main(int ac, char *av[]) { -#endif - checkSymbol(); - if (ac > 1000) { - return *av[0]; - } - return 0; -} diff --git a/cmake/Modules/CheckPrototypeDefinition.cmake b/cmake/Modules/CheckPrototypeDefinition.cmake deleted file mode 100644 index 2342b3c4f..000000000 --- a/cmake/Modules/CheckPrototypeDefinition.cmake +++ /dev/null @@ -1,98 +0,0 @@ -# - Check if the protoype we expect is correct. -# check_prototype_definition(FUNCTION PROTOTYPE RETURN HEADER VARIABLE) -# FUNCTION - The name of the function (used to check if prototype exists) -# PROTOTYPE- The prototype to check. -# RETURN - The return value of the function. -# HEADER - The header files required. -# VARIABLE - The variable to store the result. -# Example: -# check_prototype_definition(getpwent_r -# "struct passwd *getpwent_r(struct passwd *src, char *buf, int buflen)" -# "NULL" -# "unistd.h;pwd.h" -# SOLARIS_GETPWENT_R) -# The following variables may be set before calling this macro to -# modify the way the check is run: -# -# CMAKE_REQUIRED_FLAGS = string of compile command line flags -# CMAKE_REQUIRED_DEFINITIONS = list of macros to define (-DFOO=bar) -# CMAKE_REQUIRED_INCLUDES = list of include directories -# CMAKE_REQUIRED_LIBRARIES = list of libraries to link - -#============================================================================= -# Copyright 2005-2009 Kitware, Inc. -# Copyright 2010-2011 Andreas Schneider -# -# Distributed under the OSI-approved BSD License (the "License"); -# see accompanying file Copyright.txt for details. -# -# This software is distributed WITHOUT ANY WARRANTY; without even the -# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the License for more information. -#============================================================================= -# (To distribute this file outside of CMake, substitute the full -# License text for the above reference.) -# - - -get_filename_component(__check_proto_def_dir "${CMAKE_CURRENT_LIST_FILE}" PATH) - - -function(CHECK_PROTOTYPE_DEFINITION _FUNCTION _PROTOTYPE _RETURN _HEADER _VARIABLE) - - if ("${_VARIABLE}" MATCHES "^${_VARIABLE}$") - set(CHECK_PROTOTYPE_DEFINITION_CONTENT "/* */\n") - - set(CHECK_PROTOTYPE_DEFINITION_FLAGS ${CMAKE_REQUIRED_FLAGS}) - if (CMAKE_REQUIRED_LIBRARIES) - set(CHECK_PROTOTYPE_DEFINITION_LIBS - LINK_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES}) - else() - set(CHECK_PROTOTYPE_DEFINITION_LIBS) - endif() - if (CMAKE_REQUIRED_INCLUDES) - set(CMAKE_SYMBOL_EXISTS_INCLUDES - "-DINCLUDE_DIRECTORIES:STRING=${CMAKE_REQUIRED_INCLUDES}") - else() - set(CMAKE_SYMBOL_EXISTS_INCLUDES) - endif() - - foreach(_FILE ${_HEADER}) - set(CHECK_PROTOTYPE_DEFINITION_HEADER - "${CHECK_PROTOTYPE_DEFINITION_HEADER}#include <${_FILE}>\n") - endforeach() - - set(CHECK_PROTOTYPE_DEFINITION_SYMBOL ${_FUNCTION}) - set(CHECK_PROTOTYPE_DEFINITION_PROTO ${_PROTOTYPE}) - set(CHECK_PROTOTYPE_DEFINITION_RETURN ${_RETURN}) - - configure_file("${__check_proto_def_dir}/CheckPrototypeDefinition.c.in" - "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/CheckPrototypeDefinition.c" @ONLY) - - file(READ ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/CheckPrototypeDefinition.c _SOURCE) - - try_compile(${_VARIABLE} - ${CMAKE_BINARY_DIR} - ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/CheckPrototypeDefinition.c - COMPILE_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS} - ${CHECK_PROTOTYPE_DEFINITION_LIBS} - CMAKE_FLAGS -DCOMPILE_DEFINITIONS:STRING=${CHECK_PROTOTYPE_DEFINITION_FLAGS} - "${CMAKE_SYMBOL_EXISTS_INCLUDES}" - OUTPUT_VARIABLE OUTPUT) - - if (${_VARIABLE}) - set(${_VARIABLE} 1 CACHE INTERNAL "Have correct prototype for ${_FUNCTION}") - message(STATUS "Checking prototype ${_FUNCTION} for ${_VARIABLE} - True") - file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeOutput.log - "Determining if the prototype ${_FUNCTION} exists for ${_VARIABLE} passed with the following output:\n" - "${OUTPUT}\n\n") - else () - message(STATUS "Checking prototype ${_FUNCTION} for ${_VARIABLE} - False") - set(${_VARIABLE} 0 CACHE INTERNAL "Have correct prototype for ${_FUNCTION}") - file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log - "Determining if the prototype ${_FUNCTION} exists for ${_VARIABLE} failed with the following output:\n" - "${OUTPUT}\n\n${_SOURCE}\n\n") - endif () - endif() - -endfunction() diff --git a/cmake/Modules/CheckPrototypeDefinition_fixed.cmake b/cmake/Modules/CheckPrototypeDefinition_fixed.cmake deleted file mode 100644 index 550bcaa50..000000000 --- a/cmake/Modules/CheckPrototypeDefinition_fixed.cmake +++ /dev/null @@ -1,98 +0,0 @@ -# - Check if the protoype we expect is correct. -# check_prototype_definition(FUNCTION PROTOTYPE RETURN HEADER VARIABLE) -# FUNCTION - The name of the function (used to check if prototype exists) -# PROTOTYPE- The prototype to check. -# RETURN - The return value of the function. -# HEADER - The header files required. -# VARIABLE - The variable to store the result. -# Example: -# check_prototype_definition(getpwent_r -# "struct passwd *getpwent_r(struct passwd *src, char *buf, int buflen)" -# "NULL" -# "unistd.h;pwd.h" -# SOLARIS_GETPWENT_R) -# The following variables may be set before calling this macro to -# modify the way the check is run: -# -# CMAKE_REQUIRED_FLAGS = string of compile command line flags -# CMAKE_REQUIRED_DEFINITIONS = list of macros to define (-DFOO=bar) -# CMAKE_REQUIRED_INCLUDES = list of include directories -# CMAKE_REQUIRED_LIBRARIES = list of libraries to link - -#============================================================================= -# Copyright 2005-2009 Kitware, Inc. -# Copyright 2010-2011 Andreas Schneider -# -# Distributed under the OSI-approved BSD License (the "License"); -# see accompanying file Copyright.txt for details. -# -# This software is distributed WITHOUT ANY WARRANTY; without even the -# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the License for more information. -#============================================================================= -# (To distribute this file outside of CMake, substitute the full -# License text for the above reference.) -# - - -get_filename_component(__check_proto_def_dir "${CMAKE_CURRENT_LIST_FILE}" PATH) - - -function(CHECK_PROTOTYPE_DEFINITION _FUNCTION _PROTOTYPE _RETURN _HEADER _VARIABLE) - - if ("${_VARIABLE}" MATCHES "^${_VARIABLE}$") - set(CHECK_PROTOTYPE_DEFINITION_CONTENT "/* */\n") - - set(CHECK_PROTOTYPE_DEFINITION_FLAGS ${CMAKE_REQUIRED_FLAGS}) - if (CMAKE_REQUIRED_LIBRARIES) - set(CHECK_PROTOTYPE_DEFINITION_LIBS - ${LINK_LIBRARIES} ${CMAKE_REQUIRED_LIBRARIES}) - else() - set(CHECK_PROTOTYPE_DEFINITION_LIBS) - endif() - if (CMAKE_REQUIRED_INCLUDES) - set(CMAKE_SYMBOL_EXISTS_INCLUDES - "-DINCLUDE_DIRECTORIES:STRING=${CMAKE_REQUIRED_INCLUDES}") - else() - set(CMAKE_SYMBOL_EXISTS_INCLUDES) - endif() - - foreach(_FILE ${_HEADER}) - set(CHECK_PROTOTYPE_DEFINITION_HEADER - "${CHECK_PROTOTYPE_DEFINITION_HEADER}#include <${_FILE}>\n") - endforeach() - - set(CHECK_PROTOTYPE_DEFINITION_SYMBOL ${_FUNCTION}) - set(CHECK_PROTOTYPE_DEFINITION_PROTO ${_PROTOTYPE}) - set(CHECK_PROTOTYPE_DEFINITION_RETURN ${_RETURN}) - - configure_file("${__check_proto_def_dir}/CheckPrototypeDefinition.c.in" - "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/CheckPrototypeDefinition.c" @ONLY) - - file(READ ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/CheckPrototypeDefinition.c _SOURCE) - - try_compile(${_VARIABLE} - ${CMAKE_BINARY_DIR} - ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/CheckPrototypeDefinition.c - COMPILE_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS} - ${CHECK_PROTOTYPE_DEFINITION_LIBS} - CMAKE_FLAGS -DCOMPILE_DEFINITIONS:STRING=${CHECK_PROTOTYPE_DEFINITION_FLAGS} - "${CMAKE_SYMBOL_EXISTS_INCLUDES}" - OUTPUT_VARIABLE OUTPUT) - - if (${_VARIABLE}) - set(${_VARIABLE} 1 CACHE INTERNAL "Have correct prototype for ${_FUNCTION}") - message(STATUS "Checking prototype ${_FUNCTION} for ${_VARIABLE} - True") - file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeOutput.log - "Determining if the prototype ${_FUNCTION} exists for ${_VARIABLE} passed with the following output:\n" - "${OUTPUT}\n\n") - else () - message(STATUS "Checking prototype ${_FUNCTION} for ${_VARIABLE} - False") - set(${_VARIABLE} 0 CACHE INTERNAL "Have correct prototype for ${_FUNCTION}") - file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log - "Determining if the prototype ${_FUNCTION} exists for ${_VARIABLE} failed with the following output:\n" - "${OUTPUT}\n\n${_SOURCE}\n\n") - endif () - endif() - -endfunction() diff --git a/cmake/Modules/FindFFMPEG.cmake b/cmake/Modules/FindFFMPEG.cmake new file mode 100644 index 000000000..9048834c0 --- /dev/null +++ b/cmake/Modules/FindFFMPEG.cmake @@ -0,0 +1,163 @@ +#[=======================================================================[.rst: +FindFFMPEG +---------- + +Find the FFmpeg and associated libraries. + + +This module accepts following COMPONENTS:: + + avcodec + avdevice + avfilter + avformat + avutil + swresample + swscale + +IMPORTED Targets +^^^^^^^^^^^^^^^^ + +This module defines the following :prop_tgt:`IMPORTED` targets: + +``FFMPEG::`` + The FFmpeg component. + +Result Variables +^^^^^^^^^^^^^^^^ + +``FFMPEG_INCLUDE_DIRS`` + Include directories necessary to use FFmpeg. +``FFMPEG_LIBRARIES`` + Libraries necessary to use FFmpeg. Note that this only includes libraries for the components requested. +``FFMPEG_VERSION`` + The version of FFMPEG found (avutil). + + +For each component, the following are provided: + +``FFMPEG__FOUND`` + FFmpeg component was found. +``FFMPEG__INCLUDE_DIRS`` + Include directories for the component. +``FFMPEG__LIBRARIES`` + Libraries for the component. + +#]=======================================================================] + +function(_ffmpeg_find component pkgconfig_name header) + find_package(PkgConfig) + pkg_check_modules(PC_FFMPEG_${component} ${pkgconfig_name}) + + find_path(FFMPEG_${component}_INCLUDE_DIR + NAMES "lib${component}/${header}" + HINTS + ${PC_FFMPEG_${component}_INCLUDEDIR} + ${PC_FFMPEG_${component}_INCLUDE_DIRS} + PATH_SUFFIXES + ffmpeg) + mark_as_advanced("FFMPEG_${component}_INCLUDE_DIR") + + find_library(FFMPEG_${component}_LIBRARY + NAMES + ${component} + ${PC_FFMPEG_${component}_LIBRARIES} + HINTS + ${PC_FFMPEG_${component}_LIBDIR} + ${PC_FFMPEG_${component}_LIBRARY_DIRS}) + mark_as_advanced("${component}_LIBRARY") + + if(FFMPEG_${component}_LIBRARY AND FFMPEG_${component}_INCLUDE_DIR) + set(_deps_found TRUE) + set(_deps_link) + foreach(_ffmpeg_dep IN LISTS ARGN) + if(TARGET "FFMPEG::${_ffmpeg_dep}") + list(APPEND _deps_link "FFMPEG::${_ffmpeg_dep}") + else() + set(_deps_found FALSE) + endif() + endforeach() + if(_deps_found) + if(NOT TARGET "FFMPEG::${component}") + add_library("FFMPEG::${component}" UNKNOWN IMPORTED) + set_target_properties("FFMPEG::${component}" PROPERTIES + IMPORTED_LOCATION "${FFMPEG_${component}_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_${component}_INCLUDE_DIR}" + IMPORTED_LINK_INTERFACE_LIBRARIES "${_deps_link}") + endif() + set(FFMPEG_${component}_FOUND 1 PARENT_SCOPE) + set(FFMPEG_${component}_VERSION "${PC_FFMPEG_${component}_VERSION}" PARENT_SCOPE) + else() + set("FFMPEG_${component}_FOUND" 0 PARENT_SCOPE) + set(what) + if(NOT FFMPEG_${component}_LIBRARY) + set(what "library") + endif() + if(NOT FFMPEG_${component}_INCLUDE_DIR) + if(what) + string(APPEND what " or headers") + else() + set(what "headers") + endif() + endif() + set("FFMPEG_${component}_NOT_FOUND_MESSAGE" + "Could not find the ${what} for ${component}." + PARENT_SCOPE) + endif() + endif() +endfunction() + +_ffmpeg_find(avutil libavutil avutil.h) +_ffmpeg_find(swresample libswresample swresample.h + avutil) +_ffmpeg_find(swscale libswscale swscale.h + avutil) +_ffmpeg_find(avcodec libavcodec avcodec.h + avutil) +_ffmpeg_find(avformat libavformat avformat.h + avcodec avutil) +_ffmpeg_find(avfilter libavfilter avfilter.h + avutil) +_ffmpeg_find(avdevice libavdevice avdevice.h + avformat avutil) + +if(TARGET FFMPEG::avutil) + set(FFMPEG_VERSION "${FFMPEG_avutil_VERSION}") +endif() + +set(FFMPEG_INCLUDE_DIRS) +set(FFMPEG_LIBRARIES) +set(_ffmpeg_required_vars) +foreach(_ffmpeg_component IN LISTS FFMPEG_FIND_COMPONENTS) + if(TARGET "FFMPEG::${_ffmpeg_component}") + set(FFMPEG_${_ffmpeg_component}_INCLUDE_DIRS + "${FFMPEG_${_ffmpeg_component}_INCLUDE_DIR}") + set(FFMPEG_${_ffmpeg_component}_LIBRARIES + "${FFMPEG_${_ffmpeg_component}_LIBRARY}") + list(APPEND FFMPEG_INCLUDE_DIRS + "${FFMPEG_${_ffmpeg_component}_INCLUDE_DIRS}") + list(APPEND FFMPEG_LIBRARIES + "${FFMPEG_${_ffmpeg_component}_LIBRARIES}") + if(FFMEG_FIND_REQUIRED_${_ffmpeg_component}) + list(APPEND _ffmpeg_required_vars + "FFMPEG_${_ffmpeg_required_vars}_INCLUDE_DIRS" + "FFMPEG_${_ffmpeg_required_vars}_LIBRARIES") + endif() + endif() +endforeach() +unset(_ffmpeg_component) + +if(FFMPEG_INCLUDE_DIRS) + list(REMOVE_DUPLICATES FFMPEG_INCLUDE_DIRS) +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(FFMPEG + REQUIRED_VARS + FFMPEG_INCLUDE_DIRS + FFMPEG_LIBRARIES + ${_ffmpeg_required_vars} + VERSION_VAR + FFMPEG_VERSION + HANDLE_COMPONENTS) +unset(_ffmpeg_required_vars) diff --git a/cmake/Modules/FindLibJWT.cmake b/cmake/Modules/FindLibJWT.cmake index e0c834609..c82065f9d 100644 --- a/cmake/Modules/FindLibJWT.cmake +++ b/cmake/Modules/FindLibJWT.cmake @@ -1,28 +1,89 @@ -include(FindPackageHandleStandardArgs) +#[=======================================================================[.rst: +FindLibJWT +---------- +Find the JWT C Library (libjwt) + + +This module accepts optional COMPONENTS to select the crypto backend (these are mutually exclusive):: + + openssl (default) + gnutls + +IMPORTED Targets +^^^^^^^^^^^^^^^^ + +This module defines the following :prop_tgt:`IMPORTED` targets: + +``JWT::libjwt`` + The JWT library, if found with the specified crypto backend. + +Result Variables +^^^^^^^^^^^^^^^^ + +``LIBJWT_FOUND`` + System has libjwt +``LIBJWT_INCLUDE_DIRS`` + The libjwt include directory +``LIBJWT_LIBRARIES`` + The libraries needed to use libjwt +#]=======================================================================] + +include(FindPackageHandleStandardArgs) find_package(PkgConfig QUIET) + +if(LibJWT_FIND_COMPONENTS) + set(LIBJWT_CRYPTO_BACKEND "") + foreach(component IN LISTS LibJWT_FIND_COMPONENTS) + if(component MATCHES "^(openssl|gnutls)") + if(LIBJWT_CRYPTO_BACKEND) + message(FATAL_ERROR "LibJWT: Only one crypto library can be selected.") + endif() + set(LIBJWT_CRYPTO_BACKEND ${component}) + else() + message(FATAL_ERROR "LibJWT: Wrong crypto backend specified.") + endif() + endforeach() +else() + set(LIBJWT_CRYPTO_BACKEND "openssl") +endif() + +set(LIBJWT_LIB_NAMES "") +if(LIBJWT_CRYPTO_BACKEND STREQUAL "openssl") + set(LIBJWT_LIB_NAMES "jwt" "libjwt") +elseif(LIBJWT_CRYPTO_BACKEND STREQUAL "gnutls") + set(LIBJWT_LIB_NAMES "jwt-gnutls" "libjwt-gnutls") +endif() + pkg_check_modules(PC_LIBJWT QUIET libjwt) find_path(LIBJWT_INCLUDE_DIR NAMES jwt.h - HINTS ${PC_LIBJWT_INCLUDEDIR} ${PC_LIBJWT_INCLUDE_DIRS} - ) + HINTS + ${PC_LIBJWT_INCLUDEDIR} + ${PC_LIBJWT_INCLUDE_DIRS}) +mark_as_advanced(LIBJWT_INCLUDE_DIR) find_library(LIBJWT_LIBRARY - NAMES jwt-gnutls libjwt-gnutls liblibjwt-gnutls - HINTS ${PC_LIBJWT_LIBDIR} ${PC_LIBJWT_LIBRARY_DIR} - ) + NAMES ${LIBJWT_LIB_NAMES} + HINTS + ${PC_LIBJWT_LIBDIR} + ${PC_LIBJWT_LIBRARY_DIR}) +mark_as_advanced(LIBJWT_LIBRARY) find_package_handle_standard_args(LibJWT - REQUIRED_VARS LIBJWT_INCLUDE_DIR LIBJWT_LIBRARY - ) + REQUIRED_VARS + LIBJWT_INCLUDE_DIR + LIBJWT_LIBRARY + FAIL_MESSAGE + "Could NOT find LibJWT with the crypto backend ${LIBJWT_CRYPTO_BACKEND}.") if(LIBJWT_FOUND) - add_library(libjwt STATIC IMPORTED GLOBAL) - set_target_properties(libjwt PROPERTIES - IMPORTED_LOCATION "${LIBJWT_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${LIBJWT_INCLUDE_DIR}" - ) -endif() + set(LIBJWT_LIBRARIES ${LIBJWT_LIBRARY}) + set(LIBJWT_INCLUDE_DIRS ${LIBJWT_INCLUDE_DIR}) -mark_as_advanced(LIBJWT_INCLUDE_DIR LIBJWT_LIBRARY) \ No newline at end of file + add_library(JWT::libjwt UNKNOWN IMPORTED) + set_target_properties(JWT::libjwt PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${LIBJWT_INCLUDE_DIRS}" + IMPORTED_LOCATION "${LIBJWT_LIBRARY}") +endif() diff --git a/cmake/Modules/FindV4L2.cmake b/cmake/Modules/FindV4L2.cmake new file mode 100644 index 000000000..bad2e6793 --- /dev/null +++ b/cmake/Modules/FindV4L2.cmake @@ -0,0 +1,99 @@ +#[=======================================================================[.rst: +FindV4L2 +---------- + +Find V4L2 headers and libv4l2 + + +This module accepts optional COMPONENTS: + + videodev2 (default) + libv4l2 + +IMPORTED Targets +^^^^^^^^^^^^^^^^ + +This module defines the following :prop_tgt:`IMPORTED` targets:: + +``V4L2::videodev2`` + The Video for Linux Two header file, if found. +``V4L2::libv4l2`` + A thin abstraction layer on top of video4linux2 devices, if found. + +Result Variables +^^^^^^^^^^^^^^^^ + +``V4L2_FOUND`` + System has v4l2 support. If no components are specified only the videodev2.h header has to be found. +``V4L2_INCLUDE_DIRS`` + The v4l2 include directories. +``V4L2_LIBRARIES`` + The libraries needed to have v4l2 support according to the specified components. +#]=======================================================================] + +find_path(V4L2_VIDEODEV2_INCLUDE_DIR + NAMES linux/videodev2.h) +mark_as_advanced(V4L2_VIDEODEV2_INCLUDE_DIR) + +if(EXISTS "${V4L2_VIDEODEV2_INCLUDE_DIR}") + set(V4L2_videodev2_FOUND TRUE) +else() + set(V4L2_videodev2_FOUND FALSE) +endif() + +pkg_check_modules(PC_V4L2_LIBV4L2 QUIET libv4l2) + +find_path(V4L2_LIBV4L2_INCLUDE_DIR + NAMES libv4l2.h + HINTS + ${PC_V4L2_LIBV4L2_INCLUDEDIR} + ${PC_V4L2_LIBV4L2_INCLUDE_DIRS}) +mark_as_advanced(V4L2_LIBV4L2_INCLUDE_DIR) + +find_library(V4L2_LIBV4L2_LIBRARY + NAMES ${PC_V4L2_LIBV4L2_LIBRARIES} + HINTS + ${PC_V4L2_LIBV4L2_LIBDIR} + ${PC_V4L2_LIBV4L2_LIBRARY_DIR}) +mark_as_advanced(V4L2_LIBV4L2_LIBRARY) + +if(EXISTS "${V4L2_LIBV4L2_INCLUDE_DIR}" AND + EXISTS "${V4L2_LIBV4L2_LIBRARY}") + set(V4L2_libv4l2_FOUND TRUE) +else() + set(V4L2_libv4l2_FOUND FALSE) +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(V4L2 + REQUIRED_VARS + V4L2_VIDEODEV2_INCLUDE_DIR + HANDLE_COMPONENTS) + +set(V4L2_INCLUDE_DIRS) +set(V4L2_LIBRARIES) + +if(V4L2_videodev2_FOUND) + set(V4L2_VIDEODEV2_INCLUDE_DIRS ${V4L2_VIDEODEV2_INCLUDE_DIR}) + list(APPEND V4L2_INCLUDE2_DIRS + "${V4L2_VIDEODEV2_INCLUDE_DIRS}") + + add_library(V4L2::videodev2 INTERFACE IMPORTED) + set_target_properties(V4L2::videodev2 PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${V4L2_VIDEODEV2_INCLUDE_DIRS}") +endif() + +if(V4L2_libv4l2_FOUND) + set(V4L2_LIBV4L2_INCLUDE_DIRS ${V4L2_LIBV4L2_INCLUDE_DIR}) + set(V4L2_LIBV4L2_LIBRARIES ${V4L2_LIBV4L2_LIBRARY}) + + list(APPEND V4L2_INCLUDE_DIRS + "${V4L2_LIBV4L2_INCLUDE_DIRS}") + list(APPEND V4L2_LIBRARIES + "${V4L2_LIBV4L2_LIBRARIES}") + + add_library(V4L2::libv4l2 UNKNOWN IMPORTED) + set_target_properties(V4L2::libv4l2 PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${V4L2_LIBV4L2_INCLUDE_DIRS}" + IMPORTED_LOCATION "${V4L2_LIBV4L2_LIBRARY}") +endif() diff --git a/cmake/compiler/clang/settings.cmake b/cmake/compiler/clang/settings.cmake index 6c5419a94..d5ba4204a 100644 --- a/cmake/compiler/clang/settings.cmake +++ b/cmake/compiler/clang/settings.cmake @@ -3,7 +3,8 @@ target_compile_options(zm-warning-interface -Wall -Wextra -Wimplicit-fallthrough - -Wno-unused-parameter) + -Wno-unused-parameter + -Wvla) if(ENABLE_WERROR) target_compile_options(zm-warning-interface diff --git a/cmake/compiler/gcc/settings.cmake b/cmake/compiler/gcc/settings.cmake index 097270d51..638bc39b4 100644 --- a/cmake/compiler/gcc/settings.cmake +++ b/cmake/compiler/gcc/settings.cmake @@ -6,8 +6,10 @@ target_compile_options(zm-warning-interface -Wformat-security -Wno-cast-function-type $<$,11>:-Wno-clobbered> + $<$,5.1>:-Wno-missing-field-initializers> -Wno-unused-parameter - -Woverloaded-virtual) + -Woverloaded-virtual + -Wvla) if(ENABLE_WERROR) target_compile_options(zm-warning-interface @@ -18,10 +20,12 @@ endif() if(ASAN) target_compile_options(zm-compile-option-interface INTERFACE + -D_GLIBCXX_SANITIZE_VECTOR=1 -fno-omit-frame-pointer -fsanitize=address -fsanitize-recover=address - -fsanitize-address-use-after-scope) + -fsanitize-address-use-after-scope + -Wno-stringop-truncation) target_link_options(zm-compile-option-interface INTERFACE diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index fdad3f7ac..93af9039d 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -538,7 +538,7 @@ CREATE TABLE `Monitors` ( `ZoneCount` TINYINT NOT NULL DEFAULT 0, `Refresh` int(10) unsigned default NULL, `Latitude` DECIMAL(10,8), - `Longitude` DECIMAL(10,8), + `Longitude` DECIMAL(11,8), `RTSPServer` BOOLEAN NOT NULL DEFAULT FALSE, `RTSPStreamName` varchar(255) NOT NULL default '', `Importance` enum('Not','Less','Normal'), @@ -1082,11 +1082,11 @@ CREATE TABLE MontageLayouts ( PRIMARY KEY (`Id`) ); -INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES ('Freeform', '{ "default":{"float":"left","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' ); -INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES ('2 Wide', '{ "default":{"float":"left", "width":"49%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' ); +INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES ('Freeform', '{ "default":{"float":"left","left":"0px","right":"0px","top":"0px","bottom":"0px","width":"auto"} }' ); +INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES ('2 Wide', '{ "default":{"float":"left", "width":"50%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' ); INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES ('3 Wide', '{ "default":{"float":"left", "width":"33%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' ); -INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES ('4 Wide', '{ "default":{"float":"left", "width":"24.5%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' ); -INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES ('5 Wide', '{ "default":{"float":"left", "width":"19%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' ); +INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES ('4 Wide', '{ "default":{"float":"left", "width":"25%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' ); +INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES ('5 Wide', '{ "default":{"float":"left", "width":"20%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' ); CREATE TABLE Sessions ( id char(32) not null, diff --git a/db/zm_update-1.28.99.sql b/db/zm_update-1.28.99.sql index ccbc9c059..4f2054474 100644 --- a/db/zm_update-1.28.99.sql +++ b/db/zm_update-1.28.99.sql @@ -7,318 +7,146 @@ -- Add Controls definition for FI9831W -- Add Controls definition for FI8918W -- -INSERT INTO Controls -SELECT * FROM (SELECT NULL as Id, - 'ONVIF Camera' as Name, - 'Ffmpeg' as Type, - 'onvif' as Protocol, - 0 as CanWake, - 0 as CanSleep, - 1 as CanReset, - 1 as CanZoom, - 0 as CanAutoZoom, - 0 as CanZoomAbs, - 0 as CanZoomRel, - 1 as CanZoomCon, - 0 as MinZoomRange, - 0 as MaxZoomRange, - 0 as MinZoomStep, - 0 as MaxZoomStep, - 0 as HasZoomSpeed, - 0 as MinZoomSpeed, - 0 as MaxZoomSpeed, - 0 as CanFocus, - 0 as CanAutoFocus, - 0 as CanFocusAbs, - 0 as CanFocusRel, - 0 as CanFocusCon, - 0 as MinFocusRange, - 0 as MaxFocusRange, - 0 as MinFocusStep, - 0 as MaxFocusStep, - 0 as HasFocusSpeed, - 0 as MinFocusSpeed, - 0 as MaxFocusSpeed, - 1 as CanIris, - 0 as CanAutoIris, - 1 as CanIrisAbs, - 0 as CanIrisRel, - 0 as CanIrisCon, - 0 as MinIrisRange, - 255 as MaxIrisRange, - 16 as MinIrisStep, - 16 as MaxIrisStep, - 0 as HasIrisSpeed, - 0 as MinIrisSpeed, - 0 as MaxIrisSpeed, - 0 as CanGain, - 0 as CanAutoGain, - 0 as CanGainAbs, - 0 as CanGainRel, - 0 as CanGainCon, - 0 as MinGainRange, - 0 as MaxGainRange, - 0 as MinGainStep, - 0 as MaxGainStep, - 0 as HasGainSpeed, - 0 as MinGainSpeed, - 0 as MaxGainSpeed, - 1 as CanWhite, - 0 as CanAutoWhite, - 1 as CanWhiteAbs, - 0 as CanWhiteRel, - 0 as CanWhiteCon, - 0 as MinWhiteRange, - 6 as MaxWhiteRange, - 1 as MinWhiteStep, - 1 as MaxWhiteStep, - 0 as HasWhiteSpeed, - 0 as MinWhiteSpeed, - 0 as MaxWhiteSpeed, - 1 as HasPresets, - 10 as NumPresets, - 0 as HasHomePreset, - 1 as CanSetPresets, - 1 as CanMove, - 1 as CanMoveDiag, - 0 as CanMoveMap, - 0 as CanMoveAbs, - 0 as CanMoveRel, - 1 as CanMoveCon, - 1 as CanPan, - 0 as MinPanRange, - 0 as MaxPanRange, - 0 as MinPanStep, - 0 as MaxPanStep, - 0 as HasPanSpeed, - 0 as MinPanSpeed, - 0 as MaxPanSpeed, - 0 as HasTurboPan, - 0 as TurboPanSpeed, - 1 as CanTilt, - 0 as MinTiltRange, - 0 as MaxTiltRange, - 0 as MinTiltStep, - 0 as MaxTiltStep, - 0 as HasTiltSpeed, - 0 as MinTiltSpeed, - 0 as MaxTiltSpeed, - 0 as HasTurboTilt, - 0 as TurboTiltSpeed, - 0 as CanAutoScan, - 0 as NumScanPaths) AS tmp -WHERE NOT EXISTS ( - SELECT Name FROM Controls WHERE name = 'ONVIF Camera' -) LIMIT 1; +SET @s = (SELECT IF( (SELECT COUNT(*) FROM Controls WHERE Name='ONVIF Camera') > 0, +"SELECT 'ONVIF Camera Control already exists in Controls'", +"INSERT INTO Controls ( + Name,Type,Protocol, + CanWake,CanSleep,CanReset, + CanZoom,CanAutoZoom, CanZoomAbs,CanZoomRel,CanZoomCon, + MinZoomRange, MaxZoomRange, MinZoomStep,MaxZoomStep,HasZoomSpeed,MinZoomSpeed,MaxZoomSpeed, + CanFocus,CanAutoFocus,CanFocusAbs,CanFocusRel,CanFocusCon, + MinFocusRange,MaxFocusRange,MinFocusStep,MaxFocusStep,HasFocusSpeed,MinFocusSpeed,MaxFocusSpeed, + CanIris,CanAutoIris,CanIrisAbs,CanIrisRel,CanIrisCon, + MinIrisRange, MaxIrisRange, MinIrisStep, MaxIrisStep,HasIrisSpeed,MinIrisSpeed,MaxIrisSpeed, + CanGain,CanAutoGain, CanGainAbs,CanGainRel, CanGainCon, + MinGainRange,MaxGainRange, MinGainStep, MaxGainStep, HasGainSpeed, MinGainSpeed, MaxGainSpeed, + CanWhite, CanAutoWhite, CanWhiteAbs, CanWhiteRel, CanWhiteCon, + MinWhiteRange, MaxWhiteRange, MinWhiteStep, MaxWhiteStep, HasWhiteSpeed,MinWhiteSpeed,MaxWhiteSpeed, + HasPresets, NumPresets, HasHomePreset, CanSetPresets, + CanMove, CanMoveDiag, CanMoveMap, CanMoveAbs, CanMoveRel, CanMoveCon, + CanPan, MinPanRange, MaxPanRange, MinPanStep, MaxPanStep, + HasPanSpeed,MinPanSpeed,MaxPanSpeed,HasTurboPan,TurboPanSpeed,CanTilt, MinTiltRange,MaxTiltRange, MinTiltStep,MaxTiltStep, + HasTiltSpeed,MinTiltSpeed,MaxTiltSpeed, HasTurboTilt,TurboTiltSpeed, + CanAutoScan,NumScanPaths) + VALUES + ('ONVIF Camera', 'Ffmpeg', 'onvif', + 0, /* as CanWake, */ 0, /* as CanSleep, */ 1, /* as CanReset, */ -INSERT INTO Controls -SELECT * FROM (SELECT NULL as Id, - 'Foscam 9831W' as Name, - 'Ffmpeg' as Type, - 'FI9831W' as Protocol, - 0 as CanWake, - 0 as CanSleep, - 1 as CanReset, - 0 as CanZoom, - 0 as CanAutoZoom, - 0 as CanZoomAbs, - 0 as CanZoomRel, - 0 as CanZoomCon, - 0 as MinZoomRange, - 0 as MaxZoomRange, - 0 as MinZoomStep, - 0 as MaxZoomStep, - 0 as HasZoomSpeed, - 0 as MinZoomSpeed, - 0 as MaxZoomSpeed, - 0 as CanFocus, - 0 as CanAutoFocus, - 0 as CanFocusAbs, - 0 as CanFocusRel, - 0 as CanFocusCon, - 0 as MinFocusRange, - 0 as MaxFocusRange, - 0 as MinFocusStep, - 0 as MaxFocusStep, - 0 as HasFocusSpeed, - 0 as MinFocusSpeed, - 0 as MaxFocusSpeed, - 0 as CanIris, - 0 as CanAutoIris, - 0 as CanIrisAbs, - 0 as CanIrisRel, - 0 as CanIrisCon, - 0 as MinIrisRange, - 0 as MaxIrisRange, - 0 as MinIrisStep, - 0 as MaxIrisStep, - 0 as HasIrisSpeed, - 0 as MinIrisSpeed, - 0 as MaxIrisSpeed, - 0 as CanGain, - 0 as CanAutoGain, - 0 as CanGainAbs, - 0 as CanGainRel, - 0 as CanGainCon, - 0 as MinGainRange, - 0 as MaxGainRange, - 0 as MinGainStep, - 0 as MaxGainStep, - 0 as HasGainSpeed, - 0 as MinGainSpeed, - 0 as MaxGainSpeed, - 0 as CanWhite, - 0 as CanAutoWhite, - 0 as CanWhiteAbs, - 0 as CanWhiteRel, - 0 as CanWhiteCon, - 0 as MinWhiteRange, - 0 as MaxWhiteRange, - 0 as MinWhiteStep, - 0 as MaxWhiteStep, - 0 as HasWhiteSpeed, - 0 as MinWhiteSpeed, - 0 as MaxWhiteSpeed, - 0 as HasPresets, - 16 as NumPresets, - 1 as HasHomePreset, - 1 as CanSetPresets, - 1 as CanMove, - 1 as CanMoveDiag, - 0 as CanMoveMap, - 0 as CanMoveAbs, - 0 as CanMoveRel, - 1 as CanMoveCon, - 1 as CanPan, - 0 as MinPanRange, - 360 as MaxPanRange, - 0 as MinPanStep, - 360 as MaxPanStep, - 1 as HasPanSpeed, - 0 as MinPanSpeed, - 4 as MaxPanSpeed, - 0 as HasTurboPan, - 0 as TurboPanSpeed, - 1 as CanTilt, - 0 as MinTiltRange, - 90 as MaxTiltRange, - 0 as MinTiltStep, - 90 as MaxTiltStep, - 0 as HasTiltSpeed, - 0 as MinTiltSpeed, - 0 as MaxTiltSpeed, - 0 as HasTurboTilt, - 0 as TurboTiltSpeed, - 0 as CanAutoScan, - 0 as NumScanPaths) AS tmp -WHERE NOT EXISTS ( - SELECT Name FROM Controls WHERE name = 'Foscam 9831W' -) LIMIT 1; + 1, /* as CanZoom, */ 0, /* as CanAutoZoom, */ 0, /* as CanZoomAbs, */ 0, /* as CanZoomRel, */ 1, /* as CanZoomCon, */ + 0, /* as MinZoomRange, */ 0, /* as MaxZoomRange, */ 0, /* as MinZoomStep, */ 0, /* as MaxZoomStep, */ 0, /* as HasZoomSpeed, */ 0, /* as MinZoomSpeed, */ 0, /* as MaxZoomSpeed, */ + 0, /* as CanFocus, */ 0, /* as CanAutoFocus, */ 0, /* as CanFocusAbs, */ 0, /* as CanFocusRel, */ 0, /* as CanFocusCon, */ + 0, /* as MinFocusRange, */ 0, /* as MaxFocusRange, */ 0, /* as MinFocusStep, */ 0, /* as MaxFocusStep, */ 0, /* as HasFocusSpeed, */ 0, /* as MinFocusSpeed, */ 0, /* as MaxFocusSpeed, */ + 1, /* as CanIris, */ 0, /* as CanAutoIris, */ 1, /* as CanIrisAbs, */ 0, /* as CanIrisRel, */ 0, /* as CanIrisCon, */ + 0, /* as MinIrisRange, */ 255, /* as MaxIrisRange, */ 16, /* as MinIrisStep, */ 16, /* as MaxIrisStep, */ 0, /* as HasIrisSpeed, */ 0, /* as MinIrisSpeed, */ 0, /* as MaxIrisSpeed, */ + 0, /* as CanGain, */ 0, /* as CanAutoGain, */ 0, /* as CanGainAbs, */ 0, /* as CanGainRel, */ 0, /* as CanGainCon, */ + 0, /* as MinGainRange, */ 0, /* as MaxGainRange, */ 0, /* as MinGainStep, */ 0, /* as MaxGainStep, */ 0, /* as HasGainSpeed, */ 0, /* as MinGainSpeed, */ 0, /* as MaxGainSpeed, */ + 1, /* as CanWhite, */ 0, /* as CanAutoWhite, */ 1, /* as CanWhiteAbs, */ 0, /* as CanWhiteRel, */ 0, /* as CanWhiteCon, */ + 0, /* as MinWhiteRange, */ 6, /* as MaxWhiteRange, */ 1, /* as MinWhiteStep, */ 1, /* as MaxWhiteStep, */ 0, /* as HasWhiteSpeed, */ 0, /* as MinWhiteSpeed, */ 0, /* as MaxWhiteSpeed, */ + 1, /* as HasPresets, */ 10, /* as NumPresets, */ 0, /* as HasHomePreset, */ 1, /* as CanSetPresets, */ + 1, /* as CanMove, */ 1, /* as CanMoveDiag, */ 0, /* as CanMoveMap, */ 0, /* as CanMoveAbs, */ 0, /* as CanMoveRel, */ 1, /* as CanMoveCon, */ + 1, /* as CanPan, */ 0, /* as MinPanRange, */ 0, /* as MaxPanRange, */ 0, /* as MinPanStep, */ 0, /* as MaxPanStep, */ + 0, /* as HasPanSpeed, */ 0, /* as MinPanSpeed, */ 0, /* as MaxPanSpeed, */ 0, /* as HasTurboPan, */ 0, /* as TurboPanSpeed, */ + 1, /* as CanTilt, */ 0, /* as MinTiltRange, */ 0, /* as MaxTiltRange, */ 0, /* as MinTiltStep, */ 0, /* as MaxTiltStep, */ + 0, /* as HasTiltSpeed, */ 0, /* as MinTiltSpeed, */ 0, /* as MaxTiltSpeed, */ 0, /* as HasTurboTilt, */ 0, /* as TurboTiltSpeed, */ + 0, /* as CanAutoScan, */ + 0 /* as NumScanPaths */ + )" +)); -INSERT INTO Controls -SELECT * FROM (SELECT NULL as Id, - 'Foscam FI8918W' as Name, - 'Ffmpeg' as Type, - 'FI8918W' as Protocol, - 0 as CanWake, - 0 as CanSleep, - 1 as CanReset, - 0 as CanZoom, - 0 as CanAutoZoom, - 0 as CanZoomAbs, - 0 as CanZoomRel, - 0 as CanZoomCon, - 0 as MinZoomRange, - 0 as MaxZoomRange, - 0 as MinZoomStep, - 0 as MaxZoomStep, - 0 as HasZoomSpeed, - 0 as MinZoomSpeed, - 0 as MaxZoomSpeed, - 0 as CanFocus, - 0 as CanAutoFocus, - 0 as CanFocusAbs, - 0 as CanFocusRel, - 0 as CanFocusCon, - 0 as MinFocusRange, - 0 as MaxFocusRange, - 0 as MinFocusStep, - 0 as MaxFocusStep, - 0 as HasFocusSpeed, - 0 as MinFocusSpeed, - 0 as MaxFocusSpeed, - 0 as CanIris, - 0 as CanAutoIris, - 0 as CanIrisAbs, - 0 as CanIrisRel, - 0 as CanIrisCon, - 0 as MinIrisRange, - 0 as MaxIrisRange, - 0 as MinIrisStep, - 0 as MaxIrisStep, - 0 as HasIrisSpeed, - 0 as MinIrisSpeed, - 0 as MaxIrisSpeed, - 0 as CanGain, - 0 as CanAutoGain, - 0 as CanGainAbs, - 0 as CanGainRel, - 0 as CanGainCon, - 0 as MinGainRange, - 0 as MaxGainRange, - 0 as MinGainStep, - 0 as MaxGainStep, - 0 as HasGainSpeed, - 0 as MinGainSpeed, - 0 as MaxGainSpeed, - 0 as CanWhite, - 0 as CanAutoWhite, - 0 as CanWhiteAbs, - 0 as CanWhiteRel, - 0 as CanWhiteCon, - 0 as MinWhiteRange, - 0 as MaxWhiteRange, - 0 as MinWhiteStep, - 0 as MaxWhiteStep, - 0 as HasWhiteSpeed, - 0 as MinWhiteSpeed, - 0 as MaxWhiteSpeed, - 0 as HasPresets, - 8 as NumPresets, - 0 as HasHomePreset, - 1 as CanSetPresets, - 1 as CanMove, - 1 as CanMoveDiag, - 0 as CanMoveMap, - 0 as CanMoveAbs, - 0 as CanMoveRel, - 1 as CanMoveCon, - 1 as CanPan, - 0 as MinPanRange, - 360 as MaxPanRange, - 0 as MinPanStep, - 360 as MaxPanStep, - 1 as HasPanSpeed, - 0 as MinPanSpeed, - 4 as MaxPanSpeed, - 0 as HasTurboPan, - 0 as TurboPanSpeed, - 1 as CanTilt, - 0 as MinTiltRange, - 90 as MaxTiltRange, - 0 as MinTiltStep, - 90 as MaxTiltStep, - 0 as HasTiltSpeed, - 0 as MinTiltSpeed, - 0 as MaxTiltSpeed, - 0 as HasTurboTilt, - 0 as TurboTiltSpeed, - 0 as CanAutoScan, - 0 as NumScanPaths) AS tmp -WHERE NOT EXISTS ( - SELECT Name FROM Controls WHERE name = 'Foscam FI8918W' -) LIMIT 1; +PREPARE stmt FROM @s; +EXECUTE stmt; +SET @s = (SELECT IF( (SELECT COUNT(*) FROM Controls WHERE Name='Foscam 9831W') > 0, +"SELECT 'Foscam 9831W Camera Control already exists in Controls'", +"INSERT INTO Controls ( + Name,Type,Protocol, + CanWake,CanSleep,CanReset, + CanZoom,CanAutoZoom, CanZoomAbs,CanZoomRel,CanZoomCon, + MinZoomRange, MaxZoomRange, MinZoomStep,MaxZoomStep,HasZoomSpeed,MinZoomSpeed,MaxZoomSpeed, + CanFocus,CanAutoFocus,CanFocusAbs,CanFocusRel,CanFocusCon,MinFocusRange,MaxFocusRange,MinFocusStep,MaxFocusStep,HasFocusSpeed,MinFocusSpeed,MaxFocusSpeed, + CanIris,CanAutoIris,CanIrisAbs,CanIrisRel,CanIrisCon,MinIrisRange, MaxIrisRange, MinIrisStep, MaxIrisStep,HasIrisSpeed,MinIrisSpeed,MaxIrisSpeed, + CanGain,CanAutoGain, CanGainAbs,CanGainRel, CanGainCon, + MinGainRange,MaxGainRange, MinGainStep, MaxGainStep, HasGainSpeed, MinGainSpeed, MaxGainSpeed, + CanWhite, CanAutoWhite, CanWhiteAbs, CanWhiteRel, CanWhiteCon, + MinWhiteRange, MaxWhiteRange, MinWhiteStep, MaxWhiteStep, HasWhiteSpeed,MinWhiteSpeed,MaxWhiteSpeed, + HasPresets, NumPresets, HasHomePreset, CanSetPresets, + CanMove, CanMoveDiag, CanMoveMap, CanMoveAbs, CanMoveRel, CanMoveCon, + CanPan, MinPanRange, MaxPanRange, MinPanStep, MaxPanStep, + HasPanSpeed,MinPanSpeed,MaxPanSpeed,HasTurboPan,TurboPanSpeed,CanTilt, MinTiltRange,MaxTiltRange, MinTiltStep,MaxTiltStep, + HasTiltSpeed,MinTiltSpeed,MaxTiltSpeed, HasTurboTilt,TurboTiltSpeed, + CanAutoScan,NumScanPaths) + VALUES + ('Foscam 9831W', 'Ffmpeg', 'FI9831W', + 0, /* as CanWake, */ 0, /* as CanSleep, */ 1, /* as CanReset, */ + 0, /* as CanZoom, */ 0, /* as CanAutoZoom, */ 0, /* as CanZoomAbs, */ 0, /* as CanZoomRel, */ 0, /* as CanZoomCon, */ + 0, /* as MinZoomRange, */ 0, /* as MaxZoomRange, */ 0, /* as MinZoomStep, */ 0, /* as MaxZoomStep, */ 0, /* as HasZoomSpeed, */ 0, /* as MinZoomSpeed, */ 0, /* as MaxZoomSpeed, */ + 0, /* as CanFocus, */ 0, /* as CanAutoFocus, */ 0, /* as CanFocusAbs, */ 0, /* as CanFocusRel, */ 0, /* as CanFocusCon, */ + 0, /* as MinFocusRange, */ 0, /* as MaxFocusRange, */ 0, /* as MinFocusStep, */ 0, /* as MaxFocusStep, */ 0, /* as HasFocusSpeed, */ 0, /* as MinFocusSpeed, */ 0, /* as MaxFocusSpeed, */ + 0, /* as CanIris, */ 0, /* as CanAutoIris, */ 0, /* as CanIrisAbs, */ 0, /* as CanIrisRel, */ 0, /* as CanIrisCon, */ + 0, /* as MinIrisRange, */ 0, /* as MaxIrisRange, */ 0, /* as MinIrisStep, */ 0, /* as MaxIrisStep, */ 0, /* as HasIrisSpeed, */ 0, /* as MinIrisSpeed, */ 0, /* as MaxIrisSpeed, */ + 0, /* as CanGain, */ 0, /* as CanAutoGain, */ 0, /* as CanGainAbs, */ 0, /* as CanGainRel, */ 0, /* as CanGainCon, */ + 0, /* as MinGainRange, */ 0, /* as MaxGainRange, */ 0, /* as MinGainStep, */ 0, /* as MaxGainStep, */ 0, /* as HasGainSpeed, */ 0, /* as MinGainSpeed, */ 0, /* as MaxGainSpeed, */ + 0, /* as CanWhite, */ 0, /* as CanAutoWhite, */ 0, /* as CanWhiteAbs, */ 0, /* as CanWhiteRel, */ 0, /* as CanWhiteCon, */ + 0, /* as MinWhiteRange, */ 0, /* as MaxWhiteRange, */ 0, /* as MinWhiteStep, */ 0, /* as MaxWhiteStep, */ 0, /* as HasWhiteSpeed, */ 0, /* as MinWhiteSpeed, */ 0, /* as MaxWhiteSpeed, */ + 0, /* as HasPresets, */ 16, /* as NumPresets, */ 1, /* as HasHomePreset, */ 1, /* as CanSetPresets, */ + 1, /* as CanMove, */ 1, /* as CanMoveDiag, */ 0, /* as CanMoveMap, */ 0, /* as CanMoveAbs, */ 0, /* as CanMoveRel, */ 1, /* as CanMoveCon, */ + 1, /* as CanPan, */ 0, /* as MinPanRange, */ 360, /* as MaxPanRange, */ 0, /* as MinPanStep, */ 360, /* as MaxPanStep, */ + 1, /* as HasPanSpeed, */ 0, /* as MinPanSpeed, */ 4, /* as MaxPanSpeed, */ 0, /* as HasTurboPan, */ 0, /* as TurboPanSpeed, */ + 1, /* as CanTilt, */ 0, /* as MinTiltRange, */ 90, /* as MaxTiltRange, */ 0, /* as MinTiltStep, */ 90, /* as MaxTiltStep, */ + 0, /* as HasTiltSpeed, */ 0, /* as MinTiltSpeed, */ 0, /* as MaxTiltSpeed, */ 0, /* as HasTurboTilt, */ 0, /* as TurboTiltSpeed, */ + 0, /* as CanAutoScan, */ + 0 /* as NumScanPaths */ + )" + )); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( (SELECT COUNT(*) FROM Controls WHERE Name='Foscam FI8918W') > 0, +"SELECT 'Foscam 8918W Camera Control already exists in Controls'", +"INSERT INTO Controls ( + Name,Type,Protocol, + CanWake,CanSleep,CanReset, + CanZoom,CanAutoZoom, CanZoomAbs,CanZoomRel,CanZoomCon, + MinZoomRange, MaxZoomRange, MinZoomStep,MaxZoomStep,HasZoomSpeed,MinZoomSpeed,MaxZoomSpeed, + CanFocus,CanAutoFocus,CanFocusAbs,CanFocusRel,CanFocusCon,MinFocusRange,MaxFocusRange,MinFocusStep,MaxFocusStep,HasFocusSpeed,MinFocusSpeed,MaxFocusSpeed, + CanIris,CanAutoIris,CanIrisAbs,CanIrisRel,CanIrisCon,MinIrisRange, MaxIrisRange, MinIrisStep, MaxIrisStep,HasIrisSpeed,MinIrisSpeed,MaxIrisSpeed, + CanGain,CanAutoGain, CanGainAbs,CanGainRel, CanGainCon, + MinGainRange,MaxGainRange, MinGainStep, MaxGainStep, HasGainSpeed, MinGainSpeed, MaxGainSpeed, + CanWhite, CanAutoWhite, CanWhiteAbs, CanWhiteRel, CanWhiteCon, + MinWhiteRange, MaxWhiteRange, MinWhiteStep, MaxWhiteStep, HasWhiteSpeed,MinWhiteSpeed,MaxWhiteSpeed, + HasPresets, NumPresets, HasHomePreset, CanSetPresets, + CanMove, CanMoveDiag, CanMoveMap, CanMoveAbs, CanMoveRel, CanMoveCon, + CanPan, MinPanRange, MaxPanRange, MinPanStep, MaxPanStep, + HasPanSpeed,MinPanSpeed,MaxPanSpeed,HasTurboPan,TurboPanSpeed,CanTilt, MinTiltRange,MaxTiltRange, MinTiltStep,MaxTiltStep, + HasTiltSpeed,MinTiltSpeed,MaxTiltSpeed, HasTurboTilt,TurboTiltSpeed, + CanAutoScan,NumScanPaths) + VALUES + ('Foscam FI8918W', 'Ffmpeg', 'FI8918W', + 0, /* as CanWake, */ 0, /* as CanSleep, */ 1, /* as CanReset, */ + 0, /* as CanZoom, */ 0, /* as CanAutoZoom, */ 0, /* as CanZoomAbs, */ 0, /* as CanZoomRel, */ 0, /* as CanZoomCon, */ + 0, /* as MinZoomRange, */ 0, /* as MaxZoomRange, */ 0, /* as MinZoomStep, */ 0, /* as MaxZoomStep, */ 0, /* as HasZoomSpeed, */ 0, /* as MinZoomSpeed, */ 0, /* as MaxZoomSpeed, */ + 0, /* as CanFocus, */ 0, /* as CanAutoFocus, */ 0, /* as CanFocusAbs, */ 0, /* as CanFocusRel, */ 0, /* as CanFocusCon, */ + 0, /* as MinFocusRange, */ 0, /* as MaxFocusRange, */ 0, /* as MinFocusStep, */ 0, /* as MaxFocusStep, */ 0, /* as HasFocusSpeed, */ 0, /* as MinFocusSpeed, */ 0, /* as MaxFocusSpeed, */ + 0, /* as CanIris, */ 0, /* as CanAutoIris, */ 0, /* as CanIrisAbs, */ 0, /* as CanIrisRel, */ 0, /* as CanIrisCon, */ + 0, /* as MinIrisRange, */ 0, /* as MaxIrisRange, */ 0, /* as MinIrisStep, */ 0, /* as MaxIrisStep, */ 0, /* as HasIrisSpeed, */ 0, /* as MinIrisSpeed, */ 0, /* as MaxIrisSpeed, */ + 0, /* as CanGain, */ 0, /* as CanAutoGain, */ 0, /* as CanGainAbs, */ 0, /* as CanGainRel, */ 0, /* as CanGainCon, */ + 0, /* as MinGainRange, */ 0, /* as MaxGainRange, */ 0, /* as MinGainStep, */ 0, /* as MaxGainStep, */ 0, /* as HasGainSpeed, */ 0, /* as MinGainSpeed, */ 0, /* as MaxGainSpeed, */ + 0, /* as CanWhite, */ 0, /* as CanAutoWhite, */ 0, /* as CanWhiteAbs, */ 0, /* as CanWhiteRel, */ 0, /* as CanWhiteCon, */ + 0, /* as MinWhiteRange, */ 0, /* as MaxWhiteRange, */ 0, /* as MinWhiteStep, */ 0, /* as MaxWhiteStep, */ 0, /* as HasWhiteSpeed, */ 0, /* as MinWhiteSpeed, */ 0, /* as MaxWhiteSpeed, */ + 0, /* as HasPresets, */ 8, /* as NumPresets, */ 0, /* as HasHomePreset, */ 1, /* as CanSetPresets, */ + 1, /* as CanMove, */ 1, /* as CanMoveDiag, */ 0, /* as CanMoveMap, */ 0, /* as CanMoveAbs, */ 0, /* as CanMoveRel, */ 1, /* as CanMoveCon, */ + 1, /* as CanPan, */ 0, /* as MinPanRange, */ 360, /* as MaxPanRange, */ 0, /* as MinPanStep, */ 360, /* as MaxPanStep, */ + 1, /* as HasPanSpeed, */ 0, /* as MinPanSpeed, */ 4, /* as MaxPanSpeed, */ 0, /* as HasTurboPan, */ 0, /* as TurboPanSpeed, */ + 1, /* as CanTilt, */ 0, /* as MinTiltRange, */ 90, /* as MaxTiltRange, */ 0, /* as MinTiltStep, */ 90, /* as MaxTiltStep, */ + 0, /* as HasTiltSpeed, */ 0, /* as MinTiltSpeed, */ 0, /* as MaxTiltSpeed, */ 0, /* as HasTurboTilt, */ 0, /* as TurboTiltSpeed, */ + 0, /* as CanAutoScan, */ + 0 /* as NumScanPaths */ + )" + )); + +PREPARE stmt FROM @s; +EXECUTE stmt; -- -- Hide USE_DEEP_STORAGE from user to prevent accidental event loss -- diff --git a/db/zm_update-1.36.6.sql b/db/zm_update-1.36.6.sql new file mode 100644 index 000000000..e75e59610 --- /dev/null +++ b/db/zm_update-1.36.6.sql @@ -0,0 +1 @@ +ALTER TABLE `Monitors` MODIFY `Longitude` DECIMAL(11,8); diff --git a/db/zm_update-1.37.1.sql b/db/zm_update-1.37.1.sql new file mode 100644 index 000000000..e75e59610 --- /dev/null +++ b/db/zm_update-1.37.1.sql @@ -0,0 +1 @@ +ALTER TABLE `Monitors` MODIFY `Longitude` DECIMAL(11,8); diff --git a/db/zm_update-1.37.2.sql b/db/zm_update-1.37.2.sql new file mode 100644 index 000000000..0002f12ae --- /dev/null +++ b/db/zm_update-1.37.2.sql @@ -0,0 +1,4 @@ +UPDATE MontageLayouts SET `Positions` = '{ "default":{"float":"left","left":"0px","right":"0px","top":"0px","bottom":"0px","width":"auto"} }' WHERE `Name`='Freeform'; +UPDATE MontageLayouts SET `Positions` = '{ "default":{"float":"left", "width":"49%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' WHERE `Name`='2 Wide'; +UPDATE MontageLayouts SET `Positions` = '{ "default":{"float":"left", "width":"25%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' WHERE `Name`='4 Wide'; +UPDATE MontageLayouts SET `Positions` = '{ "default":{"float":"left", "width":"20%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' WHERE `Name`='5 Wide'; diff --git a/distros/beowulf/control b/distros/beowulf/control index 28135f583..50d25a0c4 100644 --- a/distros/beowulf/control +++ b/distros/beowulf/control @@ -5,7 +5,6 @@ Maintainer: Isaac Connor Uploaders: Isaac Connor Build-Depends: debhelper, sphinx-doc, dh-linktree, dh-apache2 ,cmake - ,libavdevice-dev ,libavcodec-dev ,libavformat-dev ,libavutil-dev @@ -14,7 +13,6 @@ Build-Depends: debhelper, sphinx-doc, dh-linktree, dh-apache2 ,ffmpeg ,net-tools ,libbz2-dev - ,libgcrypt20-dev ,libcurl4-gnutls-dev ,libturbojpeg0-dev ,default-libmysqlclient-dev | libmysqlclient-dev | libmariadbclient-dev-compat diff --git a/distros/beowulf/copyright b/distros/beowulf/copyright index a932efa2f..f9a69959e 100644 --- a/distros/beowulf/copyright +++ b/distros/beowulf/copyright @@ -41,7 +41,6 @@ Copyright: 2005-2013 Cake Software Foundation, Inc. (http://cakefoundation.org) License: Expat Files: - cmake/Modules/CheckPrototypeDefinition*.cmake cmake/Modules/FindGLIB2.cmake cmake/Modules/FindPolkit.cmake cmake/Modules/GNUInstallDirs.cmake diff --git a/distros/debian/control b/distros/debian/control index 6bb59f206..221a1dda5 100644 --- a/distros/debian/control +++ b/distros/debian/control @@ -9,7 +9,6 @@ Build-Depends: debhelper (>= 9), cmake , libjpeg8-dev | libjpeg-dev , libpcre3-dev , libavcodec-dev, libavformat-dev (>= 3:0.svn20090204), libswscale-dev (>= 3:0.svn20090204), libavutil-dev - , libavdevice-dev , libv4l-dev (>= 0.8.3) , libbz2-dev , ffmpeg | libav-tools @@ -17,7 +16,7 @@ Build-Depends: debhelper (>= 9), cmake , libnetpbm10-dev , libvlccore-dev, libvlc-dev , libcurl4-gnutls-dev | libcurl4-nss-dev | libcurl4-openssl-dev - , libgcrypt11-dev, libpolkit-gobject-1-dev + , libpolkit-gobject-1-dev , libphp-serialization-perl , libdate-manip-perl, libmime-lite-perl, libmime-tools-perl, libdbd-mysql-perl , libwww-perl, libarchive-tar-perl, libarchive-zip-perl, libdevice-serialport-perl @@ -47,7 +46,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,libnumber-bytes-human-perl ,libfile-slurp-perl , libpcre3 - , ffmpeg | libav-tools, libavdevice53 | libavdevice55 | libavdevice57 + , ffmpeg | libav-tools , rsyslog | system-log-daemon , netpbm , zip diff --git a/distros/opensuse/redalert.wav b/distros/opensuse/redalert.wav index 7469d4f3b..56a0ee9e4 120000 --- a/distros/opensuse/redalert.wav +++ b/distros/opensuse/redalert.wav @@ -1 +1 @@ -../redhat/misc/redalert.wav \ No newline at end of file +../redhat/common/redalert.wav \ No newline at end of file diff --git a/distros/opensuse/zoneminder.cmake.OS13.spec b/distros/opensuse/zoneminder.cmake.OS13.spec index e1ed14325..416f59662 100644 --- a/distros/opensuse/zoneminder.cmake.OS13.spec +++ b/distros/opensuse/zoneminder.cmake.OS13.spec @@ -27,7 +27,7 @@ Source: ZoneMinder-%{version}.tar.gz BuildRequires: cmake polkit-devel BuildRequires: perl-DBI perl-DBD-mysql perl-Date-Manip perl-Sys-Mmap -BuildRequires: libjpeg62 libjpeg62-devel libmysqld-devel libSDL-devel libgcrypt-devel libgnutls-devel +BuildRequires: libjpeg62 libjpeg62-devel libmysqld-devel libSDL-devel libgnutls-devel BuildRequires: libffmpeg-devel x264 BuildRequires: pcre-devel w32codec-all diff --git a/distros/redhat/zoneminder.spec b/distros/redhat/zoneminder.spec index 8c0773911..69e7fbc03 100644 --- a/distros/redhat/zoneminder.spec +++ b/distros/redhat/zoneminder.spec @@ -17,21 +17,26 @@ # This will tell zoneminder's cmake process we are building against a known distro %global zmtargetdistro %{?rhel:el%{rhel}}%{!?rhel:fc%{fedora}} -# Fedora needs apcu backwards compatibility module -%if 0%{?fedora} -%global with_apcu_bc 1 -%endif - # Newer php's keep json functions in a subpackage %if 0%{?fedora} || 0%{?rhel} >= 8 %global with_php_json 1 %endif +# el7 uses cmake3 package and macros +%if 0%{?rhel} == 7 +%global cmake %{cmake3} +%global cmake_build %{cmake3_build} +%global cmake_install %{cmake3_install} +%global cmake_pkg_name cmake3 +%else +%global cmake_pkg_name cmake +%endif + # The default for everything but el7 these days %global _hardened_build 1 Name: zoneminder -Version: 1.35.28 +Version: 1.37.1 Release: 1%{?dist} Summary: A camera monitoring and analysis tool Group: System Environment/Daemons @@ -56,7 +61,7 @@ BuildRequires: systemd-devel BuildRequires: mariadb-devel BuildRequires: perl-podlators BuildRequires: polkit-devel -BuildRequires: cmake3 +BuildRequires: %{cmake_pkg_name} BuildRequires: gnutls-devel BuildRequires: bzip2-devel BuildRequires: pcre-devel @@ -116,8 +121,8 @@ Requires: php-mysqli Requires: php-common Requires: php-gd %{?with_php_json:Requires: php-json} -Requires: php-pecl-apcu -%{?with_apcu_bc:Requires: php-pecl-apcu-bc} +%{?fedora:Requires: php-pecl-memcached} +%{?rhel:Requires: php-pecl-apcu} Requires: cambozola Requires: net-tools Requires: psmisc @@ -216,16 +221,16 @@ mv -f RtspServer-%{rtspserver_commit} ./dep/RtspServer # See https://fedoraproject.org/wiki/LTOByDefault %define _lto_cflags %{nil} -%cmake3 \ +%cmake \ -DZM_WEB_USER="%{zmuid_final}" \ -DZM_WEB_GROUP="%{zmgid_final}" \ -DZM_TARGET_DISTRO="%{zmtargetdistro}" \ . -%cmake3_build +%cmake_build %install -%cmake3_install +%cmake_install desktop-file-install \ --dir %{buildroot}%{_datadir}/applications \ @@ -425,9 +430,115 @@ ln -sf %{_sysconfdir}/zm/www/zoneminder.nginx.conf %{_sysconfdir}/zm/www/zonemin %dir %attr(755,nginx,nginx) %{_localstatedir}/log/zoneminder %changelog -* Wed Apr 07 2021 Andrew Bauer - 1.35.23-1 -- 1.35.23 Development snapshot -- Build against rtspserver +* Mon Jul 05 2021 Andrew Bauer - 1.37.1-1 +- 1.37.x development build + +* Tue Jun 22 2021 Andrew Bauer - 1.36.5-1 +- 1.36.5 release + +* Fri Jun 18 2021 Andrew Bauer - 1.36.4-2 +- apcu-bc deprecated on fedora, use memcached instead +- only refer to cmake3 when building on el7 + +* Tue Jun 08 2021 Andrew Bauer - 1.36.4-1 +- 1.36.4 release + +* Sun May 30 2021 Andrew Bauer - 1.36.3-1 +- 1.36.3 release + +* Fri May 28 2021 Andrew Bauer - 1.36.2-1 +- 1.36.2 release + +* Fri May 21 2021 Andrew Bauer - 1.36.1-1 +- 1.36.1 release +- add rtspserver submodule + +* Wed Apr 21 2021 Andrew Bauer - 1.34.26-1 +- 1.34.26 Release + +* Sun Apr 18 2021 Andrew Bauer - 1.34.24-1 +- 1.34.24 Release + +* Thu Feb 04 2021 RPM Fusion Release Engineering - 1.34.23-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_34_Mass_Rebuild + +* Sun Jan 31 2021 Andrew Bauer - 1.34.23-1 +- 1.34.23 Release +- create ssl cert in all cases, not just nginx + +* Fri Jan 1 2021 Leigh Scott - 1.34.22-3 +- Rebuilt for new ffmpeg snapshot + +* Fri Nov 27 2020 Sérgio Basto - 1.34.22-2 +- Mass rebuild for x264-0.161 + +* Mon Sep 28 2020 Andrew Bauer - 1.34.22-1 +- 1.34.22 Release + +* Mon Sep 28 2020 Andrew Bauer - 1.34.21-1 +- 1.34.21 Release + +* Sun Aug 23 2020 Andrew Bauer - 1.34.20-1 +- 1.34.20 Release +- Buildrequire epel-rpm-macros on rhel +- Update license references for js libraries + +* Wed Aug 19 2020 RPM Fusion Release Engineering - 1.34.18-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild + +* Thu Aug 06 2020 Andrew Bauer - 1.34.18-1 +- 1.34.18 Release + +* Thu Aug 06 2020 Andrew Bauer - 1.34.17-2 +- Disable LTO due to top level asm + +* Wed Aug 05 2020 Andrew Bauer - 1.34.17-1 +- 1.34.17 Release + +* Tue Aug 04 2020 Andrew Bauer - 1.34.16-4 +- Use new cmake build macros for f33 compat + +* Tue Jul 07 2020 Sérgio Basto - 1.34.16-3 +- Mass rebuild for x264 + +* Fri Jul 03 2020 Leigh Scott - 1.34.16-2 +- Perl 5.32 rebuild + +* Fri Jun 05 2020 Andrew Bauer - 1.34.16-1 +- 1.34.16 Release + +* Fri May 15 2020 Andrew Bauer - 1.34.14-1 +- 1.34.14 Release + +* Thu May 14 2020 Andrew Bauer - 1.34.13-1 +- 1.34.13 Release + +* Sun May 10 2020 Andrew Bauer - 1.34.12-1 +- 1.34.12 Release + +* Sat May 2 2020 Andrew Bauer - 1.34.11-1 +- 1.34.11 Release + +* Sun Apr 26 2020 Andrew Bauer - 1.34.10-1 +- 1.34.10 Release + +* Mon Apr 6 2020 Andrew Bauer - 1.34.9-1 +- 1.34.9 Release + +* Sat Mar 28 2020 Andrew Bauer - 1.34.7-1 +- 1.34.7 Release + +* Mon Mar 23 2020 Andrew Bauer - 1.34.6-1 +- 1.34.6 Release + +* Sat Feb 29 2020 Andrew Bauer - 1.34.5-1 +- 1.34.5 Release + +* Sun Feb 23 2020 Andrew Bauer - 1.34.3-1 +- 1.34.3 Release + +* Sat Feb 22 2020 RPM Fusion Release Engineering - 1.34.2-2 +- Rebuild for ffmpeg-4.3 git * Tue Feb 04 2020 Andrew Bauer - 1.34.2-1 - 1.34.2 Release diff --git a/distros/ubuntu1504_cmake_split_packages/control b/distros/ubuntu1504_cmake_split_packages/control index d4be60413..1345c39ee 100644 --- a/distros/ubuntu1504_cmake_split_packages/control +++ b/distros/ubuntu1504_cmake_split_packages/control @@ -8,14 +8,14 @@ Build-Depends: debhelper (>= 9), po-debconf (>= 1.0), autoconf, automake, libtoo , libdate-manip-perl, libwww-perl , libjpeg8-dev | libjpeg9-dev | libjpeg62-turbo-dev , libpcre3-dev -, libavcodec-ffmpeg-dev, libavformat-ffmpeg-dev, libswscale-ffmpeg-dev, libavutil-ffmpeg-dev, libavdevice-ffmpeg-dev +, libavcodec-ffmpeg-dev, libavformat-ffmpeg-dev, libswscale-ffmpeg-dev, libavutil-ffmpeg-dev , libv4l-dev (>= 0.8.3) , libbz2-dev , libsys-mmap-perl , libdevice-serialport-perl, libarchive-zip-perl, libmime-lite-perl , libvlccore-dev, libvlc-dev , libcurl4-gnutls-dev | libcurl4-nss-dev | libcurl4-openssl-dev -, libgcrypt11-dev | libgcrypt20-dev, libpolkit-gobject-1-dev +, libpolkit-gobject-1-dev , libdbi-perl, libnet-sftp-foreign-perl, libexpect-perl, libmime-tools-perl Standards-Version: 3.9.6 Homepage: http://www.zoneminder.com/ diff --git a/distros/ubuntu1504_cmake_split_packages/rules b/distros/ubuntu1504_cmake_split_packages/rules index 4cf1e0c58..218ab3c17 100755 --- a/distros/ubuntu1504_cmake_split_packages/rules +++ b/distros/ubuntu1504_cmake_split_packages/rules @@ -15,7 +15,6 @@ DEB_HOST_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_HOST_GNU_TYPE) DEB_BUILD_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_BUILD_GNU_TYPE) CFLAGS = -Wall -CXXFLAGS = -DHAVE_LIBCRYPTO ifneq (,$(findstring debug,$(DEB_BUILD_OPTIONS))) DEBOPT = --enable-debug diff --git a/distros/ubuntu1604/NEWS b/distros/ubuntu1604/NEWS deleted file mode 100644 index 6200726cf..000000000 --- a/distros/ubuntu1604/NEWS +++ /dev/null @@ -1,10 +0,0 @@ -zoneminder (1.28.1-1) unstable; urgency=low - - This version is no longer automatically initialize or upgrade database. - See README.Debian for details. - - Changed installation paths (please correct your web server configuration): - /usr/share/zoneminder --> /usr/share/zoneminder/www - /usr/lib/cgi-bin --> /usr/lib/zoneminder/cgi-bin - - -- Dmitry Smirnov Tue, 31 Mar 2015 15:12:17 +1100 diff --git a/distros/ubuntu1604/README.Debian b/distros/ubuntu1604/README.Debian deleted file mode 100644 index 4fe3464d2..000000000 --- a/distros/ubuntu1604/README.Debian +++ /dev/null @@ -1,130 +0,0 @@ -Zoneminder for Debian ---------------------- - -Initializing database ---------------------- - - pv /usr/share/zoneminder/db/zm_create.sql | sudo mysql --defaults-file=/etc/mysql/debian.cnf -OR - cat /usr/share/zoneminder/db/zm_create.sql | sudo mysql --defaults-file=/etc/mysql/debian.cnf - - echo 'grant lock tables,alter,create,index,select,insert,update,delete on zm.* to 'zmuser'@localhost identified by "zmpass";'\ - | sudo mysql --defaults-file=/etc/mysql/debian.cnf mysql - -Hint: generate secure password with `pwgen` and update "/etc/zm/zm.conf" -accordingly. - -The following command can help to ensure that zoneminder can read its -configuration file: - - chgrp -c www-data /etc/zm/zm.conf - - -Upgrading database ------------------- - -The database is updated automatically on installation. You should not need to take this step. - -Assuming that database is on "localhost" then the following command can be -used to upgrade "zm" database: - - zmupdate.pl - -Additional permissions may be required to perform upgrade: - - echo 'grant lock tables, create, alter on zm.* to 'zmuser'@localhost identified by "zmpass";'\ - | sudo mysql --defaults-file=/etc/mysql/debian.cnf mysql - -The following command prints the current version of zoneminder database: - - echo 'select Value from Config where Name = "ZM_DYN_CURR_VERSION";' \ - | sudo mysql --defaults-file=/etc/mysql/debian.cnf --skip-column-names zm - - -Enabling service ----------------- - -By default Zoneminder service is not automatically started and needs to be -manually enabled once database is configured: - - sudo systemctl enable zoneminder.service - - -Web server set-up ------------------ - -There are few manual steps to get the web interface working: - -## Apache2 - -Apache can be configured as folder "/zm" using sample .conf: - - sudo a2enconf zoneminder - -Alternatively Apache web site configuration template can be used to setup -zoneminder as "http://zoneminder": - - sudo cp -v /usr/share/doc/zoneminder/examples/apache.conf /etc/apache2/sites-available/ - sudo a2ensite zoneminder.conf - -Common configuration steps for Apache2: - - sudo a2enmod cgi - sudo service apache2 reload - - -## nginx / fcgiwrap - -Nginx needs "php-fpm" package to support PHP and "fcgiwrap" package -for binary "cgi-bin" applications: - - sudo apt-get install php-fpm fcgiwrap - -To enable a URL alias that makes Zoneminder available from - - http://yourserver/zm - -the following line is to be added to "server" section of a web site -configuration: - - include /usr/share/doc/zoneminder/examples/nginx.conf; - -For "default" web site it would be sufficient to include the above -statement to the file - - /etc/nginx/sites-enabled/default - -To avoid problems with feeds from multiple cameras "fcgiwrap" should be -configured to start at least as many processes as there are cameras. -It can be done by adjusting DAEMON_OPTS in "/etc/default/fcgiwrap". -Systemd users may be affected by the following bug: - - http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=792705 - - -## Note: - -When Zoneminder web site is running it may be necessary to set -Options/Paths/PATH_ZMS to "/zm/cgi-bin/nph-zms" or according to chosen web -site configuration. - - -Changing the location for images and events -------------------------------------------- - -ZoneMinder is now able to be configured to use an alternative location for storing -events and images at compile time. This package makes use of that, so symlinks in -/usr/share/zoneminder/www are no longer necessary. - -Access to /dev/video* ---------------------- - -For cameras which require access to /dev/video*, zoneminder may need the -www-data user added to the video group in order to see those cameras: - - adduser www-data video - -Note that all web applications running on the zoneminder server will then have -access to all video devices on the system. - - -- Vagrant Cascadian Sun, 27 Mar 2011 13:06:56 -0700 diff --git a/distros/ubuntu1604/TODO.Debian b/distros/ubuntu1604/TODO.Debian deleted file mode 100644 index 9dc59613b..000000000 --- a/distros/ubuntu1604/TODO.Debian +++ /dev/null @@ -1,12 +0,0 @@ - -## Separate substantial /usr/share into its own arch-all package. - -## Decide how to handle database updates. - - * Consider possibility that database may be on another machine (#469239). - * Consider dbconfig-common? Probably not (what if database is not on localhost?). - -### Run `zmupdate.pl` from service control scripts (init.d, service) on start? - - Automatic upgrade will break "one DB, many zoneminders" setup (unimportant?). - diff --git a/distros/ubuntu1604/changelog b/distros/ubuntu1604/changelog deleted file mode 100644 index 0fc2fda2e..000000000 --- a/distros/ubuntu1604/changelog +++ /dev/null @@ -1,3 +0,0 @@ -zoneminder (1.35.6~20200825.27-xenial) xenial; urgency=low - * - -- Isaac Connor Tue, 25 Aug 2020 09:28:18 -0400 diff --git a/distros/ubuntu1604/clean b/distros/ubuntu1604/clean deleted file mode 100644 index 941ef2a3a..000000000 --- a/distros/ubuntu1604/clean +++ /dev/null @@ -1,3 +0,0 @@ -.gitattributes -web/api/.gitattributes -web/api/.gitignore diff --git a/distros/ubuntu1604/compat b/distros/ubuntu1604/compat deleted file mode 100644 index ec635144f..000000000 --- a/distros/ubuntu1604/compat +++ /dev/null @@ -1 +0,0 @@ -9 diff --git a/distros/ubuntu1604/conf/apache2/zoneminder.conf b/distros/ubuntu1604/conf/apache2/zoneminder.conf deleted file mode 100644 index e3164d36c..000000000 --- a/distros/ubuntu1604/conf/apache2/zoneminder.conf +++ /dev/null @@ -1,57 +0,0 @@ -# Remember to enable cgi mod (i.e. "a2enmod cgi"). -ScriptAlias /zm/cgi-bin "/usr/lib/zoneminder/cgi-bin" - - Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch - AllowOverride All - Require all granted - - - -# Order matters. This alias must come first. -Alias /zm/cache /var/cache/zoneminder/cache - - Options -Indexes +FollowSymLinks - AllowOverride None - - # Apache 2.4 - Require all granted - - - # Apache 2.2 - Order deny,allow - Allow from all - - - -Alias /zm /usr/share/zoneminder/www - - Options -Indexes +FollowSymLinks - - DirectoryIndex index.php - - - -# For better visibility, the following directives have been migrated from the -# default .htaccess files included with the CakePHP project. -# Parameters not set here are inherited from the parent directive above. - - RewriteEngine on - RewriteRule ^$ app/webroot/ [L] - RewriteRule (.*) app/webroot/$1 [L] - RewriteBase /zm/api - - - - RewriteEngine on - RewriteRule ^$ webroot/ [L] - RewriteRule (.*) webroot/$1 [L] - RewriteBase /zm/api - - - - RewriteEngine On - RewriteCond %{REQUEST_FILENAME} !-d - RewriteCond %{REQUEST_FILENAME} !-f - RewriteRule ^ index.php [L] - RewriteBase /zm/api - diff --git a/distros/ubuntu1604/control b/distros/ubuntu1604/control deleted file mode 100644 index 0def73f60..000000000 --- a/distros/ubuntu1604/control +++ /dev/null @@ -1,165 +0,0 @@ -Source: zoneminder -Section: net -Priority: optional -Maintainer: Isaac Connor -Uploaders: Isaac Connor -Build-Depends: debhelper (>= 9), dh-systemd, python3-sphinx, apache2-dev, dh-linktree, dh-systemd, dh-apache2 - ,cmake - ,libavdevice-dev (>= 6:10~) - ,libavcodec-dev (>= 6:10~) - ,libavformat-dev (>= 6:10~) - ,libavutil-dev (>= 6:10~) - ,libswresample-dev | libavresample-dev - ,libswscale-dev (>= 6:10~) - ,ffmpeg | libav-tools - ,net-tools - ,libbz2-dev - ,libgcrypt-dev | libgcrypt11-dev - ,libcurl4-gnutls-dev - ,libgnutls-openssl-dev - ,libjpeg8-dev | libjpeg9-dev | libjpeg62-turbo-dev - ,default-libmysqlclient-dev | libmysqlclient-dev | libmariadbclient-dev-compat - ,libpcre3-dev - ,libpolkit-gobject-1-dev - ,libv4l-dev (>= 0.8.3) [!hurd-any] - ,libvlc-dev - ,libdate-manip-perl - ,libdbd-mysql-perl - ,libphp-serialization-perl - ,libsys-mmap-perl [!hurd-any] - ,libwww-perl - ,libdata-uuid-perl - ,libssl-dev - ,libcrypt-eksblowfish-perl - ,libdata-entropy-perl - ,libvncserver-dev -Standards-Version: 3.9.8 -Homepage: http://www.zoneminder.com/ -Vcs-Browser: http://anonscm.debian.org/cgit/collab-maint/zoneminder.git -Vcs-Git: git://anonscm.debian.org/collab-maint/zoneminder.git - -Package: zoneminder -Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} - ,javascript-common - ,libswscale-ffmpeg3|libswscale4|libswscale3|libswscale5 - ,libswresample2|libswresample3|libswresample24|libswresample-ffmpeg1 - ,ffmpeg | libav-tools - ,libdate-manip-perl, libmime-lite-perl, libmime-tools-perl - ,libdbd-mysql-perl - ,libphp-serialization-perl - ,libmodule-load-conditional-perl - ,libnet-sftp-foreign-perl - ,libarchive-zip-perl - ,libdbd-mysql-perl - ,libdevice-serialport-perl - ,libimage-info-perl - ,libjson-maybexs-perl - ,libsys-mmap-perl [!hurd-any] - ,liburi-encode-perl - ,libwww-perl, liburi-perl - ,libdata-dump-perl - ,libdatetime-perl - ,libclass-std-fast-perl - ,libsoap-wsdl-perl - ,libio-socket-multicast-perl - ,libdigest-sha-perl - ,libsys-cpu-perl, libsys-meminfo-perl - ,libdata-uuid-perl - ,libnumber-bytes-human-perl - ,libfile-slurp-perl - ,mysql-client | mariadb-client | virtual-mysql-client - ,perl-modules - ,php5-mysql | php-mysql, php5-gd | php-gd , php5-apcu | php-apcu , php-apc | php-apcu-bc, php-json | php5-json - ,policykit-1 - ,rsyslog | system-log-daemon - ,zip - ,libpcre3 - ,libssl | libssl1.0.0 | libssl1.1 - ,libcrypt-eksblowfish-perl - ,libdata-entropy-perl - ,libvncclient1|libvncclient0 -Recommends: ${misc:Recommends} - ,libapache2-mod-php5 | libapache2-mod-php | php5-fpm | php-fpm - ,mysql-server | mariadb-server | virtual-mysql-server - ,zoneminder-doc (>= ${source:Version}) - ,ffmpeg -Suggests: fcgiwrap, logrotate -Description: video camera security and surveillance solution - ZoneMinder is intended for use in single or multi-camera video security - applications, including commercial or home CCTV, theft prevention and child - or family member or home monitoring and other care scenarios. It - supports capture, analysis, recording, and monitoring of video data coming - from one or more video or network cameras attached to a Linux system. - ZoneMinder also support web and semi-automatic control of Pan/Tilt/Zoom - cameras using a variety of protocols. It is suitable for use as a home - video security system and for commercial or professional video security - and surveillance. It can also be integrated into a home automation system - via X.10 or other protocols. - -#Package: libzoneminder-perl -#Section: perl -#Architecture: all -#Multi-Arch: foreign -#Depends: ${misc:Depends}, ${perl:Depends} -# ,libarchive-zip-perl -# ,libdbd-mysql-perl -# ,libdevice-serialport-perl -# ,libimage-info-perl -# ,libjson-maybexs-perl -# ,libsys-mmap-perl [!hurd-any] -# ,liburi-encode-perl -# ,libwww-perl -#Description: ZoneMinder Perl libraries -# ZoneMinder is intended for use in single or multi-camera video security -# applications, including commercial or home CCTV, theft prevention and child -# or family member or home monitoring and other care scenarios. It -# supports capture, analysis, recording, and monitoring of video data coming -# from one or more video or network cameras attached to a Linux system. -# ZoneMinder also support web and semi-automatic control of Pan/Tilt/Zoom -# cameras using a variety of protocols. It is suitable for use as a home -# video security system and for commercial or professional video security -# and surveillance. It can also be integrated into a home automation system -# via X.10 or other protocols. -# . -# This package provides ZoneMinder Perl libraries; it can be used to -# write custom interfaces as well. - -Package: zoneminder-doc -Section: doc -Architecture: all -Multi-Arch: foreign -Depends: ${misc:Depends}, ${sphinxdoc:Depends}, python3-sphinx-rtd-theme -Suggests: www-browser -Description: ZoneMinder documentation - ZoneMinder is intended for use in single or multi-camera video security - applications, including commercial or home CCTV, theft prevention and child - or family member or home monitoring and other care scenarios. It - supports capture, analysis, recording, and monitoring of video data coming - from one or more video or network cameras attached to a Linux system. - ZoneMinder also support web and semi-automatic control of Pan/Tilt/Zoom - cameras using a variety of protocols. It is suitable for use as a home - video security system and for commercial or professional video security - and surveillance. It can also be integrated into a home automation system - via X.10 or other protocols. - . - This package provides ZoneMinder documentation in HTML format. - -Package: zoneminder-dbg -Section: debug -Priority: extra -Architecture: any -Depends: zoneminder (= ${binary:Version}), ${misc:Depends} -Description: Zoneminder -- debugging symbols - ZoneMinder is intended for use in single or multi-camera video security - applications, including commercial or home CCTV, theft prevention and child - or family member or home monitoring and other care scenarios. It - supports capture, analysis, recording, and monitoring of video data coming - from one or more video or network cameras attached to a Linux system. - ZoneMinder also support web and semi-automatic control of Pan/Tilt/Zoom - cameras using a variety of protocols. It is suitable for use as a home - video security system and for commercial or professional video security - and surveillance. It can also be integrated into a home automation system - via X.10 or other protocols. - . - This package provides debugging symbols diff --git a/distros/ubuntu1604/copyright b/distros/ubuntu1604/copyright deleted file mode 100644 index a932efa2f..000000000 --- a/distros/ubuntu1604/copyright +++ /dev/null @@ -1,168 +0,0 @@ -Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: ZoneMinder -Upstream-Contact: Philip Coombes -Source: https://github.com/ZoneMinder/ZoneMinder -Comment: - This package was originally debianized by matrix - on Mon, 7 Mar 2005 02:07:57 -0500. - It was re-done for submission to the Debian project by Peter Howard - on Fri, 8 Dec 2006 10:19:43 +1100 -Files-Excluded: - web/skins/*/js/jquery-* - -Files: * -Copyright: 2001-2014 Philip Coombes - 2008 Brian Rudy - 2014 Vincent Giovannone - 2013 Tim Craig - 2003-2008 Corey DeLasaux - 2001-2010 Chris Kistner -License: GPL-2+ - -Files: distros/* -Copyright: 2001-2008 Philip Coombes - 2014 Isaac Connor - 2005 Serg Oskin -License: GPL-2+ - -Files: web/skins/*/js/jquery-* -Copyright: 2010 John Resig - 2010 The Dojo Foundation -License: GPL-2 or Expat -Comment: - Dual licensed under the MIT or GPL Version 2 licenses. - http://jquery.org/license - . - Includes Sizzle.js http://sizzlejs.com/ - Released under the MIT, BSD, and GPL Licenses. - -Files: web/api/* -Copyright: 2005-2013 Cake Software Foundation, Inc. (http://cakefoundation.org) -License: Expat - -Files: - cmake/Modules/CheckPrototypeDefinition*.cmake - cmake/Modules/FindGLIB2.cmake - cmake/Modules/FindPolkit.cmake - cmake/Modules/GNUInstallDirs.cmake -Copyright: - 2005-2011 Kitware, Inc. - 2010-2011 Andreas Schneider - 2009 Dario Freddi - 2008 Laurent Montel, - 2011 Nikita Krupen'ko -License: BSD-3-clause - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are - met: - . - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - . - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - . - * The names of Kitware, Inc., the Insight Consortium, or the names of - any consortium members, or of any contributors, may not be used to - endorse or promote products derived from this software without - specific prior written permission. - . - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS IS'' - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR - ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -Files: cmake/Modules/FindPerlModules.cmake -Copyright: 2012 Iowa State University -License: Boost-1.0 - Boost Software License - Version 1.0 - August 17th, 2003 - . - Permission is hereby granted, free of charge, to any person or organization - obtaining a copy of the software and accompanying documentation covered by - this license (the "Software") to use, reproduce, display, distribute, - execute, and transmit the Software, and to prepare derivative works of the - Software, and to permit third-parties to whom the Software is furnished to - do so, all subject to the following: - . - The copyright notices in the Software and this entire statement, including - the above license grant, this restriction and the following disclaimer, - must be included in all copies of the Software, in whole or in part, and - all derivative works of the Software, unless such copies or derivative - works are solely in the form of machine-executable object code generated by - a source language processor. - . - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT - SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE - FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, - ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. - -Files: debian/* -Copyright: 2015 Dmitry Smirnov - 2007-2014 Peter Howard - 2010-2012 Vagrant Cascadian - 2001-2008 Philip Coombes -License: GPL-2+ - -License: Expat - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - . - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - . - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - -License: GPL-2+ - This package 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 package 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 package; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - . - The complete text of the GNU General Public License version 2 - can be found in "/usr/share/common-licenses/GPL-2". - -License: GPL-2 - This package 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; version 2 of the License. - . - This package 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 package; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - . - The complete text of the GNU General Public License version 2 - can be found in "/usr/share/common-licenses/GPL-2". diff --git a/distros/ubuntu1604/examples/nginx.conf b/distros/ubuntu1604/examples/nginx.conf deleted file mode 100644 index 5636ca3e1..000000000 --- a/distros/ubuntu1604/examples/nginx.conf +++ /dev/null @@ -1,32 +0,0 @@ -location /zm/cgi-bin { - gzip off; - alias /usr/lib/zoneminder/cgi-bin; - - include /etc/nginx/fastcgi_params; - fastcgi_param SCRIPT_FILENAME $request_filename; - fastcgi_pass unix:/var/run/fcgiwrap.socket; -} - -location /zm { -# if ($scheme ~ ^http:){ -# rewrite ^(.*)$ https://$host$1 permanent; -# } - - gzip off; - alias /usr/share/zoneminder/www; - index index.php; - - location ~ \.php$ { - if (!-f $request_filename) { return 404; } - expires epoch; - include /etc/nginx/fastcgi_params; - fastcgi_param SCRIPT_FILENAME $request_filename; - fastcgi_index index.php; - fastcgi_pass unix:/var/run/php5-fpm.sock; - } - - location ~ \.(jpg|jpeg|gif|png|ico)$ { - access_log off; - expires 33d; - } -} diff --git a/distros/ubuntu1604/gbp.conf b/distros/ubuntu1604/gbp.conf deleted file mode 100644 index 4608913d9..000000000 --- a/distros/ubuntu1604/gbp.conf +++ /dev/null @@ -1,7 +0,0 @@ - -[dch] -id-length = 0 - -[import-orig] -pristine-tar = False -merge = False diff --git a/distros/ubuntu1604/libzoneminder-perl.install b/distros/ubuntu1604/libzoneminder-perl.install deleted file mode 100644 index 67191d9cf..000000000 --- a/distros/ubuntu1604/libzoneminder-perl.install +++ /dev/null @@ -1,2 +0,0 @@ -usr/share/man/man3 -usr/share/perl5 diff --git a/distros/ubuntu1604/patches/series b/distros/ubuntu1604/patches/series deleted file mode 100644 index e69de29bb..000000000 diff --git a/distros/ubuntu1604/rules b/distros/ubuntu1604/rules deleted file mode 100755 index 9a16b1f8f..000000000 --- a/distros/ubuntu1604/rules +++ /dev/null @@ -1,96 +0,0 @@ -#!/usr/bin/make -f -# -*- makefile -*- - -# Uncomment this to turn on verbose mode. -#export DH_VERBOSE=1 - -export DEB_BUILD_MAINT_OPTIONS = hardening=+all -export DEB_LDFLAGS_MAINT_APPEND += -Wl,--as-needed - -ifeq ($(DEB_BUILD_ARCH_OS),hurd) -ARGS:= -DZM_NO_MMAP=ON -endif - -%: - dh $@ --parallel --buildsystem=cmake --builddirectory=dbuild \ - --with systemd,sphinxdoc,apache2,linktree - -override_dh_auto_configure: - dh_auto_configure -- $(ARGS) \ - -DCMAKE_VERBOSE_MAKEFILE=ON \ - -DCMAKE_BUILD_TYPE=Release \ - -DZM_CONFIG_DIR="/etc/zm" \ - -DZM_CONFIG_SUBDIR="/etc/zm/conf.d" \ - -DZM_RUNDIR="/run/zm" \ - -DZM_SOCKDIR="/run/zm" \ - -DZM_TMPDIR="/tmp/zm" \ - -DZM_CGIDIR="/usr/lib/zoneminder/cgi-bin" \ - -DZM_CACHEDIR="/var/cache/zoneminder/cache" \ - -DZM_DIR_EVENTS="/var/cache/zoneminder/events" \ - -DZM_PATH_SHUTDOWN="/sbin/shutdown" \ - -DZM_PATH_ZMS="/zm/cgi-bin/nph-zms" - -override_dh_clean: - dh_clean $(MANPAGES1) - $(RM) -r docs/_build - -build-indep: - #$(MAKE) -C docs text - $(MAKE) -C docs html - -MANPAGES1 = dbuild/scripts/zmupdate.pl.1 -$(MANPAGES1): - # generate man page(s): - pod2man -s1 --stderr --utf8 $(patsubst %.1, %, $@) $@ - -## reproducible build: -LAST_CHANGE=$(shell dpkg-parsechangelog -S Date) -BUILD_DATE=$(shell LC_ALL=C date -u "+%B %d, %Y" -d "$(LAST_CHANGE)") -override_dh_installman: $(MANPAGES1) - $(MAKE) -C docs man SPHINXOPTS="-D today=\"$(BUILD_DATE)\"" - dh_installman --language=C $(MANPAGES1) - -override_dh_auto_install: - dh_auto_install --destdir=$(CURDIR)/debian/tmp - # remove worthless files: - $(RM) -v $(CURDIR)/debian/tmp/usr/share/perl5/*/*/*/.packlist - $(RM) -v $(CURDIR)/debian/tmp/usr/share/perl5/*/*.in - # remove empty directories: - find $(CURDIR)/debian/tmp/usr -type d -empty -delete -printf 'removed %p\n' - # remove extra-license-file: - $(RM) -v $(CURDIR)/debian/tmp/usr/share/zoneminder/www/api/lib/Cake/LICENSE.txt - -override_dh_fixperms: - dh_fixperms - # - # As requested by the Debian Webapps Policy Manual §3.2.1 - chown root:www-data $(CURDIR)/debian/zoneminder/etc/zm/zm.conf - chmod 640 $(CURDIR)/debian/zoneminder/etc/zm/zm.conf - -override_dh_systemd_start: - dh_systemd_start --no-start - -override_dh_systemd_enable: - dh_systemd_enable --no-enable - -override_dh_apache2: - dh_apache2 --noenable - -override_dh_strip: - [ -d "$(CURDIR)/debian/zoneminder-dbg" ] \ - && dh_strip --dbg-package=zoneminder-dbg \ - || dh_strip - -#%: -# dh $@ --parallel --buildsystem=autoconf --with autoreconf -# -#override_dh_auto_configure: -# dh_auto_configure -- \ -# --sysconfdir=/etc/zm \ -# --with-mysql=/usr \ -# --with-webdir=/usr/share/zoneminder \ -# --with-ffmpeg=/usr \ -# --with-cgidir=/usr/lib/cgi-bin \ -# --with-webuser=www-data \ -# --with-webgroup=www-data \ -# --enable-mmap=yes diff --git a/distros/ubuntu1604/source/format b/distros/ubuntu1604/source/format deleted file mode 100644 index 163aaf8d8..000000000 --- a/distros/ubuntu1604/source/format +++ /dev/null @@ -1 +0,0 @@ -3.0 (quilt) diff --git a/distros/ubuntu1604/source/lintian-overrides b/distros/ubuntu1604/source/lintian-overrides deleted file mode 100644 index f905a5a2f..000000000 --- a/distros/ubuntu1604/source/lintian-overrides +++ /dev/null @@ -1,5 +0,0 @@ -## We're using "libjs-jquery" instead. -source-is-missing web/skins/*/js/jquery-3.5.1.min.js - -## Acknowledged, will repack eventually. -source-contains-prebuilt-javascript-object web/skins/*/js/jquery-3.5.1.min.js diff --git a/distros/ubuntu1604/zoneminder-doc.doc-base b/distros/ubuntu1604/zoneminder-doc.doc-base deleted file mode 100644 index c43dc4336..000000000 --- a/distros/ubuntu1604/zoneminder-doc.doc-base +++ /dev/null @@ -1,8 +0,0 @@ -Document: zoneminder-doc -Title: Zoneminder documentation -Abstract: This document describes how to use Zoneminder. -Section: System/Administration - -Format: HTML -Index: /usr/share/doc/zoneminder-doc/html/index.html -Files: /usr/share/doc/zoneminder-doc/html/* diff --git a/distros/ubuntu1604/zoneminder-doc.install b/distros/ubuntu1604/zoneminder-doc.install deleted file mode 100644 index c19bc6f3a..000000000 --- a/distros/ubuntu1604/zoneminder-doc.install +++ /dev/null @@ -1 +0,0 @@ -docs/_build/html usr/share/doc/zoneminder-doc/ diff --git a/distros/ubuntu1604/zoneminder-doc.links b/distros/ubuntu1604/zoneminder-doc.links deleted file mode 100644 index cc09f6462..000000000 --- a/distros/ubuntu1604/zoneminder-doc.links +++ /dev/null @@ -1,2 +0,0 @@ -## Convenience symlink: -/usr/share/doc/zoneminder-doc/html /usr/share/doc/zoneminder/html diff --git a/distros/ubuntu1604/zoneminder.apache2 b/distros/ubuntu1604/zoneminder.apache2 deleted file mode 100644 index 466144fa7..000000000 --- a/distros/ubuntu1604/zoneminder.apache2 +++ /dev/null @@ -1 +0,0 @@ -conf debian/conf/apache2/zoneminder.conf nginx diff --git a/distros/ubuntu1604/zoneminder.bug-presubj b/distros/ubuntu1604/zoneminder.bug-presubj deleted file mode 100644 index 990fc1d94..000000000 --- a/distros/ubuntu1604/zoneminder.bug-presubj +++ /dev/null @@ -1,5 +0,0 @@ -Unless bug is specific to Debian please consider reporting it directly to -upstream developer(s): - - https://github.com/ZoneMinder/ZoneMinder/issues - diff --git a/distros/ubuntu1604/zoneminder.dirs b/distros/ubuntu1604/zoneminder.dirs deleted file mode 100644 index 3c7237bf3..000000000 --- a/distros/ubuntu1604/zoneminder.dirs +++ /dev/null @@ -1,10 +0,0 @@ -var/log/zm -var/lib/zm -var/cache/zoneminder/events -var/cache/zoneminder/images -var/cache/zoneminder/temp -var/cache/zoneminder/cache -usr/share/zoneminder/db -usr/share/zoneminder/fonts -etc/zm/ -etc/zm/conf.d diff --git a/distros/ubuntu1604/zoneminder.docs b/distros/ubuntu1604/zoneminder.docs deleted file mode 100644 index b43bf86b5..000000000 --- a/distros/ubuntu1604/zoneminder.docs +++ /dev/null @@ -1 +0,0 @@ -README.md diff --git a/distros/ubuntu1604/zoneminder.examples b/distros/ubuntu1604/zoneminder.examples deleted file mode 100644 index 3b8befe7b..000000000 --- a/distros/ubuntu1604/zoneminder.examples +++ /dev/null @@ -1,2 +0,0 @@ -debian/examples/* -dbuild/misc/apache.conf diff --git a/distros/ubuntu1604/zoneminder.install b/distros/ubuntu1604/zoneminder.install deleted file mode 100644 index 17364c744..000000000 --- a/distros/ubuntu1604/zoneminder.install +++ /dev/null @@ -1,12 +0,0 @@ -etc/zm/zm.conf -etc/zm/conf.d/* -usr/bin -usr/lib/zoneminder -usr/share/polkit-1 -usr/share/zoneminder/db -usr/share/zoneminder/www -usr/share/zoneminder/fonts - -# libzoneminder-perl files: -usr/share/man/man3 -usr/share/perl5 diff --git a/distros/ubuntu1604/zoneminder.links b/distros/ubuntu1604/zoneminder.links deleted file mode 100644 index b7258c3c4..000000000 --- a/distros/ubuntu1604/zoneminder.links +++ /dev/null @@ -1 +0,0 @@ -/var/tmp /usr/share/zoneminder/www/api/app/tmp diff --git a/distros/ubuntu1604/zoneminder.linktrees b/distros/ubuntu1604/zoneminder.linktrees deleted file mode 100644 index 61edb4173..000000000 --- a/distros/ubuntu1604/zoneminder.linktrees +++ /dev/null @@ -1,6 +0,0 @@ -## cakephp -#replace /usr/share/php/Cake /usr/share/zoneminder/www/api/lib/Cake - -## libjs-jquery -#replace /usr/share/javascript/jquery/jquery.min.js /usr/share/zoneminder/www/skins/classic/js/jquery-3.5.1.min.js -#replace /usr/share/javascript/jquery/jquery.min.js /usr/share/zoneminder/www/skins/flat/js/jquery-3.5.1.min.js diff --git a/distros/ubuntu1604/zoneminder.lintian-overrides b/distros/ubuntu1604/zoneminder.lintian-overrides deleted file mode 100644 index 90be05a9f..000000000 --- a/distros/ubuntu1604/zoneminder.lintian-overrides +++ /dev/null @@ -1,14 +0,0 @@ -# Depends: policykit-1 -unusual-interpreter usr/bin/zmsystemctl.pl #!/usr/bin/pkexec - -# Intentionally not others-readable, #637685. -non-standard-file-perm etc/zm/zm.conf 0640 != 0644 - -# Bundled Cake PHP framework, not intended for direct execution: -script-not-executable usr/share/zoneminder/www/api/* - -# Annoying but seems to be too much troubles to fix; should be fixed upstream: -script-with-language-extension usr/bin/*.pl - -# dh-linktree: -package-contains-broken-symlink usr/share/zoneminder/www/api/lib/Cake/* diff --git a/distros/ubuntu1604/zoneminder.logrotate b/distros/ubuntu1604/zoneminder.logrotate deleted file mode 100644 index 6162e9c4d..000000000 --- a/distros/ubuntu1604/zoneminder.logrotate +++ /dev/null @@ -1,13 +0,0 @@ -/var/log/zm/*.log { - missingok - notifempty - sharedscripts - delaycompress - compress - postrotate - /usr/bin/zmpkg.pl logrot >>/dev/null 2>&1 || : - endscript - daily - rotate 7 - maxage 7 -} diff --git a/distros/ubuntu1604/zoneminder.maintscript b/distros/ubuntu1604/zoneminder.maintscript deleted file mode 100644 index 3aa20b3a0..000000000 --- a/distros/ubuntu1604/zoneminder.maintscript +++ /dev/null @@ -1 +0,0 @@ -rm_conffile /etc/zm/apache.conf 1.28.1-5~ diff --git a/distros/ubuntu1604/zoneminder.manpages b/distros/ubuntu1604/zoneminder.manpages deleted file mode 100644 index d2053d688..000000000 --- a/distros/ubuntu1604/zoneminder.manpages +++ /dev/null @@ -1 +0,0 @@ -docs/_build/man/*.1 diff --git a/distros/ubuntu1604/zoneminder.postinst b/distros/ubuntu1604/zoneminder.postinst deleted file mode 100644 index ae362a495..000000000 --- a/distros/ubuntu1604/zoneminder.postinst +++ /dev/null @@ -1,101 +0,0 @@ -#! /bin/sh - -set +e - -if [ "$1" = "configure" ]; then - - . /etc/zm/zm.conf - for CONFFILE in /etc/zm/conf.d/*.conf; do - . "$CONFFILE" - done - - # The logs can contain passwords, etc... so by setting group root, only www-data can read them, not people in the www-data group - chown www-data:root /var/log/zm - chown www-data:www-data /var/lib/zm - chown www-data:www-data /var/cache/zoneminder /var/cache/zoneminder/* - if [ ! -e "/etc/apache2/mods-enabled/cgi.load" ] && [ "$(command -v a2enmod)" != "" ]; then - echo "The cgi module is not enabled in apache2. I am enabling it using a2enmod cgi." - a2enmod cgi - fi - if [ ! -e "/etc/apache2/mods-enabled/rewrite.load" ] && [ "$(command -v a2enmod)" != "" ]; then - echo "The rewrite module is not enabled in apache2. I am enabling it using a2enmod rewrite." - a2enmod rewrite - fi - - if [ "$ZM_DB_HOST" = "localhost" ]; then - - if [ -e "/lib/systemd/system/mysql.service" ] || [ -e "/lib/systemd/system/mariadb.service" ] || [ -e "/etc/init.d/mysql" ]; then - # Ensure zoneminder is stopped - deb-systemd-invoke stop zoneminder.service || exit $? - - # - # Get mysql started if it isn't running - # - - if [ -e "/lib/systemd/system/mariadb.service" ]; then - DBSERVICE="mariadb.service" - else - DBSERVICE="mysql.service" - fi - echo "Detected db service is $DBSERVICE" - if systemctl is-failed --quiet $DBSERVICE; then - echo "$DBSERVICE is in a failed state; it will not be started." - echo "If you have already resolved the problem preventing $DBSERVICE from running," - echo "run sudo systemctl restart $DBSERVICE then run sudo dpkg-reconfigure zoneminder." - exit 1 - fi - - if ! systemctl is-active --quiet mysql.service mariadb.service; then - # Due to /etc/init.d service autogeneration, mysql.service always returns the status of mariadb.service - # However, mariadb.service will not return the status of mysql.service. - deb-systemd-invoke start $DBSERVICE - fi - - # Make sure systemctl status exit code is 0; i.e. the DB is running - if systemctl is-active --quiet "$DBSERVICE"; then - mysqladmin --defaults-file=/etc/mysql/debian.cnf -f reload - # test if database if already present... - if ! $(echo quit | mysql --defaults-file=/etc/mysql/debian.cnf zm > /dev/null 2> /dev/null) ; then - echo "Creating zm db" - cat /usr/share/zoneminder/db/zm_create.sql | mysql --defaults-file=/etc/mysql/debian.cnf - if [ $? -ne 0 ]; then - echo "Error creating db." - exit 1; - fi - # This creates the user. - echo "CREATE USER '${ZM_DB_USER}'@localhost IDENTIFIED BY '${ZM_DB_PASS}';" | mysql --defaults-file=/etc/mysql/debian.cnf mysql - fi - echo "Updating permissions for user ${ZM_DB_USER}@localhost" - echo "GRANT LOCK TABLES,ALTER,DROP,SELECT,INSERT,UPDATE,DELETE,CREATE,INDEX,ALTER ROUTINE,CREATE ROUTINE, TRIGGER,EXECUTE,REFERENCES ON ${ZM_DB_NAME}.* TO '${ZM_DB_USER}'@localhost;" | mysql --defaults-file=/etc/mysql/debian.cnf mysql - - zmupdate.pl -s --nointeractive - if [ $? -ne 0 ]; then - echo "Error updating db." - exit 1; - fi - zmupdate.pl --nointeractive -f - if [ $? -ne 0 ]; then - echo "Error updating config." - exit 1; - fi - - # Add any new PTZ control configurations to the database (will not overwrite) - zmcamtool.pl --import >/dev/null 2>&1 - echo "Done Updating; starting ZoneMinder." - else - echo 'NOTE: MySQL/MariaDB not running; please start mysql and run dpkg-reconfigure zoneminder when it is running.' - fi - else - echo 'MySQL/MariaDB not found; assuming remote server.' - fi - - else - echo "Not doing database upgrade due to remote db server ($ZM_DB_HOST)." - fi - - echo "Done Updating; starting ZoneMinder." - deb-systemd-invoke restart zoneminder.service - -fi - -#DEBHELPER# diff --git a/distros/ubuntu1604/zoneminder.postrm b/distros/ubuntu1604/zoneminder.postrm deleted file mode 100644 index ba2066c8d..000000000 --- a/distros/ubuntu1604/zoneminder.postrm +++ /dev/null @@ -1,14 +0,0 @@ -#! /bin/sh - -set -e - -if [ "$1" = "purge" ]; then - echo " -Reminder: to completely remove \"zoneminder\" it may be necessary - * to delete database using the following sample command: - sudo mysqladmin --defaults-file=/etc/mysql/debian.cnf -f drop zm - * to delete remaining data files in "/var/cache/zoneminder". -" -fi - -#DEBHELPER# diff --git a/distros/ubuntu1604/zoneminder.preinst b/distros/ubuntu1604/zoneminder.preinst deleted file mode 100644 index 6088c3ea9..000000000 --- a/distros/ubuntu1604/zoneminder.preinst +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -set -e - -## Remove obsolete symlink which is in the way of dh_apache2: -ol="/etc/apache2/conf-available/zoneminder.conf" -if [ -h "${ol}" ]; then - [ "$(readlink ${ol})" = "/etc/zm/apache.conf" ] && rm -f "${ol}" -fi - -#DEBHELPER# diff --git a/distros/ubuntu1604/zoneminder.service b/distros/ubuntu1604/zoneminder.service deleted file mode 100644 index cb2d6791e..000000000 --- a/distros/ubuntu1604/zoneminder.service +++ /dev/null @@ -1,23 +0,0 @@ -# ZoneMinder systemd unit file -# This file is intended to work with Debian distributions - -[Unit] -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 - -[Service] -#User=www-data -Type=forking -ExecStart=/usr/bin/zmpkg.pl start -ExecReload=/usr/bin/zmpkg.pl restart -ExecStop=/usr/bin/zmpkg.pl stop -PIDFile=/run/zm/zm.pid -Restart=always -RestartSec=10 -Environment=TZ=:/etc/localtime -TimeoutSec=600 - -[Install] -WantedBy=multi-user.target diff --git a/distros/ubuntu2004/control b/distros/ubuntu2004/control index 01c3a8472..a4683bfde 100644 --- a/distros/ubuntu2004/control +++ b/distros/ubuntu2004/control @@ -2,9 +2,8 @@ Source: zoneminder Section: net Priority: optional Maintainer: Isaac Connor -Build-Depends: debhelper (>= 12), sphinx-doc, python3-sphinx, dh-linktree, dh-apache2 +Build-Depends: debhelper (>= 11), sphinx-doc, python3-sphinx, dh-linktree, dh-apache2 ,cmake - ,libavdevice-dev ,libavcodec-dev ,libavformat-dev ,libavutil-dev @@ -13,7 +12,6 @@ Build-Depends: debhelper (>= 12), sphinx-doc, python3-sphinx, dh-linktree, dh-ap ,ffmpeg ,net-tools ,libbz2-dev - ,libgcrypt20-dev ,libcurl4-gnutls-dev ,libjpeg-turbo8-dev | libjpeg62-turbo-dev | libjpeg8-dev | libjpeg9-dev ,libturbojpeg0-dev @@ -32,16 +30,17 @@ Build-Depends: debhelper (>= 12), sphinx-doc, python3-sphinx, dh-linktree, dh-ap ,libcrypt-eksblowfish-perl ,libdata-entropy-perl ,libvncserver-dev - ,libjwt-gnutls-dev + ,libjwt-gnutls-dev|libjwt-dev Standards-Version: 4.5.0 Homepage: https://www.zoneminder.com/ Package: zoneminder Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} + ,sudo ,javascript-common - ,libswscale5 - ,libswresample3 + ,libswscale5|libswscale4 + ,libswresample3|libswresample2 ,ffmpeg ,libdate-manip-perl, libmime-lite-perl, libmime-tools-perl ,libdbd-mysql-perl @@ -75,7 +74,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,libcrypt-eksblowfish-perl ,libdata-entropy-perl ,libvncclient1|libvncclient0 - ,libjwt-gnutls0 + ,libjwt-gnutls0|libjwt0 Recommends: ${misc:Recommends} ,libapache2-mod-php | php-fpm ,default-mysql-server | mariadb-server | virtual-mysql-server diff --git a/distros/ubuntu2004/copyright b/distros/ubuntu2004/copyright index 64189b0f4..eb5274cbf 100644 --- a/distros/ubuntu2004/copyright +++ b/distros/ubuntu2004/copyright @@ -41,7 +41,6 @@ Copyright: 2005-2013 Cake Software Foundation, Inc. (http://cakefoundation.org) License: Expat Files: - cmake/Modules/CheckPrototypeDefinition*.cmake cmake/Modules/FindGLIB2.cmake cmake/Modules/FindPolkit.cmake cmake/Modules/GNUInstallDirs.cmake diff --git a/distros/ubuntu1604/zoneminder.tmpfile b/distros/ubuntu2004/zoneminder.tmpfile similarity index 73% rename from distros/ubuntu1604/zoneminder.tmpfile rename to distros/ubuntu2004/zoneminder.tmpfile index cbfdec1de..a92ad2627 100644 --- a/distros/ubuntu1604/zoneminder.tmpfile +++ b/distros/ubuntu2004/zoneminder.tmpfile @@ -1,4 +1,4 @@ d /run/zm 0755 www-data www-data d /tmp/zm 0755 www-data www-data -d /var/tmp/zm 0755 www-data www-data +d /var/tmp/zm 0755 www-data www-data 7d d /var/cache/zoneminder/cache 0755 www-data www-data diff --git a/distros/ubuntu2004/zoneminder.tmpfiles b/distros/ubuntu2004/zoneminder.tmpfiles deleted file mode 100644 index cbfdec1de..000000000 --- a/distros/ubuntu2004/zoneminder.tmpfiles +++ /dev/null @@ -1,4 +0,0 @@ -d /run/zm 0755 www-data www-data -d /tmp/zm 0755 www-data www-data -d /var/tmp/zm 0755 www-data www-data -d /var/cache/zoneminder/cache 0755 www-data www-data diff --git a/docs/faq.rst b/docs/faq.rst index 1db30e05f..dd0273667 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -11,18 +11,21 @@ How can I stop ZoneMinder filling up my disk? --------------------------------------------- Recent versions of ZoneMinder come with a filter you can use for this purpose already included. -The filter is called **PurgeWhenFull** and to find it, choose one of the event counts from the console page, for instance events in the last hour, for one of your monitors. **Note** that this filter is automatically enabled if you do a fresh install of ZoneMinder including creating a new database. If you already have an existing database and are upgrading ZoneMinder, it will retain the settings of the filter (which in earlier releases was disabled by default). So you may want to check if PurgeWhenFull is enabled and if not, enable it. +The filter is called **PurgeWhenFull** and to find it, click on the word **Filters** in the header. +**Note** that this filter is automatically enabled if you do a fresh install of ZoneMinder including creating a new database. If you already have an existing database and are upgrading ZoneMinder, it will retain the settings of the filter (which in earlier releases was disabled by default). So you may want to check if PurgeWhenFull is enabled and if not, enable it. -To enable it, go to Web Console, click on any of your Events of any of your monitors. -This will bring up an event listing and a filter window. +To enable it, go to Web Console, click on the word **Filters** in the UI header. -In the filter window there is a drop down select box labeled 'Use Filter', that lets your select a saved filter. Select 'PurgeWhenFull' and it will load that filter. +In the filter window there is a drop down select box labeled 'Use Filter', that lets you select a saved filter. Select 'PurgeWhenFull' and it will load that filter. Make any modifications you might want, such as the percentage full you want it to kick in, or how many events to delete at a time (it will repeat the filter as many times as needed to clear the space, but will only delete this many events each time to get there). -Then click on 'Save' which will bring up a new window. Make sure the 'Automatically delete' box is checked and press save to save your filter. This will then run in the background to keep your disk within those limits. +Ensure that the Run filter in background checkbox is checked. +Ensure that the Delete all matches checkbox is checked. -After you've done that, you changes will automatically be loaded into zmfilter within a few minutes. +Then click on 'Save'. The filter will immediately begin executing in the background to keep your disk within those limits. + +Please note that that this filter will only affect the default storage location. If you have added other storage areas, you must create a PurgeWhenFull filter for each one, and specify the Storage Area as one of the parameters in the filter. You can duplicate the existing PurgeWhenFull filter by using Save As instead of Save. Check the ``zmfilter.log`` file to make sure it is running as sometimes missing perl modules mean that it never runs but people don't always realize. @@ -46,7 +49,7 @@ Normally an event created as the result of an alarm consists of entries in one o ZM_RUN_AUDIT: -The zmaudit daemon exists to check that the saved information in the database and on the file system match and are consistent with each other. If an error occurs or if you are using 'fast deletes' it may be that database records are deleted but files remain. In this case, and similar, zmaudit will remove redundant information to synchronize the two data stores. This option controls whether zmaudit is run in the background and performs these checks and fixes continuously. This is recommended for most systems however if you have a very large number of events the process of scanning the database and file system may take a long time and impact performance. In this case you may prefer to not have zmaudit running unconditionally and schedule occasional checks at other, more convenient, times. +The zmaudit daemon exists to check that the saved information in the database and on the file system match and are consistent with each other. If an error occurs or if you are using 'fast deletes' it may be that database records are deleted but files remain. In this case, and similar, zmaudit will remove redundant information to synchronize the two data stores. This option controls whether zmaudit is run in the background and performs these checks and fixes continuously. This is not recommended for most systems, as zmaudit.pl is very resource intensive. ZM_AUDIT_CHECK_INTERVAL: @@ -184,14 +187,27 @@ Once I did this, images started to stream for me. Lastly, please look for errors created by the zmc processes. If zmc isn't running, then zms will not be able to get an image from it and will exit. -I have several monitors configured but when I load the Montage view in FireFox why can I only see two? or, Why don't all my cameras display when I use the Montage view in FireFox? +I have several monitors configured but when I load the Montage view why can I only see two? or, Why don't all my cameras display when I use the Montage view? -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -By default FireFox only supports a small number of simultaneous connections. Using the montage view usually requires one persistent connection for each camera plus intermittent connections for other information such as statuses. +By default most browsers only support a small number of simultaneous connections to any given server. Using the montage view usually requires one persistent connection for each camera plus intermittent connections for other information such as statuses. -You will need to increase the number of allowed connections to use the montage view with more than a small number of cameras. Certain FireFox extensions such as FasterFox may also help to achieve the same result. +In firefox you can increase the limit, but other browsers are not configurable in this way. -To resolve this situation, follow the instructions below: +A solution for all browsers is something we call multi-port. We reconfigure apache to operate on ports other than the default of 80(http) or 443(https). You need to pick a range, let's say 30000 to 30010 in order to support 10 cameras. We add lines to your zoneminder apache config file as follows: + +Listen 30000 +Listen 30001 +Listen 30002 +Listen 30003 +etc +Listen 30010 + +If you are using virtualhosts, you will have to add these to the VirtualHost directive as well. + +Then in ZoneMinder config, Go Options -> Network and set MIN_STREAMING_PORT to 30000. Now when generating urls to stream images from ZoneMinder a port will be appended that is 30000 + MonitorId, so Monitor 1 will stream from 30001 and so on. This will allow Montage to stream from all monitors. + +Alternatively if you are in fact using only Firefox, you can increase the limit as follows: Enter ``about:config`` in the address bar diff --git a/docs/installationguide/debian.rst b/docs/installationguide/debian.rst index 2244177e6..c6e0cebcf 100644 --- a/docs/installationguide/debian.rst +++ b/docs/installationguide/debian.rst @@ -56,13 +56,13 @@ Add the following to the /etc/apt/sources.list.d/zoneminder.list file :: # ZoneMinder repository - deb https://zmrepo.zoneminder.com/debian/release-1.34 buster/ + deb https://zmrepo.zoneminder.com/debian/release-1.36 buster/ You can do this using: .. code-block:: - echo "deb https://zmrepo.zoneminder.com/debian/release-1.34 buster/" | sudo tee /etc/apt/sources.list.d/zoneminder.list + echo "deb https://zmrepo.zoneminder.com/debian/release-1.36 buster/" | sudo tee /etc/apt/sources.list.d/zoneminder.list Because ZoneMinder's package repository provides a secure connection through HTTPS, apt must be enabled for HTTPS. :: @@ -158,7 +158,7 @@ You are now ready to go with ZoneMinder. Open a browser and type either ``localh Easy Way: Debian Stretch ------------------------ -This procedure will guide you through the installation of ZoneMinder on Debian 9 (Stretch). This section has been tested with ZoneMinder 1.34 on Debian 9.8. +This procedure will guide you through the installation of ZoneMinder on Debian 9 (Stretch). This section has been tested with ZoneMinder 1.36 on Debian 9.8. **Step 1:** Make sure your system is up to date @@ -204,7 +204,7 @@ Add the following to the bottom of the file :: # ZoneMinder repository - deb https://zmrepo.zoneminder.com/debian/release-1.34 stretch/ + deb https://zmrepo.zoneminder.com/debian/release-1.36 stretch/ CTRL+o and to save CTRL+x to exit @@ -293,175 +293,3 @@ 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. - - -Easy Way: Debian Jessie ------------------------ - -**Step 1:** Setup Sudo - -By default Debian does not come with sudo. Log in as root or use su command. -N.B. The instructions below are for setting up sudo for your current account, you can -do this as root if you prefer. - -:: - - apt-get update - apt-get install sudo - usermod -a -G sudo - exit - -Logout or try ``newgrp`` to reload user groups - -**Step 2:** Run sudo and update - -Now run session using sudo and ensure system is updated. -:: - - sudo -i - apt-get upgrade - -**Step 3:** Install Apache and MySQL - -These are not dependencies for the package as they could -be installed elsewhere. - -:: - - apt-get install apache2 mysql-server - -**Step 4:** Edit sources.list to add jessie-backports - -:: - - nano /etc/apt/sources.list - -Add the following to the bottom of the file - -:: - - # Backports repository - deb http://archive.debian.org/debian/ jessie-backports main contrib non-free - -CTRL+o and to save -CTRL+x to exit - -Run the following - -:: - - echo 'Acquire::Check-Valid-Until no;' > /etc/apt/apt.conf.d/99no-check-valid-until - -**Step 5:** Install ZoneMinder - -:: - - apt-get update - apt-get install zoneminder - -**Step 6:** Read the Readme - -The rest of the install process is covered in the README.Debian, so feel free to have -a read. - -:: - - zcat /usr/share/doc/zoneminder/README.Debian.gz - -**Step 7:** Setup Database - -Install the zm database and setup the user account. Refer to Hints in Ubuntu install -should you choose to change default database user and password. - -:: - - cat /usr/share/zoneminder/db/zm_create.sql | sudo mysql --defaults-file=/etc/mysql/debian.cnf - echo 'grant lock tables,alter,create,select,insert,update,delete,index on zm.* to 'zmuser'@localhost identified by "zmpass";' | sudo mysql --defaults-file=/etc/mysql/debian.cnf mysql - -**Step 8:** zm.conf Permissions - -Adjust permissions to the zm.conf file to allow web account to access it. - -:: - - chgrp -c www-data /etc/zm/zm.conf - -**Step 9:** Setup ZoneMinder service - - :: - - systemctl enable zoneminder.service - -**Step 10:** Configure Apache - -The following commands will setup the default /zm virtual directory and configure -required apache modules. - -:: - - a2enconf zoneminder - a2enmod cgi - a2enmod rewrite - -**Step 11:** Edit Timezone in PHP - -:: - - nano /etc/php5/apache2/php.ini - -Search for [Date] (Ctrl + w then type Date and press Enter) and change -date.timezone for your time zone. **Don't forget to remove the ; from in front -of date.timezone** - -:: - - [Date] - ; Defines the default timezone used by the date functions - ; http://php.net/date.timezone - date.timezone = America/New_York - -CTRL+o then [Enter] to save - -CTRL+x to exit - - -**Step 12:** Please check the configuration - - 1. Check path of ZM_PATH in '/etc/zm/conf.d/zmcustom.conf' is ZM_PATH_ZMS=/zm/cgi-bin/nph-zms - :: - cat /etc/zm/conf.d/zmcustom.conf - - 2. Check config of /etc/apache2/conf-enabled/zoneminder.conf has the same ScriptAlias /zm/cgi-bin that is configured - in ZM_PATH. The part /nph-zms has to be left out of the ScriptAlias - - ScriptAlias /zm/cgi-bin "/usr/lib/zoneminder/cgi-bin" - - - :: - cat /etc/apache2/conf-enabled/zoneminder.conf - -**Step 13:** Start ZoneMinder - -Reload Apache to enable your changes and then start ZoneMinder. - -:: - - systemctl reload apache2 - systemctl start zoneminder - -**Step 14:** Making sure ZoneMinder works - -1. Open up a browser and go to ``http://hostname_or_ip/zm`` - should bring up ZoneMinder Console - -2. (Optional API Check)Open up a tab in the same browser and go to ``http://hostname_or_ip/zm/api/host/getVersion.json`` - - If it is working correctly you should get version information similar to the example below: - - :: - - { - "version": "1.34.0", - "apiversion": "1.34.0.1" - } - -**Congratulations** Your installation is complete diff --git a/docs/installationguide/packpack.rst b/docs/installationguide/packpack.rst index 72fdf8661..fc1d6c077 100644 --- a/docs/installationguide/packpack.rst +++ b/docs/installationguide/packpack.rst @@ -85,7 +85,7 @@ Where is the name of the distro you wish to build on, such as fedor :: - OS=ubuntu DIST=xenial utils/packpack/startpackpack.sh + OS=ubuntu DIST=hirsute utils/packpack/startpackpack.sh Once you enter the appropriate command, go get a coffee while a ZoneMinder package is built. When the build finished, you can find the resulting packages under a subfolder called "build". @@ -93,13 +93,13 @@ Note that this will build packages with x86_64 architecture. This build method c :: - OS=ubuntu DIST=xenial ARCH=i386 utils/packpack/startpackpack.sh + OS=ubuntu DIST=hirsute ARCH=i386 utils/packpack/startpackpack.sh For advanced users who really want to go out into uncharted waters, it is theoretically possible to build arm packages as well, as long as the host architecture is compatible. :: - OS=ubuntu DIST=xenial ARCH=armhfp utils/packpack/startpackpack.sh + OS=ubuntu DIST=hirsute ARCH=armhfp utils/packpack/startpackpack.sh Building arm packages in this manner has not been tested by us, however. diff --git a/docs/installationguide/ubuntu.rst b/docs/installationguide/ubuntu.rst index 7f2a01eb2..d59d58f38 100644 --- a/docs/installationguide/ubuntu.rst +++ b/docs/installationguide/ubuntu.rst @@ -41,7 +41,7 @@ guide you with a quick search. :: - add-apt-repository ppa:iconnor/zoneminder-1.34 + add-apt-repository ppa:iconnor/zoneminder-1.36 Update repo and upgrade. @@ -176,175 +176,6 @@ CTRL+x to exit PPA install may need some tweaking of ZMS_PATH in ZoneMinder options. `Socket_sendto or no live streaming`_ -Easy Way: Ubuntu 16.04 (Xenial) -------------------------------- -These instructions are for a brand new ubuntu 16.04 system which does not have ZM -installed. - - -It is recommended that you use an Ubuntu Server install and select the LAMP option -during install to install Apache, MySQL and PHP. If you failed to do this you can -achieve the same result by running: - -:: - - sudo tasksel install lamp-server - -During installation it will ask you to set up a master/root password for the MySQL. -Installing LAMP is not ZoneMinder specific so you will find plenty of resources to -guide you with a quick search. - -**Step 1:** Either run commands in this install using sudo or use the below to become root -:: - - sudo -i - -**Step 2:** Update Repos - -.. topic :: Latest Release - - ZoneMinder is now part of the current standard Ubuntu repository, but - sometimes the official repository can lag behind. To find out check our - `releases page `_ for - the latest release. - - Alternatively, the ZoneMinder project team maintains a `PPA `_, which is updated immediately - following a new release of ZoneMinder. To use this repository instead of the - official Ubuntu repository, enter the following from the command line: - - :: - - add-apt-repository ppa:iconnor/zoneminder - add-apt-repository ppa:iconnor/zoneminder-1.32 - -Update repo and upgrade. - -:: - - apt-get update - apt-get upgrade - apt-get dist-upgrade - - -**Step 3:** Configure MySQL - -.. sidebar :: Note - - The MySQL default configuration file (/etc/mysql/mysql.cnf)is read through - several symbolic links beginning with /etc/mysql/my.cnf as follows: - - | /etc/mysql/my.cnf -> /etc/alternatives/my.cnf - | /etc/alternatives/my.cnf -> /etc/mysql/mysql.cnf - | /etc/mysql/mysql.cnf is a basic file - -Certain new defaults in MySQL 5.7 cause some issues with ZoneMinder < 1.32.0, -the workaround is to modify the sql_mode setting of MySQL. Please note that these -changes are NOT required for ZoneMinder 1.32.0 and some people have reported them -causing problems in 1.32.0. - -To better manage the MySQL server it is recommended to copy the sample config file and -replace the default my.cnf symbolic link. - -:: - - rm /etc/mysql/my.cnf (this removes the current symbolic link) - cp /etc/mysql/mysql.conf.d/mysqld.cnf /etc/mysql/my.cnf - -To change MySQL settings: - -:: - - nano /etc/mysql/my.cnf - -In the [mysqld] section add the following - -:: - - sql_mode = NO_ENGINE_SUBSTITUTION - -CTRL+o then [Enter] to save - -CTRL+x to exit - -Restart MySQL - -:: - - systemctl restart mysql - - -**Step 4:** Install ZoneMinder - -:: - - apt-get install zoneminder - -**Step 5:** Configure the ZoneMinder Database - -This step should not be required on ZoneMinder 1.32.0. - -:: - - mysql -uroot -p < /usr/share/zoneminder/db/zm_create.sql - mysql -uroot -p -e "grant lock tables,alter,drop,select,insert,update,delete,create,index,alter routine,create routine, trigger,execute on zm.* to 'zmuser'@localhost identified by 'zmpass';" - - -**Step 6:** Set permissions - -Set /etc/zm/zm.conf to root:www-data 740 and www-data access to content - -:: - - chmod 740 /etc/zm/zm.conf - chown root:www-data /etc/zm/zm.conf - chown -R www-data:www-data /usr/share/zoneminder/ - -**Step 7:** Configure Apache correctly: - -:: - - a2enmod cgi - a2enmod rewrite - a2enconf zoneminder - -You may also want to enable to following modules to improve caching performance - -:: - - a2enmod expires - a2enmod headers - -**Step 8:** Enable and start Zoneminder - -:: - - systemctl enable zoneminder - systemctl start zoneminder - -**Step 10:** Reload Apache service - -:: - - systemctl reload apache2 - -**Step 11:** Making sure ZoneMinder works - -1. Open up a browser and go to ``http://hostname_or_ip/zm`` - should bring up ZoneMinder Console - -2. (Optional API Check)Open up a tab in the same browser and go to ``http://hostname_or_ip/zm/api/host/getVersion.json`` - - If it is working correctly you should get version information similar to the example below: - - :: - - { - "version": "1.34.0", - "apiversion": "1.34.0.1" - } - -**Congratulations** Your installation is complete - -PPA install may need some tweaking of ZMS_PATH in ZoneMinder options. `Socket_sendto or no live streaming`_ Harder Way: Build Package From Source ------------------------------------- @@ -382,7 +213,7 @@ To build the latest stable release: Note that the distribution will be guessed using ``lsb_release -a 2>/dev/null | grep Codename | awk '{print $2}'`` -which simply extracts your distribution name - like "xenial", "bionic" etc. You +which simply extracts your distribution name - like "bionic", "hirsute" etc. You can always specify it using --distro=your distro name if you know it. As far as the script goes, it checks if your distro is "trusty" in which case it pulls in pre-systemd release configurations and if its not "trusty" it assumes its based on systemd diff --git a/docs/userguide/filterevents.rst b/docs/userguide/filterevents.rst index ae1b4b132..b30a3185d 100644 --- a/docs/userguide/filterevents.rst +++ b/docs/userguide/filterevents.rst @@ -4,7 +4,7 @@ Filtering Events Filters allow you to define complex conditions with associated actions in ZoneMinder. Examples could include: * Send an email each time a new event occurs for a specific monitor -* Delete events that are more than 10 days old +* Delete events that are more than 10 days old And many more. @@ -29,14 +29,17 @@ Here is what the filter window looks like events later and also make sure archived events don't get deleted, for example .. todo :: - fill in what update used disk space, copy all matches, move all matches do. For the "create video" filter, put in more details on how it works, any dependencies etc. + For the "create video" filter, put in more details on how it works, any dependencies etc. - * Update used disk space: + * Update used disk space: calculates how much disk space is currently taken by the event and updates the db record. * Create video for all matches: creates a video file of all the events that match - * Execute command on all matches: Allows you to execute any arbitrary command on the matched events. You can use replacement tokens as subsequent arguents to the command, the last argument will be the absolute path to the event, preceeded by replacement arguents. eg: /usr/bin/script.sh %MN% will excecute as /usr/bin/script.sh MonitorName /path/to/event. + * Create video for all matches: ffmpeg will be used to create a video file (mp4) out of all the stored jpgs if using jpeg storage. + * Execute command on all matches: Allows you to execute any arbitrary command on the matched events. You can use replacement tokens as subsequent arguents to the command, the last argument will be the absolute path to the event, preceeded by replacement arguents. eg: /usr/bin/script.sh %MN% will excecute as /usr/bin/script.sh MonitorName /path/to/event. Please note that urls may contain characters like & that need quoting. So you may need to put quotes around them like /usr/bin/scrupt.sh "%MN%". * Delete all matches: Deletes all the matched events. - * Copy all matches: - * Move all matches: + * Email details of all matches: Sends an email to the configured address with details about the event. + * Copy all matches: copies the event files to another location, specified in the Copy To dropdown. The other location must be setup in the Storage Tab under options. + * Message details of all matches: Uses an email to SMS gateway to send an SMS message for each match. + * Move all matches: copies the event files to another location, specified in the Move To dropdown. The other location must be setup in the Storage Tab under options. The files will be delete from the original location. * Run filter in background: When checked, ZoneMinder will make sure the filter is checked regularly. For example, if you want to be notified of new events by email, you should make sure this is checked. Filters that are configured to run in the background have a “*” next to it. * Run filter concurrently: Allows this filter to run in its own thread thereby letting other filters run in parallel. @@ -78,10 +81,13 @@ Here is what the filter window looks like * %EPIMOD% Path to image containing object detection, suitable for use in img tags * %EPIMODG% Path to image containing object detection animated gif version, suitable for use in img tags * %EI1% Attach first alarmed event image + * %EI1A% Attach first alarmed event analysis image * %EIM% Attach (first) event image with the highest score + * %EIMA% Attach (first) event analysis image with the highest score * %EIMOD% Attach image containing object detection * %EIMODG% Attach image containing object detection animated gif version * %EV% Attach event mpeg video + * %EVM% Attach event mpeg video in phone format * %MN% Name of the monitor * %MET% Total number of events for the monitor * %MEH% Number of events for the monitor in the last hour @@ -111,12 +117,11 @@ How filters actually work -------------------------- It is useful to know how filters actually work behind the scenes in ZoneMinder, in the event you find your filter not functioning as intended: -* the primary filter processing process in ZoneMinder is a perl file called ``zmfilter.pl`` which retrieves filters from the Filters database table +* Each filter set to run in the background will be run in it's own process called ``zmfilter.pl`` which retrieves filters from the Filters database table * zmfilter.pl runs every FILTER_EXECUTE_INTERVAL seconds (default is 20s, can be changed in Options->System) -* in each run, it goes through all the filters which are marked as "Run in Background" and if the conditions match performs the specified action -* zmfilter.pl also reloads all the filters every FILTER_RELOAD_DELAY seconds (default is 300s/5mins, can be changed in Options->System) - * So if you have just created a new filter, zmfilter will not see it till the next FILTER_RELOAD_DELAY cycle - * This is also important if you are using "relative times" like 'now' - see :ref:`relative_caveat` +* after each interval the filter will query the database and apply the action to each matching event. +* zmfilter.pl also reloads the filter every FILTER_RELOAD_DELAY seconds (default is 300s/5mins, can be changed in Options->System) +* In previous versions of ZoneMinder filter changes would not take immediate effect, but now the web ui will start/stop/restart filters as appropriate upon editing a filter. Relative items in date strings diff --git a/docs/userguide/options/options_email.rst b/docs/userguide/options/options_email.rst index eef35bfa2..84c1596f4 100644 --- a/docs/userguide/options/options_email.rst +++ b/docs/userguide/options/options_email.rst @@ -101,6 +101,6 @@ FROM_EMAIL - The emails or messages that will be sent to you informing you of ev URL - The emails or messages that will be sent to you informing you of events can include a link to the events themselves for easy viewing. If you intend to use this feature then set this option to the url of your installation as it would appear from where you read your email, e.g. ``http://host.your.domain/zm/index.php``. -SSMTP_MAIL - SSMTP is a lightweight and efficient method to send email. The SSMTP application is not installed by default. NEW_MAIL_MODULES must also be enabled. Please visit the ZoneMinder `SSMTP Wiki page `__ for setup and configuration help. +SSMTP_MAIL - SSMTP is a lightweight and efficient method to send email. The SSMTP application is not installed by default. NEW_MAIL_MODULES must also be enabled. Please visit the ZoneMinder `SSMTP Wiki page `__ for setup and configuration help. -SSMTP_PATH - The path to the SSMTP application. If path is not defined. Zoneminder will try to determine the path via shell command. Example path: /usr/sbin/ssmtp. \ No newline at end of file +SSMTP_PATH - The path to the SSMTP application. If path is not defined. Zoneminder will try to determine the path via shell command. Example path: /usr/sbin/ssmtp. diff --git a/docs/userguide/options/options_storage.rst b/docs/userguide/options/options_storage.rst index 623ad50d5..73dfb574a 100644 --- a/docs/userguide/options/options_storage.rst +++ b/docs/userguide/options/options_storage.rst @@ -27,6 +27,7 @@ S3 storage setup You must use s3fs to mount the S3 bucket in your fs tree. Telling ZoneMinder that the location is S3 will let it use more efficient code to send and delete the event data. +The Do Deletes option tells ZoneMinder whether to actually perform delete operations when deleting events. S3fs systems often do deletes in a cron job or other background task and doing the deletes can overload an S3 system. Refer to this guide for installation and configuration of s3fs - https://github.com/s3fs-fuse/s3fs-fuse diff --git a/docs/userguide/options/options_web.rst b/docs/userguide/options/options_web.rst index 6766a2248..7ae0ac628 100644 --- a/docs/userguide/options/options_web.rst +++ b/docs/userguide/options/options_web.rst @@ -5,10 +5,7 @@ This screen lets you customize several aspects of the web interface of ZoneMinde .. image:: images/Options_web.png -WEB_TITLE - - -.. todo :: - not quite sure what this does. Seems to change the "target" name - not sure what effect it is supposed to have. +WEB_TITLE - The actual text that is shown on the login screen. It is possible that it also appears in other areas. WEB_TITLE_PREFIX - If you have more than one installation of ZoneMinder it can be helpful to display different titles for each one. Changing this option allows you to customise the window titles to include further information to aid identification. @@ -48,4 +45,4 @@ WEB_USE_OBJECT_TAGS - There are two methods of including media content in web pa WEB_XFRAME_WARN - When creating a Web Site monitor, if the target web site has X-Frame-Options set to sameorigin in the header, the site will not display in ZoneMinder. This is a design feature in most modern browsers. When this condition occurs, ZoneMinder will write a warning to the log file. To get around this, one can install a browser plugin or extension to ignore X-Frame headers, and then the page will display properly. Once the plugin or extension has ben installed, the end user may choose to turn this warning off -WEB_FILTER_SOURCE - This option only affects monitors with a source type of Ffmpeg, Libvlc, or WebSite. This setting controls what information is displayed in the Source column on the console. Selecting 'None' will not filter anything. The entire source string will be displayed, which may contain sensitive information. Selecting 'NoCredentials' will strip out usernames and passwords from the string. If there are any port numbers in the string and they are common (80, 554, etc) then those will be removed as well. Selecting 'Hostname' will filter out all information except for the hostname or ip address. When in doubt, stay with the default 'Hostname'. This feature uses the php function 'url_parts' to identify the various pieces of the url. If the url in question is unusual or not standard in some way, then filtering may not produce the desired results. \ No newline at end of file +WEB_FILTER_SOURCE - This option only affects monitors with a source type of Ffmpeg, Libvlc, or WebSite. This setting controls what information is displayed in the Source column on the console. Selecting 'None' will not filter anything. The entire source string will be displayed, which may contain sensitive information. Selecting 'NoCredentials' will strip out usernames and passwords from the string. If there are any port numbers in the string and they are common (80, 554, etc) then those will be removed as well. Selecting 'Hostname' will filter out all information except for the hostname or ip address. When in doubt, stay with the default 'Hostname'. This feature uses the php function 'url_parts' to identify the various pieces of the url. If the url in question is unusual or not standard in some way, then filtering may not produce the desired results. diff --git a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in index a1d9521f6..0c8aec4a2 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in @@ -2669,7 +2669,7 @@ our @options = ( }, { name => 'ZM_WEB_EVENT_SORT_FIELD', - default => 'StartTime', + default => 'StartDateTime', description => 'Default field the event lists are sorted by', help => q` Events in lists can be initially ordered in any way you want. @@ -2681,7 +2681,7 @@ our @options = ( `, type => { db_type =>'string', - hint =>'Id|Name|Cause|DiskSpace|MonitorName|StartTime|Length|Frames|AlarmFrames|TotScore|AvgScore|MaxScore', + hint =>'Id|Name|Cause|DiskSpace|MonitorName|StartDateTime|Length|Frames|AlarmFrames|TotScore|AvgScore|MaxScore', pattern =>qr|.|, format =>q( $1 ) }, @@ -3766,7 +3766,7 @@ our @options = ( SSMTP is a lightweight and efficient method to send email. The SSMTP application is not installed by default. NEW_MAIL_MODULES must also be enabled. - Please visit the ZoneMinder [SSMTP Wiki page](http://www.zoneminder.com/wiki/index.php/How_to_get_ssmtp_working_with_Zoneminder) + Please visit the ZoneMinder [SSMTP Wiki page](https://wiki.zoneminder.com/How_to_get_ssmtp_working_with_Zoneminder) for setup and configuration help. `, type => $types{boolean}, diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm index df3a538ea..b95ac9e66 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm @@ -220,14 +220,14 @@ sub moveConUpRight { my $self = shift; Debug('Move Diagonally Up Right'); $$self{Monitor}->suspendMotionDetection() if !$self->{Monitor}->{ModectDuringPTZ}; - $$self{LastCmd} = 'code=RightUp&channel=0&arg1=0&arg2=1&arg3=0'; + $$self{LastCmd} = 'code=RightUp&channel=0&arg1=1&arg2=1&arg3=0'; $self->sendCmd('cgi-bin/ptz.cgi?action=start&'.$$self{LastCmd}); } sub moveConDownRight { my $self = shift; Debug('Move Diagonally Down Right'); - $$self{LastCmd} = 'code=RightDown&channel=0&arg1=0&arg2=1&arg3=0'; + $$self{LastCmd} = 'code=RightDown&channel=0&arg1=1&arg2=1&arg3=0'; $$self{Monitor}->suspendMotionDetection() if !$self->{Monitor}->{ModectDuringPTZ}; $self->sendCmd('cgi-bin/ptz.cgi?action=start&'.$$self{LastCmd}); } @@ -236,7 +236,7 @@ sub moveConUpLeft { my $self = shift; Debug('Move Diagonally Up Left'); $$self{Monitor}->suspendMotionDetection() if !$self->{Monitor}->{ModectDuringPTZ}; - $$self{LastCmd} = 'code=LeftUp&channel=0&arg1=0&arg2=1&arg3=0'; + $$self{LastCmd} = 'code=LeftUp&channel=0&arg1=1&arg2=1&arg3=0'; $self->sendCmd('cgi-bin/ptz.cgi?action=start&'.$$self{LastCmd}); } @@ -244,7 +244,7 @@ sub moveConDownLeft { my $self = shift; Debug('Move Diagonally Down Left'); $$self{Monitor}->suspendMotionDetection() if !$self->{Monitor}->{ModectDuringPTZ}; - $$self{LastCmd} = 'code=LeftDown&channel=0&arg1=0&arg2=1&arg3=0'; + $$self{LastCmd} = 'code=LeftDown&channel=0&arg1=1&arg2=1&arg3=0'; $self->sendCmd('cgi-bin/ptz.cgi?action=start&'.$$self{LastCmd}); } diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/DCS5020L.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/DCS5020L.pm index 2b2cf3fd2..dd98b828e 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/DCS5020L.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/DCS5020L.pm @@ -283,7 +283,7 @@ None by default. =head1 SEE ALSO See if there are better instructions for the DCS-5020L at -http://www.zoneminder.com/wiki/index.php/Dlink +https://wiki.zoneminder.com/Dlink =head1 AUTHOR diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/onvif.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/onvif.pm index 12a78144c..c8cf84712 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/onvif.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/onvif.pm @@ -1,6 +1,6 @@ # ========================================================================== # -# ZoneMinder ONVIF Control Protocol Module, $Date: 2021-02-25 22:07:00 +0000 (Thu, 25 Feb 2021) $, $Revision: 0001 $ +# ZoneMinder ONVIF Control Protocol Module # Based on the Netcat onvif script by Andrew Bauer (knnniggett@users.sourceforge.net) # # This program is free software; you can redistribute it and/or @@ -182,6 +182,7 @@ sub sendCmd { $req->header('connection' => 'Close'); $req->content($msg); + my $res = $self->{ua}->request($req); if ( $res->is_success ) { @@ -235,22 +236,39 @@ sub getCamParams { sub autoStop { my $self = shift; my $autostop = shift; + my $iszoom = shift; if ( $autostop ) { Debug('Auto Stop'); my $cmd = $controlUri; - my $msg_body = ' - - - '.$profileToken.' - - true - - - false - - - '; + my $msg_body; + if( $iszoom) { + $msg_body = ' + + + '.$profileToken.' + + false + + + true + + + '; + } else { + $msg_body = ' + + + '.$profileToken.' + + true + + + false + + + '; + } my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; usleep($autostop); @@ -340,9 +358,9 @@ sub moveCamera { my $type = shift; my $x = shift; my $y = shift; - my $msg_move_body = ""; + my $msg_move_body = ''; - if ( $type == "move" ){ + if ( $type eq 'move' ) { $msg_move_body = ' @@ -356,7 +374,7 @@ sub moveCamera { '; - } elsif ( $type == "zoom" ) { + } elsif ( $type eq 'zoom' ) { $msg_move_body = ' @@ -371,7 +389,6 @@ sub moveCamera { } return $msg_move_body; - } #Up Arrow @@ -382,7 +399,7 @@ sub moveConUp { my $msg_body = moveCamera("move", "0","0.5"); my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; $self->sendCmd($cmd, $msg_body, $content_type); - $self->autoStop($self->{Monitor}->{AutoStopTimeout}); + $self->autoStop($self->{Monitor}->{AutoStopTimeout},0); } @@ -394,7 +411,7 @@ sub moveConDown { my $msg_body = moveCamera("move","0","-0.5"); my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; $self->sendCmd($cmd, $msg_body, $content_type); - $self->autoStop($self->{Monitor}->{AutoStopTimeout}); + $self->autoStop($self->{Monitor}->{AutoStopTimeout},0); } #Left Arrow @@ -405,7 +422,7 @@ sub moveConLeft { my $msg_body = moveCamera("move","-0.49","0"); my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; $self->sendCmd($cmd, $msg_body, $content_type); - $self->autoStop($self->{Monitor}->{AutoStopTimeout}); + $self->autoStop($self->{Monitor}->{AutoStopTimeout},0); } #Right Arrow @@ -416,7 +433,7 @@ sub moveConRight { my $msg_body = moveCamera("move","0.49","0"); my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; $self->sendCmd($cmd, $msg_body, $content_type); - $self->autoStop($self->{Monitor}->{AutoStopTimeout}); + $self->autoStop($self->{Monitor}->{AutoStopTimeout},0); } #Zoom In @@ -427,7 +444,7 @@ sub zoomConTele { my $msg_body = moveCamera("zoom","0.49","0"); my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; $self->sendCmd($cmd, $msg_body, $content_type); - $self->autoStop($self->{Monitor}->{AutoStopTimeout}); + $self->autoStop($self->{Monitor}->{AutoStopTimeout},1); } #Zoom Out @@ -438,9 +455,19 @@ sub zoomConWide { my $msg_body = moveCamera("zoom","-0.49","0"); my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; $self->sendCmd($cmd, $msg_body, $content_type); - $self->autoStop($self->{Monitor}->{AutoStopTimeout}); + $self->autoStop($self->{Monitor}->{AutoStopTimeout},1); } +sub zoomStop { + Debug('Zoom Stop'); + my $self = shift; + my $cmd = $controlUri; + $self->autoStop($self->{Monitor}->{AutoStopTimeout},1); + Error('Zoom Stop not implemented'); +} + + + #Diagonally Up Right Arrow #This camera does not have builtin diagonal commands so we emulate them sub moveConUpRight { @@ -450,7 +477,7 @@ sub moveConUpRight { my $msg_body = moveCamera("move","0.5","0.5"); my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; $self->sendCmd($cmd, $msg_body, $content_type); - $self->autoStop($self->{Monitor}->{AutoStopTimeout}); + $self->autoStop($self->{Monitor}->{AutoStopTimeout},0); } #Diagonally Down Right Arrow @@ -462,7 +489,7 @@ sub moveConDownRight { my $msg_body = moveCamera("move","0.5","-0.5"); my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; $self->sendCmd($cmd, $msg_body, $content_type); - $self->autoStop($self->{Monitor}->{AutoStopTimeout}); + $self->autoStop($self->{Monitor}->{AutoStopTimeout},0); } #Diagonally Up Left Arrow @@ -474,7 +501,7 @@ sub moveConUpLeft { my $msg_body = moveCamera("move","-0.5","0.5"); my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; $self->sendCmd($cmd, $msg_body, $content_type); - $self->autoStop($self->{Monitor}->{AutoStopTimeout}); + $self->autoStop($self->{Monitor}->{AutoStopTimeout},0); } #Diagonally Down Left Arrow @@ -486,7 +513,7 @@ sub moveConDownLeft { my $msg_body = moveCamera("move","-0.5","-0.5"); my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; $self->sendCmd($cmd, $msg_body, $content_type); - $self->autoStop($self->{Monitor}->{AutoStopTimeout}); + $self->autoStop($self->{Monitor}->{AutoStopTimeout},0); } #Stop @@ -499,7 +526,7 @@ sub moveStop { '.$profileToken.' true - false + true '; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm index 5711437f2..3a3308938 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm @@ -127,9 +127,11 @@ sub Execute { foreach my $term ( @{$$self{PostSQLConditions}} ) { if ( $$term{attr} eq 'ExistsInFileSystem' ) { foreach my $row ( @results ) { - my $event = new ZoneMinder::Event($row); + my $event = new ZoneMinder::Event($$row{Id}, $row); if ( -e $event->Path() ) { - push @filtered_events, $row; + push @filtered_events, $row if $$term{val} eq 'true'; + } else { + push @filtered_events, $row if $$term{val} eq 'false'; } } } @@ -164,138 +166,142 @@ sub Sql { if ( exists($term->{obr}) ) { $self->{Sql} .= str_repeat('(', $term->{obr}).' '; } + if (!$term->{attr}) { + Error("Invalid term in filter $$self{Id}. Empty attr"); + next; + } + my $value = $term->{val}; my @value_list; - if ( $term->{attr} ) { - if ( $term->{attr} eq 'AlarmedZoneId' ) { - $term->{op} = 'EXISTS'; - } elsif ( $term->{attr} =~ /^Monitor/ ) { - $sql = 'SELECT E.*, unix_timestamp(E.StartDateTime) as Time, M.Name as MonitorName - FROM Events as E INNER JOIN Monitors as M on M.Id = E.MonitorId'; - my ( $temp_attr_name ) = $term->{attr} =~ /^Monitor(.+)$/; - $self->{Sql} .= 'M.'.$temp_attr_name; - } elsif ( $term->{attr} eq 'ServerId' or $term->{attr} eq 'MonitorServerId' ) { - $sql = 'SELECT E.*, unix_timestamp(E.StartDateTime) as Time, M.Name as MonitorName - FROM Events as E INNER JOIN Monitors as M on M.Id = E.MonitorId'; - $self->{Sql} .= 'M.ServerId'; - } elsif ( $term->{attr} eq 'StorageServerId' ) { - $self->{Sql} .= '(SELECT Storage.ServerId FROM Storage WHERE Storage.Id=E.StorageId)'; - } elsif ( $term->{attr} eq 'FilterServerId' ) { - $self->{Sql} .= $Config{ZM_SERVER_ID}; -# StartTime options - } elsif ( $term->{attr} eq 'DateTime' ) { - $self->{Sql} .= 'E.StartDateTime'; - } elsif ( $term->{attr} eq 'Date' ) { - $self->{Sql} .= 'to_days( E.StartDateTime )'; - } elsif ( $term->{attr} eq 'StartDate' ) { - $self->{Sql} .= 'to_days( E.StartDateTime )'; - } elsif ( $term->{attr} eq 'Time' or $term->{attr} eq 'StartTime' ) { - $self->{Sql} .= 'extract( hour_second from E.StartDateTime )'; - } elsif ( $term->{attr} eq 'Weekday' or $term->{attr} eq 'StartWeekday' ) { - $self->{Sql} .= 'weekday( E.StartDateTime )'; -# EndTIme options - } elsif ( $term->{attr} eq 'EndDateTime' ) { - $self->{Sql} .= 'E.EndDateTime'; - } elsif ( $term->{attr} eq 'EndDate' ) { - $self->{Sql} .= 'to_days( E.EndDateTime )'; - } elsif ( $term->{attr} eq 'EndDateTime' ) { - $self->{Sql} .= 'extract( hour_second from E.EndDateTime )'; - } elsif ( $term->{attr} eq 'EndWeekday' ) { - $self->{Sql} .= "weekday( E.EndDateTime )"; - } elsif ( $term->{attr} eq 'ExistsInFileSystem' ) { - push @{$self->{PostSQLConditions}}, $term; - $self->{Sql} .= 'TRUE /* ExistsInFileSystem */'; - } elsif ( $term->{attr} eq 'DiskPercent' ) { - $self->{Sql} .= 'zmDiskPercent'; - $self->{HasDiskPercent} = !undef; - } elsif ( $term->{attr} eq 'DiskBlocks' ) { - $self->{Sql} .= 'zmDiskBlocks'; - $self->{HasDiskBlocks} = !undef; - } elsif ( $term->{attr} eq 'SystemLoad' ) { - $self->{Sql} .= 'zmSystemLoad'; - $self->{HasSystemLoad} = !undef; - } else { - $self->{Sql} .= 'E.'.$term->{attr}; - } + if ( $term->{attr} eq 'AlarmedZoneId' ) { + $term->{op} = 'EXISTS'; + } elsif ( $term->{attr} =~ /^Monitor/ ) { + $sql = 'SELECT E.*, unix_timestamp(E.StartDateTime) as Time, M.Name as MonitorName + FROM Events as E INNER JOIN Monitors as M on M.Id = E.MonitorId'; + my ( $temp_attr_name ) = $term->{attr} =~ /^Monitor(.+)$/; + $self->{Sql} .= 'M.'.$temp_attr_name; + } elsif ( $term->{attr} eq 'ServerId' or $term->{attr} eq 'MonitorServerId' ) { + $sql = 'SELECT E.*, unix_timestamp(E.StartDateTime) as Time, M.Name as MonitorName + FROM Events as E INNER JOIN Monitors as M on M.Id = E.MonitorId'; + $self->{Sql} .= 'M.ServerId'; + } elsif ( $term->{attr} eq 'StorageServerId' ) { + $self->{Sql} .= '(SELECT Storage.ServerId FROM Storage WHERE Storage.Id=E.StorageId)'; + } elsif ( $term->{attr} eq 'FilterServerId' ) { + $self->{Sql} .= $Config{ZM_SERVER_ID}; + # StartTime options + } elsif ( $term->{attr} eq 'DateTime' ) { + $self->{Sql} .= 'E.StartDateTime'; + } elsif ( $term->{attr} eq 'Date' ) { + $self->{Sql} .= 'to_days( E.StartDateTime )'; + } elsif ( $term->{attr} eq 'StartDate' ) { + $self->{Sql} .= 'to_days( E.StartDateTime )'; + } elsif ( $term->{attr} eq 'Time' or $term->{attr} eq 'StartTime' ) { + $self->{Sql} .= 'extract( hour_second from E.StartDateTime )'; + } elsif ( $term->{attr} eq 'Weekday' or $term->{attr} eq 'StartWeekday' ) { + $self->{Sql} .= 'weekday( E.StartDateTime )'; - if ( $term->{attr} eq 'ExistsInFileSystem' ) { - # PostCondition, so no further SQL - } else { - ( my $stripped_value = $value ) =~ s/^["\']+?(.+)["\']+?$/$1/; - foreach my $temp_value ( split( /["'\s]*?,["'\s]*?/, $stripped_value ) ) { + # EndTIme options + } elsif ( $term->{attr} eq 'EndDateTime' ) { + $self->{Sql} .= 'E.EndDateTime'; + } elsif ( $term->{attr} eq 'EndDate' ) { + $self->{Sql} .= 'to_days( E.EndDateTime )'; + } elsif ( $term->{attr} eq 'EndTime' ) { + $self->{Sql} .= 'extract( hour_second from E.EndDateTime )'; + } elsif ( $term->{attr} eq 'EndWeekday' ) { + $self->{Sql} .= 'weekday( E.EndDateTime )'; + } elsif ( $term->{attr} eq 'ExistsInFileSystem' ) { + push @{$self->{PostSQLConditions}}, $term; + $self->{Sql} .= 'TRUE /* ExistsInFileSystem */'; + } elsif ( $term->{attr} eq 'DiskPercent' ) { + $self->{Sql} .= 'zmDiskPercent'; + $self->{HasDiskPercent} = !undef; + } elsif ( $term->{attr} eq 'DiskBlocks' ) { + $self->{Sql} .= 'zmDiskBlocks'; + $self->{HasDiskBlocks} = !undef; + } elsif ( $term->{attr} eq 'SystemLoad' ) { + $self->{Sql} .= 'zmSystemLoad'; + $self->{HasSystemLoad} = !undef; + } else { + $self->{Sql} .= 'E.'.$term->{attr}; + } - if ( $term->{attr} eq 'AlarmedZoneId' ) { - $value = '(SELECT * FROM Stats WHERE EventId=E.Id AND Score > 0 AND ZoneId='.$value.')'; - } elsif ( $term->{attr} =~ /^MonitorName/ ) { - $value = "'$temp_value'"; - } elsif ( $term->{attr} =~ /ServerId/) { - Debug("ServerId, temp_value is ($temp_value) ($ZoneMinder::Config::Config{ZM_SERVER_ID})"); - if ( $temp_value eq 'ZM_SERVER_ID' ) { - $value = "'$ZoneMinder::Config::Config{ZM_SERVER_ID}'"; - # This gets used later, I forget for what - $$self{Server} = new ZoneMinder::Server($ZoneMinder::Config::Config{ZM_SERVER_ID}); - } elsif ( $temp_value eq 'NULL' ) { - $value = $temp_value; - } else { - $value = "'$temp_value'"; - # This gets used later, I forget for what - $$self{Server} = new ZoneMinder::Server($temp_value); - } - } elsif ( $term->{attr} eq 'StorageId' ) { - $value = "'$temp_value'"; - $$self{Storage} = new ZoneMinder::Storage($temp_value); - } elsif ( $term->{attr} eq 'Name' - || $term->{attr} eq 'Cause' - || $term->{attr} eq 'Notes' - ) { - if ( $term->{op} eq 'LIKE' - || $term->{op} eq 'NOT LIKE' - ) { - $temp_value = '%'.$temp_value.'%' if $temp_value !~ /%/; - } - $value = "'$temp_value'"; - } elsif ( $term->{attr} eq 'DateTime' or $term->{attr} eq 'StartDateTime' or $term->{attr} eq 'EndDateTime' ) { - if ( $temp_value eq 'NULL' ) { - $value = $temp_value; - } else { - $value = DateTimeToSQL($temp_value); - if ( !$value ) { - Error("Error parsing date/time '$temp_value', skipping filter '$self->{Name}'"); - return; - } - $value = "'$value'"; - } - } elsif ( $term->{attr} eq 'Date' or $term->{attr} eq 'StartDate' or $term->{attr} eq 'EndDate' ) { - if ( $temp_value eq 'NULL' ) { - $value = $temp_value; - } elsif ( $temp_value eq 'CURDATE()' or $temp_value eq 'NOW()' ) { - $value = 'to_days('.$temp_value.')'; - } else { - $value = DateTimeToSQL($temp_value); - if ( !$value ) { - Error("Error parsing date/time '$temp_value', skipping filter '$self->{Name}'"); - return; - } - $value = "to_days( '$value' )"; - } - } elsif ( $term->{attr} eq 'Time' or $term->{attr} eq 'StartTime' or $term->{attr} eq 'EndTime' ) { - if ( $temp_value eq 'NULL' ) { - $value = $temp_value; - } else { - $value = DateTimeToSQL($temp_value); - if ( !$value ) { - Error("Error parsing date/time '$temp_value', skipping filter '$self->{Name}'"); - return; - } - $value = "extract( hour_second from '$value' )"; - } - } else { + if ( $term->{attr} eq 'ExistsInFileSystem' ) { + # PostCondition, so no further SQL + } else { + ( my $stripped_value = $value ) =~ s/^["\']+?(.+)["\']+?$/$1/; + foreach my $temp_value ( split( /["'\s]*?,["'\s]*?/, $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/ ) { + $value = "'$temp_value'"; + } elsif ( $term->{attr} =~ /ServerId/) { + Debug("ServerId, temp_value is ($temp_value) ($ZoneMinder::Config::Config{ZM_SERVER_ID})"); + if ( $temp_value eq 'ZM_SERVER_ID' ) { + $value = "'$ZoneMinder::Config::Config{ZM_SERVER_ID}'"; + # This gets used later, I forget for what + $$self{Server} = new ZoneMinder::Server($ZoneMinder::Config::Config{ZM_SERVER_ID}); + } elsif ( $temp_value eq 'NULL' ) { $value = $temp_value; + } else { + $value = "'$temp_value'"; + # This gets used later, I forget for what + $$self{Server} = new ZoneMinder::Server($temp_value); } - push @value_list, $value; - } # end foreach temp_value - } # end if has an attr + } elsif ( $term->{attr} eq 'StorageId' ) { + $value = "'$temp_value'"; + $$self{Storage} = new ZoneMinder::Storage($temp_value); + } elsif ( $term->{attr} eq 'Name' + || $term->{attr} eq 'Cause' + || $term->{attr} eq 'Notes' + ) { + if ( $term->{op} eq 'LIKE' + || $term->{op} eq 'NOT LIKE' + ) { + $temp_value = '%'.$temp_value.'%' if $temp_value !~ /%/; + } + $value = "'$temp_value'"; + } elsif ( $term->{attr} eq 'DateTime' or $term->{attr} eq 'StartDateTime' or $term->{attr} eq 'EndDateTime' ) { + if ( $temp_value eq 'NULL' ) { + $value = $temp_value; + } else { + $value = DateTimeToSQL($temp_value); + if ( !$value ) { + Error("Error parsing date/time '$temp_value', skipping filter '$self->{Name}'"); + return; + } + $value = "'$value'"; + } + } elsif ( $term->{attr} eq 'Date' or $term->{attr} eq 'StartDate' or $term->{attr} eq 'EndDate' ) { + if ( $temp_value eq 'NULL' ) { + $value = $temp_value; + } elsif ( $temp_value eq 'CURDATE()' or $temp_value eq 'NOW()' ) { + $value = 'to_days('.$temp_value.')'; + } else { + $value = DateTimeToSQL($temp_value); + if ( !$value ) { + Error("Error parsing date/time '$temp_value', skipping filter '$self->{Name}'"); + return; + } + $value = "to_days( '$value' )"; + } + } elsif ( $term->{attr} eq 'Time' or $term->{attr} eq 'StartTime' or $term->{attr} eq 'EndTime' ) { + if ( $temp_value eq 'NULL' ) { + $value = $temp_value; + } else { + $value = DateTimeToSQL($temp_value); + if ( !$value ) { + Error("Error parsing date/time '$temp_value', skipping filter '$self->{Name}'"); + return; + } + $value = "extract( hour_second from '$value' )"; + } + } else { + $value = $temp_value; + } + push @value_list, $value; + } # end foreach temp_value if ( $term->{op} ) { if ( $term->{op} eq '=~' ) { diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm b/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm index de29b8dd0..d01ef3455 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm @@ -136,8 +136,8 @@ $serial = $primary_key = 'Id'; %defaults = ( ServerId => 0, StorageId => 0, - Type => 'Ffmpeg', - Function => 'Mocord', + Type => q`'Ffmpeg'`, + Function => q`'Mocord'`, Enabled => 1, LinkedMonitors => undef, Device => '', @@ -166,15 +166,15 @@ $serial = $primary_key = 'Id'; VideoWriter => 0, OutputCodec => undef, OutputContainer => undef, - EncoderParameters => "# Lines beginning with # are a comment \n# For changing quality, use the crf option\n# 1 is best, 51 is worst quality\n#crf=23\n", + EncoderParameters => '', RecordAudio=>0, RTSPDescribe=>0, Brightness => -1, Contrast => -1, Hue => -1, Colour => -1, - EventPrefix => 'Event-', - LabelFormat => '%N - %d/%m/%y %H:%M:%S', + EventPrefix => q`'Event-'`, + LabelFormat => '', LabelX => 0, LabelY => 0, LabelSize => 1, @@ -208,13 +208,13 @@ $serial = $primary_key = 'Id'; DefaultRate => 100, DefaultScale => 100, SignalCheckPoints => 0, - SignalCheckColour => '#0000BE', - WebColour => '#ff0000', + SignalCheckColour => q`'#0000BE'`, + WebColour => q`'#ff0000'`, Exif => 0, Sequence => undef, ZoneCount => 0, Refresh => undef, - DefaultCodec => 'auto', + DefaultCodec => q`'auto'`, Latitude => undef, Longitude => undef, ); @@ -279,21 +279,37 @@ sub disconnect { sub suspendMotionDetection { my $self = shift; return 0 if ! ZoneMinder::Memory::zmMemVerify($self); - while (ZoneMinder::Memory::zmMemRead($self, 'shared_data:active', 1)) { + return if $$self{Function} eq 'Nodect' or $$self{Function} eq 'Monitor' or $$self{Function} eq 'None'; + my $count = 50; + while ($count and ZoneMinder::Memory::zmMemRead($self, 'shared_data:active', 1)) { ZoneMinder::Logger::Debug(1, 'Suspending motion detection'); ZoneMinder::Memory::zmMonitorSuspend($self); usleep(100000); + $count -= 1; + } + if (!$count) { + ZoneMinder::Logger::Error('Unable to suspend motion detection after 5 seconds.'); + ZoneMinder::Memory::zmMemInvalidate($self); # Close our file handle to the zmc process we are about to end + } else { + ZoneMinder::Logger::Debug(1, 'shared_data:active='.ZoneMinder::Memory::zmMemRead($self, 'shared_data:active', 1)); } - ZoneMinder::Logger::Debug(1,ZoneMinder::Memory::zmMemRead($self, 'shared_data:active', 1)); } sub resumeMotionDetection { my $self = shift; return 0 if ! ZoneMinder::Memory::zmMemVerify($self); - #while (zmMemRead($self, 'shared_data:active', 1)) { + return if $$self{Function} eq 'Nodect' or $$self{Function} eq 'Monitor' or $$self{Function} eq 'None'; + my $count = 50; + while ($count and !ZoneMinder::Memory::zmMemRead($self, 'shared_data:active', 1)) { ZoneMinder::Logger::Debug(1, 'Resuming motion detection'); - ZoneMinder::Memory::zmMonitorResume($self); - #} + ZoneMinder::Memory::zmMonitorResume($self); + usleep(100000); + $count -= 1; + } + if (!$count) { + ZoneMinder::Logger::Error('Unable to resume motion detection after 5 seconds.'); + ZoneMinder::Memory::zmMemInvalidate($self); # Close our file handle to the zmc process we are about to end + } return 1; } diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Zone.pm b/scripts/ZoneMinder/lib/ZoneMinder/Zone.pm index 986c5a4dc..97c6d32e8 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Zone.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Zone.pm @@ -40,6 +40,10 @@ $serial = $primary_key = 'Id'; MonitorId Type Units + NumCoords + Coords + Area + AlarmRGB CheckMethod MinPixelThreshold MaxPixelThreshold @@ -59,9 +63,13 @@ $serial = $primary_key = 'Id'; %defaults = ( Name => '', - Type => 'Active', - Units => 'Pixels', - CheckMethod => 'Blobs', + Type => q`'Active'`, + Units => q`'Pixels'`, + NumCoords => 0, + Coords => '', + Area => 0, + AlarmRGB => 0, + CheckMethod => q`'Blobs'`, MinPixelThreshold => undef, MaxPixelThreshold => undef, MinAlarmPixels => undef, diff --git a/scripts/zmcontrol.pl.in b/scripts/zmcontrol.pl.in index d1f50cf43..8a93a19a4 100644 --- a/scripts/zmcontrol.pl.in +++ b/scripts/zmcontrol.pl.in @@ -61,12 +61,12 @@ GetOptions( 'autostop' =>\$options{autostop}, ) or pod2usage(-exitstatus => -1); -if ( !$id ) { +if (!$id) { print(STDERR "Please give a valid monitor id\n"); pod2usage(-exitstatus => -1); } -( $id ) = $id =~ /^(\w+)$/; +($id) = $id =~ /^(\w+)$/; logInit($id?(id=>'zmcontrol_'.$id):()); my $sock_file = $Config{ZM_PATH_SOCKS}.'/zmcontrol-'.$id.'.sock'; @@ -76,7 +76,7 @@ socket(CLIENT, PF_UNIX, SOCK_STREAM, 0) or Fatal("Can't open socket: $!"); my $saddr = sockaddr_un($sock_file); -if ( $options{command} ) { +if ($options{command}) { # Have a command, so we are the client, connect to the server and send it. my $tries = 10; @@ -101,18 +101,16 @@ if ( $options{command} ) { Error("Unable to connect to zmcontrol server at $sock_file"); } } else { - # The server isn't there my $monitor = zmDbGetMonitorAndControl($id); - if ( !$monitor ) { - Fatal("Unable to load control data for monitor $id"); - } + Fatal("Unable to load control data for monitor $id") if !$monitor; + my $protocol = $monitor->{Protocol}; - if ( !$protocol ) { + if (!$protocol) { Fatal('No protocol is set in monitor. Please edit the monitor, edit control type, select the control capability and fill in the Protocol field'); } - if ( -x $protocol ) { + if (-x $protocol) { # Protocol is actually a script! # Holdover from previous versions my $command .= $protocol.' '.$arg_string; @@ -120,11 +118,11 @@ if ( $options{command} ) { my $output = qx($command); my $status = $? >> 8; - if ( $status || logDebugging() ) { + if ($status || logDebugging()) { chomp($output); Debug("Output: $output"); } - if ( $status ) { + if ($status) { Error("Command '$command' exited with status: $status"); exit($status); } @@ -134,7 +132,7 @@ if ( $options{command} ) { Info("Starting control server $id/$protocol"); close(CLIENT); - if ( ! can_load( modules => { "ZoneMinder::Control::$protocol" => undef } ) ) { + if (!can_load(modules => {'ZoneMinder::Control::'.$protocol => undef})) { Fatal("Can't load ZoneMinder::Control::$protocol\n$Module::Load::Conditional::ERROR"); } @@ -159,7 +157,7 @@ if ( $options{command} ) { $control->open(); # If we have a command when starting up, then do it. - if ( $options{command} ) { + if ($options{command}) { my $command = $options{command}; $control->$command(\%options); } diff --git a/scripts/zmfilter.pl.in b/scripts/zmfilter.pl.in index 60d6293b4..1f2906b4b 100644 --- a/scripts/zmfilter.pl.in +++ b/scripts/zmfilter.pl.in @@ -527,7 +527,7 @@ sub uploadArchFile { return(0); } - my $archFile = $Event->{MonitorName}.'-'.$Event->{Id}; + my $archFile = $Event->Monitor()->Name().'-'.$Event->{Id}; my $archImagePath = $Event->Path() .'/' .( @@ -548,6 +548,10 @@ sub uploadArchFile { my $status = &AZ_OK; foreach my $imageFile ( @archImageFiles ) { + if (! -e $imageFile) { + Debug("Not adding $imageFile because it doesn't exist"); + next; + } Debug("Adding $imageFile"); my $member = $zip->addFile($imageFile); if ( !$member ) { diff --git a/scripts/zmpkg.pl.in b/scripts/zmpkg.pl.in index 2f6708fc8..3302b0019 100644 --- a/scripts/zmpkg.pl.in +++ b/scripts/zmpkg.pl.in @@ -103,18 +103,17 @@ if ( $command eq 'state' ) { 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() ) { + while ( my $monitor = $sth->fetchrow_hashref() ) { foreach my $definition ( @{$state->{Definitions}} ) { if ( $monitor->{Id} =~ /^$definition->{Id}$/ ) { $monitor->{NewFunction} = $definition->{Function}; $monitor->{NewEnabled} = $definition->{Enabled}; + last; } } - #next if ( !$monitor->{NewFunction} ); - $monitor->{NewFunction} = 'None' - if ( !$monitor->{NewFunction} ); - $monitor->{NewEnabled} = 0 - if ( !$monitor->{NewEnabled} ); + next if ! exists $monitor->{NewFunction}; + $monitor->{NewFunction} = 'None' if !$monitor->{NewFunction}; + $monitor->{NewEnabled} = 0 if !$monitor->{NewEnabled}; if ( $monitor->{Function} ne $monitor->{NewFunction} || $monitor->{Enabled} ne $monitor->{NewEnabled} ) { diff --git a/scripts/zmstats.pl.in b/scripts/zmstats.pl.in index 3c86aada6..a03f68ada 100644 --- a/scripts/zmstats.pl.in +++ b/scripts/zmstats.pl.in @@ -1,5 +1,6 @@ #!@PERL_EXECUTABLE@ -wT use strict; +use warnings; use bytes; # ========================================================================== @@ -79,14 +80,18 @@ while (!$zm_terminate) { } my $rows; do { - my $rows = zmDbDo('DELETE FROM `Logs` WHERE `TimeKey` < unix_timestamp(now() - interval '.$Config{ZM_LOG_DATABASE_LIMIT}.') LIMIT 100'); - Debug("Deleted $rows log table entries by time") if defined $rows; - } while ($rows); + $rows = zmDbDo('DELETE FROM `Logs` WHERE `TimeKey` < unix_timestamp(now() - interval '.$Config{ZM_LOG_DATABASE_LIMIT}.') LIMIT 100'); + Debug("Deleted $rows log table entries by time") if $rows; + } while ($rows and ($rows == 100) and !$zm_terminate); } } # end if ZM_LOG_DATABASE_LIMIT - # Delete any sessions that are more ethan a week old. Limiting to 100 because mysql sucks - zmDbDo('DELETE FROM Sessions WHERE access < ? LIMIT 100', time - (60*60*24*7)); + my $rows; + do { + # Delete any sessions that are more than a week old. Limiting to 100 because mysql sucks + $rows = zmDbDo('DELETE FROM Sessions WHERE access < ? LIMIT 100', time - (60*60*24*7)); + Debug("Deleted $rows sessions") if $rows; + } while ($rows and ($rows == 100) and !$zm_terminate); sleep($Config{ZM_STATS_UPDATE_INTERVAL}); } # end while (!$zm_terminate) diff --git a/scripts/zmupdate.pl.in b/scripts/zmupdate.pl.in index 8226af37d..0253fadc2 100644 --- a/scripts/zmupdate.pl.in +++ b/scripts/zmupdate.pl.in @@ -27,7 +27,7 @@ zmupdate.pl - check and upgrade ZoneMinder database =head1 SYNOPSIS -zmupdate.pl -c,--check | -f,--freshen | -v,--version= [-u -p] +zmupdate.pl -c,--check | -f,--freshen | -v,--version= [-u -p ] =head1 DESCRIPTION diff --git a/scripts/zmwatch.pl.in b/scripts/zmwatch.pl.in index f1fbd16b4..96235ccd3 100644 --- a/scripts/zmwatch.pl.in +++ b/scripts/zmwatch.pl.in @@ -98,19 +98,19 @@ while (!$zm_terminate) { next if $monitor->{Type} eq 'WebSite'; my $now = time(); my $restart = 0; - if ( zmMemVerify($monitor) ) { + if (zmMemVerify($monitor)) { # Check we have got an image recently my $capture_time = zmGetLastWriteTime($monitor); - if ( !defined($capture_time) ) { + 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 ) { + if (!$capture_time) { my $startup_time = zmGetStartupTime($monitor); - if ( ( $now - $startup_time ) > $Config{ZM_WATCH_MAX_DELAY} ) { + 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}" @@ -122,7 +122,7 @@ while (!$zm_terminate) { next; } } - if ( ! $restart ) { + if (!$restart) { my $max_image_delay = ( $monitor->{MaxFPS} &&($monitor->{MaxFPS}>0) @@ -144,29 +144,28 @@ while (!$zm_terminate) { $restart = 1; } - if ( $restart ) { + if ($restart) { my $command; - if ( $monitor->{Type} eq 'Local' ) { - $command = "zmdc.pl restart zmc -d $monitor->{Device}"; + if ($monitor->{Type} eq 'Local') { + $command = 'zmdc.pl restart zmc -d '.$monitor->{Device}; } else { - $command = "zmdc.pl restart zmc -m $monitor->{Id}"; + $command = 'zmdc.pl restart zmc -m '.$monitor->{Id}; } runCommand($command); - } elsif ( $monitor->{Function} ne 'Monitor' ) { + } elsif ($monitor->{Function} ne 'Monitor') { # Now check analysis daemon $restart = 0; # Check we have got an image recently my $image_time = zmGetLastReadTime($monitor); - if ( !defined($image_time) ) { + if (!defined($image_time)) { # Can't read from shared data $restart = 1; Error("Error reading shared data for $$monitor{Id} $$monitor{Name}"); - } elsif ( !$image_time ) { + } elsif (!$image_time) { # We can't get the last capture time so can't be sure it's died. #$restart = 1; Error("Last analyse time for $$monitor{Id} $$monitor{Name} was zero."); } else { - my $max_image_delay = ( $monitor->{MaxFPS} &&($monitor->{MaxFPS}>0) &&($monitor->{MaxFPS}<1) @@ -175,7 +174,7 @@ 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 ) { + 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)" ); @@ -183,13 +182,13 @@ while (!$zm_terminate) { } } - if ( $restart ) { - Info("Restarting analysis daemon for $$monitor{Id} $$monitor{Name}\n"); + 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}"; + $command = 'zmdc.pl restart zmc -d '.$monitor->{Device}; } else { - $command = "zmdc.pl restart zmc -m $monitor->{Id}"; + $command = 'zmdc.pl restart zmc -m '.$monitor->{Id}; } runCommand($command); } # end if restart @@ -201,7 +200,7 @@ while (!$zm_terminate) { sleep($Config{ZM_WATCH_CHECK_INTERVAL}); } # end while (!$zm_terminate) -Info("Watchdog exiting"); +Info('Watchdog exiting'); exit(); 1; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 281c7ba34..5f37ff0ab 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -6,12 +6,10 @@ 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_box.cpp zm_buffer.cpp zm_camera.cpp zm_comms.cpp zm_config.cpp - zm_coord.cpp zm_curl_camera.cpp zm_crypt.cpp zm.cpp @@ -62,7 +60,6 @@ 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 @@ -79,13 +76,34 @@ target_include_directories(zm target_link_libraries(zm PUBLIC + FFMPEG::avcodec + FFMPEG::avformat + FFMPEG::avutil + FFMPEG::swresample + FFMPEG::swscale libbcrypt::bcrypt - jwt-cpp::jwt-cpp RtspServer::RtspServer martinmoene::span-lite + ${ZM_BIN_LIBS} PRIVATE zm-core-interface) +if(${ZM_JWT_BACKEND} STREQUAL "jwt_cpp") + target_link_libraries(zm + PUBLIC + jwt-cpp::jwt-cpp) +elseif(${ZM_JWT_BACKEND} STREQUAL "libjwt") + target_link_libraries(zm + PUBLIC + JWT::libjwt) +endif() + +if(TARGET V4L2::videodev2) + target_link_libraries(zm + PRIVATE + V4L2::videodev2) +endif() + add_executable(zmc zmc.cpp) add_executable(zms zms.cpp) add_executable(zmu zmu.cpp) @@ -95,7 +113,6 @@ target_link_libraries(zmc zm-core-interface zm ${ZM_EXTRA_LIBS} - ${ZM_BIN_LIBS} ${CMAKE_DL_LIBS}) target_link_libraries(zms @@ -103,7 +120,6 @@ target_link_libraries(zms zm-core-interface zm ${ZM_EXTRA_LIBS} - ${ZM_BIN_LIBS} ${CMAKE_DL_LIBS}) target_link_libraries(zmu @@ -111,9 +127,7 @@ target_link_libraries(zmu zm-core-interface zm ${ZM_EXTRA_LIBS} - ${ZM_BIN_LIBS} - ${CMAKE_DL_LIBS} - bcrypt) + ${CMAKE_DL_LIBS}) # Generate man files for the binaries destined for the bin folder if(BUILD_MAN) @@ -134,8 +148,6 @@ if(HAVE_RTSP_SERVER) zm-core-interface zm ${ZM_EXTRA_LIBS} - ${ZM_BIN_LIBS} - ${CMAKE_DL_LIBS} - bcrypt) + ${CMAKE_DL_LIBS}) install(TARGETS zm_rtsp_server RUNTIME DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) endif() diff --git a/src/zm_analysis_thread.cpp b/src/zm_analysis_thread.cpp index 74d677316..7d803c16b 100644 --- a/src/zm_analysis_thread.cpp +++ b/src/zm_analysis_thread.cpp @@ -2,7 +2,7 @@ #include "zm_monitor.h" #include "zm_signal.h" -#include "zm_utils.h" +#include "zm_time.h" AnalysisThread::AnalysisThread(Monitor *monitor) : monitor_(monitor), terminate_(false) { @@ -22,28 +22,8 @@ void AnalysisThread::Start() { } void AnalysisThread::Run() { - Microseconds analysis_rate = Microseconds(monitor_->GetAnalysisRate()); - Seconds analysis_update_delay = Seconds(monitor_->GetAnalysisUpdateDelay()); - Debug(2, "AnalysisThread::Run() has an update delay of %" PRId64 "s", - static_cast(analysis_update_delay.count())); - - monitor_->UpdateAdaptiveSkip(); - - TimePoint last_analysis_update_time = std::chrono::steady_clock::now(); - TimePoint cur_time; - while (!(terminate_ or zm_terminate)) { // Some periodic updates are required for variable capturing framerate - if (analysis_update_delay != Seconds::zero()) { - cur_time = std::chrono::steady_clock::now(); - Debug(2, "Updating adaptive skip"); - if ((cur_time - last_analysis_update_time) > analysis_update_delay) { - analysis_rate = Microseconds(monitor_->GetAnalysisRate()); - monitor_->UpdateAdaptiveSkip(); - last_analysis_update_time = cur_time; - } - } - Debug(2, "Analyzing"); if (!monitor_->Analyse()) { if (!(terminate_ or zm_terminate)) { @@ -51,11 +31,6 @@ void AnalysisThread::Run() { Debug(2, "Sleeping for %" PRId64 "us", int64(sleep_for.count())); std::this_thread::sleep_for(sleep_for); } - } else if (analysis_rate != Microseconds::zero()) { - Debug(2, "Sleeping for %" PRId64 " us", int64(analysis_rate.count())); - std::this_thread::sleep_for(analysis_rate); - } else { - Debug(2, "Not sleeping"); } } } diff --git a/src/zm_box.cpp b/src/zm_box.cpp deleted file mode 100644 index 6129b7a29..000000000 --- a/src/zm_box.cpp +++ /dev/null @@ -1,22 +0,0 @@ -// -// ZoneMinder Box 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_box.h" - -// This section deliberately left blank diff --git a/src/zm_box.h b/src/zm_box.h index 20e30bcd1..22210f2f8 100644 --- a/src/zm_box.h +++ b/src/zm_box.h @@ -20,49 +20,59 @@ #ifndef ZM_BOX_H #define ZM_BOX_H -#include "zm_coord.h" +#include "zm_line.h" +#include "zm_vector2.h" #include +#include // // Class used for storing a box, which is defined as a region // defined by two coordinates // class Box { -private: - Coord lo, hi; - Coord size; + public: + Box() = default; + Box(Vector2 lo, Vector2 hi) : lo_(lo), hi_(hi), size_(hi - lo) {} -public: - inline Box() : lo(0,0), hi(0,0), size(0,0) { } - explicit inline Box(unsigned int p_size) : lo(0, 0), hi(p_size-1, p_size-1), size(Coord::Range(hi, lo)) { } - inline Box( int p_x_size, int p_y_size ) : lo( 0, 0 ), hi ( p_x_size-1, p_y_size-1 ), size( Coord::Range( hi, lo ) ) { } - inline Box( int lo_x, int lo_y, int hi_x, int hi_y ) : lo( lo_x, lo_y ), hi( hi_x, hi_y ), size( Coord::Range( hi, lo ) ) { } - inline Box( const Coord &p_lo, const Coord &p_hi ) : lo( p_lo ), hi( p_hi ), size( Coord::Range( hi, lo ) ) { } + const Vector2 &Lo() const { return lo_; } + const Vector2 &Hi() const { return hi_; } - inline const Coord &Lo() const { return lo; } - inline int LoX() const { return lo.X(); } - inline int LoX(int p_lo_x) { return lo.X(p_lo_x); } - inline int LoY() const { return lo.Y(); } - inline int LoY(int p_lo_y) { return lo.Y(p_lo_y); } - inline const Coord &Hi() const { return hi; } - inline int HiX() const { return hi.X(); } - inline int HiX(int p_hi_x) { return hi.X(p_hi_x); } - inline int HiY() const { return hi.Y(); } - inline int HiY(int p_hi_y) { return hi.Y(p_hi_y); } - inline const Coord &Size() const { return size; } - inline int Width() const { return size.X(); } - inline int Height() const { return size.Y(); } - inline int Area() const { return size.X()*size.Y(); } + const Vector2 &Size() const { return size_; } + int32 Area() const { return size_.x_ * size_.y_; } - inline const Coord Centre() const { - int mid_x = int(std::round(lo.X()+(size.X()/2.0))); - int mid_y = int(std::round(lo.Y()+(size.Y()/2.0))); - return Coord( mid_x, mid_y ); + Vector2 Centre() const { + int32 mid_x = static_cast(std::lround(lo_.x_ + (size_.x_ / 2.0))); + int32 mid_y = static_cast(std::lround(lo_.y_ + (size_.y_ / 2.0))); + return {mid_x, mid_y}; } - inline bool Inside( const Coord &coord ) const - { - return( coord.X() >= lo.X() && coord.X() <= hi.X() && coord.Y() >= lo.Y() && coord.Y() <= hi.Y() ); + + // Get vertices of the box in a counter-clockwise order + std::vector Vertices() const { + return {lo_, {hi_.x_, lo_.y_}, hi_, {lo_.x_, hi_.y_}}; } + + // Get edges of the box in a counter-clockwise order + std::vector Edges() const { + std::vector edges; + edges.reserve(4); + + std::vector v = Vertices(); + edges.emplace_back(v[0], v[1]); + edges.emplace_back(v[1], v[2]); + edges.emplace_back(v[2], v[3]); + edges.emplace_back(v[3], v[0]); + + return edges; + } + + bool Contains(const Vector2 &coord) const { + return (coord.x_ >= lo_.x_ && coord.x_ <= hi_.x_ && coord.y_ >= lo_.y_ && coord.y_ <= hi_.y_); + } + + private: + Vector2 lo_; + Vector2 hi_; + Vector2 size_; }; #endif // ZM_BOX_H diff --git a/src/zm_buffer.cpp b/src/zm_buffer.cpp index b4b970eb3..4cb580aa4 100644 --- a/src/zm_buffer.cpp +++ b/src/zm_buffer.cpp @@ -74,11 +74,13 @@ int Buffer::read_into(int sd, unsigned int bytes) { return bytes_read; } -int Buffer::read_into(int sd, unsigned int bytes, struct timeval timeout) { +int Buffer::read_into(int sd, unsigned int bytes, Microseconds timeout) { fd_set set; FD_ZERO(&set); /* clear the set */ FD_SET(sd, &set); /* add our file descriptor to the set */ - int rv = select(sd + 1, &set, NULL, NULL, &timeout); + timeval timeout_tv = zm::chrono::duration_cast(timeout); + + int rv = select(sd + 1, &set, nullptr, nullptr, &timeout_tv); if (rv == -1) { Error("Error %d %s from select", errno, strerror(errno)); return rv; @@ -86,5 +88,6 @@ int Buffer::read_into(int sd, unsigned int bytes, struct timeval timeout) { Debug(1, "timeout"); /* a timeout occured */ return 0; } + return read_into(sd, bytes); } diff --git a/src/zm_buffer.h b/src/zm_buffer.h index c2b509e13..fddd2f3cc 100644 --- a/src/zm_buffer.h +++ b/src/zm_buffer.h @@ -21,6 +21,7 @@ #define ZM_BUFFER_H #include "zm_logger.h" +#include "zm_time.h" #include class Buffer { @@ -187,7 +188,7 @@ class Buffer { return static_cast(mSize); } int read_into(int sd, unsigned int bytes); - int read_into(int sd, unsigned int bytes, struct timeval timeout); + int read_into(int sd, unsigned int bytes, Microseconds timeout); }; #endif // ZM_BUFFER_H diff --git a/src/zm_camera.cpp b/src/zm_camera.cpp index c56cc34c7..a73961ed9 100644 --- a/src/zm_camera.cpp +++ b/src/zm_camera.cpp @@ -90,20 +90,12 @@ AVStream *Camera::getVideoStream() { mVideoStream = avformat_new_stream(mFormatContext, nullptr); if ( mVideoStream ) { mVideoStream->time_base = (AVRational){1, 1000000}; // microseconds as base frame rate -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) mVideoStream->codecpar->width = width; mVideoStream->codecpar->height = height; mVideoStream->codecpar->format = GetFFMPEGPixelFormat(colours, subpixelorder); mVideoStream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; mVideoStream->codecpar->codec_id = AV_CODEC_ID_NONE; - Debug(1, "Allocating avstream %p %p %d", mVideoStream, mVideoStream->codecpar, mVideoStream->codecpar->codec_id); -#else - mVideoStream->codec->width = width; - mVideoStream->codec->height = height; - mVideoStream->codec->pix_fmt = GetFFMPEGPixelFormat(colours, subpixelorder); - mVideoStream->codec->codec_type = AVMEDIA_TYPE_VIDEO; - mVideoStream->codec->codec_id = AV_CODEC_ID_NONE; -#endif + Debug(1, "Allocating avstream %p %p %d", mVideoStream, mVideoStream->codecpar, mVideoStream->codecpar->codec_id); } else { Error("Can't create video stream"); } diff --git a/src/zm_camera.h b/src/zm_camera.h index fb117dcfb..2caa8312d 100644 --- a/src/zm_camera.h +++ b/src/zm_camera.h @@ -24,6 +24,8 @@ #include #include +#include + class Monitor; class ZMPacket; @@ -97,20 +99,8 @@ public: unsigned int Pixels() const { return pixels; } unsigned long long ImageSize() const { return imagesize; } unsigned int Bytes() const { return bytes; }; - int getFrequency() { -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - return mAudioStream ? mAudioStream->codecpar->sample_rate : -1; -#else - return mAudioStream ? mAudioStream->codec->sample_rate : -1; -#endif - } - int getChannels() { -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - return mAudioStream ? mAudioStream->codecpar->channels : -1; -#else - return mAudioStream ? mAudioStream->codec->channels : -1; -#endif - } + int getFrequency() { return mAudioStream ? mAudioStream->codecpar->sample_rate : -1; } + int getChannels() { return mAudioStream ? mAudioStream->codecpar->channels : -1; } virtual int Brightness( int/*p_brightness*/=-1 ) { return -1; } virtual int Hue( int/*p_hue*/=-1 ) { return -1; } @@ -133,7 +123,7 @@ public: virtual int PrimeCapture() { return 0; } virtual int PreCapture() = 0; - virtual int Capture(ZMPacket &p) = 0; + virtual int Capture(std::shared_ptr &p) = 0; virtual int PostCapture() = 0; virtual int Close() = 0; }; diff --git a/src/zm_comms.cpp b/src/zm_comms.cpp index 7f933d16f..2f57f9506 100644 --- a/src/zm_comms.cpp +++ b/src/zm_comms.cpp @@ -34,9 +34,9 @@ #include // define FIONREAD #endif -int ZM::CommsBase::readV(int iovcnt, /* const void *, int, */ ...) { +int zm::CommsBase::readV(int iovcnt, /* const void *, int, */ ...) { va_list arg_ptr; - iovec iov[iovcnt]; + std::vector iov(iovcnt); va_start(arg_ptr, iovcnt); for (int i = 0; i < iovcnt; i++) { @@ -45,16 +45,16 @@ int ZM::CommsBase::readV(int iovcnt, /* const void *, int, */ ...) { } va_end(arg_ptr); - int nBytes = ::readv(mRd, iov, iovcnt); + int nBytes = ::readv(mRd, iov.data(), iovcnt); if (nBytes < 0) { Debug(1, "Readv of %d buffers max on rd %d failed: %s", iovcnt, mRd, strerror(errno)); } return nBytes; } -int ZM::CommsBase::writeV(int iovcnt, /* const void *, int, */ ...) { +int zm::CommsBase::writeV(int iovcnt, /* const void *, int, */ ...) { va_list arg_ptr; - iovec iov[iovcnt]; + std::vector iov(iovcnt); va_start(arg_ptr, iovcnt); for (int i = 0; i < iovcnt; i++) { @@ -63,14 +63,14 @@ int ZM::CommsBase::writeV(int iovcnt, /* const void *, int, */ ...) { } va_end(arg_ptr); - ssize_t nBytes = ::writev(mWd, iov, iovcnt); + ssize_t nBytes = ::writev(mWd, iov.data(), iovcnt); if (nBytes < 0) { Debug(1, "Writev of %d buffers on wd %d failed: %s", iovcnt, mWd, strerror(errno)); } return nBytes; } -bool ZM::Pipe::open() { +bool zm::Pipe::open() { if (::pipe(mFd) < 0) { Error("pipe(), errno = %d, error = %s", errno, strerror(errno)); return false; @@ -79,7 +79,7 @@ bool ZM::Pipe::open() { return true; } -bool ZM::Pipe::close() { +bool zm::Pipe::close() { if (mFd[0] > -1) { ::close(mFd[0]); } @@ -91,7 +91,7 @@ bool ZM::Pipe::close() { return true; } -bool ZM::Pipe::setBlocking(bool blocking) { +bool zm::Pipe::setBlocking(bool blocking) { int flags; /* Now set it for non-blocking I/O */ @@ -112,7 +112,7 @@ bool ZM::Pipe::setBlocking(bool blocking) { return true; } -ZM::SockAddr *ZM::SockAddr::newSockAddr(const sockaddr &addr, socklen_t len) { +zm::SockAddr *zm::SockAddr::newSockAddr(const sockaddr &addr, socklen_t len) { if ((addr.sa_family == AF_INET) && (len == SockAddrInet::addrSize())) { return new SockAddrInet((const sockaddr_in *) &addr); } else if ((addr.sa_family == AF_UNIX) && (len == SockAddrUnix::addrSize())) { @@ -123,7 +123,7 @@ ZM::SockAddr *ZM::SockAddr::newSockAddr(const sockaddr &addr, socklen_t len) { return nullptr; } -ZM::SockAddr *ZM::SockAddr::newSockAddr(const SockAddr *addr) { +zm::SockAddr *zm::SockAddr::newSockAddr(const SockAddr *addr) { if (!addr) { return nullptr; } @@ -138,7 +138,7 @@ ZM::SockAddr *ZM::SockAddr::newSockAddr(const SockAddr *addr) { return nullptr; } -bool ZM::SockAddrInet::resolve(const char *host, const char *serv, const char *proto) { +bool zm::SockAddrInet::resolve(const char *host, const char *serv, const char *proto) { memset(&mAddrIn, 0, sizeof(mAddrIn)); hostent *hostent = nullptr; @@ -160,7 +160,7 @@ bool ZM::SockAddrInet::resolve(const char *host, const char *serv, const char *p return true; } -bool ZM::SockAddrInet::resolve(const char *host, int port, const char *proto) { +bool zm::SockAddrInet::resolve(const char *host, int port, const char *proto) { memset(&mAddrIn, 0, sizeof(mAddrIn)); hostent *hostent = nullptr; @@ -175,7 +175,7 @@ bool ZM::SockAddrInet::resolve(const char *host, int port, const char *proto) { return true; } -bool ZM::SockAddrInet::resolve(const char *serv, const char *proto) { +bool zm::SockAddrInet::resolve(const char *serv, const char *proto) { memset(&mAddrIn, 0, sizeof(mAddrIn)); servent *servent = nullptr; @@ -191,7 +191,7 @@ bool ZM::SockAddrInet::resolve(const char *serv, const char *proto) { return true; } -bool ZM::SockAddrInet::resolve(int port, const char *proto) { +bool zm::SockAddrInet::resolve(int port, const char *proto) { memset(&mAddrIn, 0, sizeof(mAddrIn)); mAddrIn.sin_port = htons(port); @@ -201,16 +201,17 @@ bool ZM::SockAddrInet::resolve(int port, const char *proto) { return true; } -bool ZM::SockAddrUnix::resolve(const char *path, const char *proto) { +bool zm::SockAddrUnix::resolve(const char *path, const char *proto) { memset(&mAddrUn, 0, sizeof(mAddrUn)); strncpy(mAddrUn.sun_path, path, sizeof(mAddrUn.sun_path)); + mAddrUn.sun_path[sizeof(mAddrUn.sun_path) - 1] = '\0'; mAddrUn.sun_family = AF_UNIX; return true; } -bool ZM::Socket::socket() { +bool zm::Socket::socket() { if (mSd >= 0) { return true; } @@ -229,7 +230,7 @@ bool ZM::Socket::socket() { return true; } -bool ZM::Socket::connect() { +bool zm::Socket::connect() { if (!socket()) { return false; } @@ -244,7 +245,7 @@ bool ZM::Socket::connect() { return true; } -bool ZM::Socket::bind() { +bool zm::Socket::bind() { if (!socket()) { return false; } @@ -257,7 +258,7 @@ bool ZM::Socket::bind() { return true; } -bool ZM::Socket::listen() { +bool zm::Socket::listen() { if (::listen(mSd, SOMAXCONN) == -1) { Error("listen(), errno = %d, error = %s", errno, strerror(errno)); close(); @@ -268,7 +269,7 @@ bool ZM::Socket::listen() { return true; } -bool ZM::Socket::accept() { +bool zm::Socket::accept() { sockaddr rem_addr = {}; socklen_t rem_addr_size = getAddrSize(); @@ -286,7 +287,7 @@ bool ZM::Socket::accept() { return true; } -bool ZM::Socket::accept(int &newSd) { +bool zm::Socket::accept(int &newSd) { sockaddr rem_addr = {}; socklen_t rem_addr_size = getAddrSize(); @@ -300,7 +301,7 @@ bool ZM::Socket::accept(int &newSd) { return true; } -bool ZM::Socket::close() { +bool zm::Socket::close() { if (mSd > -1) { ::close(mSd); } @@ -310,7 +311,7 @@ bool ZM::Socket::close() { return true; } -int ZM::Socket::bytesToRead() const { +int zm::Socket::bytesToRead() const { int bytes_to_read = 0; if (ioctl(mSd, FIONREAD, &bytes_to_read) < 0) { @@ -320,7 +321,7 @@ int ZM::Socket::bytesToRead() const { return bytes_to_read; } -bool ZM::Socket::getBlocking(bool &blocking) { +bool zm::Socket::getBlocking(bool &blocking) { int flags; if ((flags = fcntl(mSd, F_GETFL)) < 0) { @@ -331,7 +332,7 @@ bool ZM::Socket::getBlocking(bool &blocking) { return true; } -bool ZM::Socket::setBlocking(bool blocking) { +bool zm::Socket::setBlocking(bool blocking) { int flags; /* Now set it for non-blocking I/O */ @@ -352,7 +353,7 @@ bool ZM::Socket::setBlocking(bool blocking) { return true; } -int ZM::Socket::getSendBufferSize(int &buffersize) const { +int zm::Socket::getSendBufferSize(int &buffersize) const { socklen_t optlen = sizeof(buffersize); if (getsockopt(mSd, SOL_SOCKET, SO_SNDBUF, &buffersize, &optlen) < 0) { Error("getsockopt(), errno = %d, error = %s", errno, strerror(errno)); @@ -361,7 +362,7 @@ int ZM::Socket::getSendBufferSize(int &buffersize) const { return buffersize; } -int ZM::Socket::getRecvBufferSize(int &buffersize) const { +int zm::Socket::getRecvBufferSize(int &buffersize) const { socklen_t optlen = sizeof(buffersize); if (getsockopt(mSd, SOL_SOCKET, SO_RCVBUF, &buffersize, &optlen) < 0) { Error("getsockopt(), errno = %d, error = %s", errno, strerror(errno)); @@ -370,7 +371,7 @@ int ZM::Socket::getRecvBufferSize(int &buffersize) const { return buffersize; } -bool ZM::Socket::setSendBufferSize(int buffersize) { +bool zm::Socket::setSendBufferSize(int buffersize) { if (setsockopt(mSd, SOL_SOCKET, SO_SNDBUF, (char *) &buffersize, sizeof(buffersize)) < 0) { Error("setsockopt(), errno = %d, error = %s", errno, strerror(errno)); return false; @@ -378,7 +379,7 @@ bool ZM::Socket::setSendBufferSize(int buffersize) { return true; } -bool ZM::Socket::setRecvBufferSize(int buffersize) { +bool zm::Socket::setRecvBufferSize(int buffersize) { if (setsockopt(mSd, SOL_SOCKET, SO_RCVBUF, (char *) &buffersize, sizeof(buffersize)) < 0) { Error("setsockopt(), errno = %d, error = %s", errno, strerror(errno)); return false; @@ -386,7 +387,7 @@ bool ZM::Socket::setRecvBufferSize(int buffersize) { return true; } -bool ZM::Socket::getRouting(bool &route) const { +bool zm::Socket::getRouting(bool &route) const { int dontRoute; socklen_t optlen = sizeof(dontRoute); if (getsockopt(mSd, SOL_SOCKET, SO_DONTROUTE, &dontRoute, &optlen) < 0) { @@ -397,7 +398,7 @@ bool ZM::Socket::getRouting(bool &route) const { return true; } -bool ZM::Socket::setRouting(bool route) { +bool zm::Socket::setRouting(bool route) { int dontRoute = !route; if (setsockopt(mSd, SOL_SOCKET, SO_DONTROUTE, (char *) &dontRoute, sizeof(dontRoute)) < 0) { Error("setsockopt(), errno = %d, error = %s", errno, strerror(errno)); @@ -406,7 +407,7 @@ bool ZM::Socket::setRouting(bool route) { return true; } -bool ZM::Socket::getNoDelay(bool &nodelay) const { +bool zm::Socket::getNoDelay(bool &nodelay) const { int int_nodelay; socklen_t optlen = sizeof(int_nodelay); if (getsockopt(mSd, IPPROTO_TCP, TCP_NODELAY, &int_nodelay, &optlen) < 0) { @@ -417,7 +418,7 @@ bool ZM::Socket::getNoDelay(bool &nodelay) const { return true; } -bool ZM::Socket::setNoDelay(bool nodelay) { +bool zm::Socket::setNoDelay(bool nodelay) { int int_nodelay = nodelay; if (setsockopt(mSd, IPPROTO_TCP, TCP_NODELAY, (char *) &int_nodelay, sizeof(int_nodelay)) < 0) { @@ -427,7 +428,7 @@ bool ZM::Socket::setNoDelay(bool nodelay) { return true; } -bool ZM::InetSocket::connect(const char *host, const char *serv) { +bool zm::InetSocket::connect(const char *host, const char *serv) { addrinfo hints; addrinfo *result, *rp; int s; @@ -499,14 +500,14 @@ bool ZM::InetSocket::connect(const char *host, const char *serv) { return true; } -bool ZM::InetSocket::connect(const char *host, int port) { +bool zm::InetSocket::connect(const char *host, int port) { char serv[8]; snprintf(serv, sizeof(serv), "%d", port); return connect(host, serv); } -bool ZM::InetSocket::bind(const char *host, const char *serv) { +bool zm::InetSocket::bind(const char *host, const char *serv) { addrinfo hints; memset(&hints, 0, sizeof(addrinfo)); @@ -564,33 +565,33 @@ bool ZM::InetSocket::bind(const char *host, const char *serv) { return true; } -bool ZM::InetSocket::bind(const char *serv) { +bool zm::InetSocket::bind(const char *serv) { return bind(nullptr, serv); } -bool ZM::InetSocket::bind(const char *host, int port) { +bool zm::InetSocket::bind(const char *host, int port) { char serv[8]; snprintf(serv, sizeof(serv), "%d", port); return bind(host, serv); } -bool ZM::InetSocket::bind(int port) { +bool zm::InetSocket::bind(int port) { char serv[8]; snprintf(serv, sizeof(serv), "%d", port); return bind(nullptr, serv); } -bool ZM::TcpInetServer::listen() { +bool zm::TcpInetServer::listen() { return Socket::listen(); } -bool ZM::TcpInetServer::accept() { +bool zm::TcpInetServer::accept() { return Socket::accept(); } -bool ZM::TcpInetServer::accept(TcpInetSocket *&newSocket) { +bool zm::TcpInetServer::accept(TcpInetSocket *&newSocket) { int newSd = -1; newSocket = nullptr; @@ -602,7 +603,7 @@ bool ZM::TcpInetServer::accept(TcpInetSocket *&newSocket) { return true; } -bool ZM::TcpUnixServer::accept(TcpUnixSocket *&newSocket) { +bool zm::TcpUnixServer::accept(TcpUnixSocket *&newSocket) { int newSd = -1; newSocket = nullptr; @@ -614,28 +615,16 @@ bool ZM::TcpUnixServer::accept(TcpUnixSocket *&newSocket) { return true; } -void ZM::Select::setTimeout(int timeout) { - mTimeout.tv_sec = timeout; - mTimeout.tv_usec = 0; - mHasTimeout = true; -} - -void ZM::Select::setTimeout(double timeout) { - mTimeout.tv_sec = int(timeout); - mTimeout.tv_usec = suseconds_t((timeout - mTimeout.tv_sec) * 1000000.0); - mHasTimeout = true; -} - -void ZM::Select::setTimeout(timeval timeout) { +void zm::Select::setTimeout(Microseconds timeout) { mTimeout = timeout; mHasTimeout = true; } -void ZM::Select::clearTimeout() { +void zm::Select::clearTimeout() { mHasTimeout = false; } -void ZM::Select::calcMaxFd() { +void zm::Select::calcMaxFd() { mMaxFd = -1; for (CommsSet::iterator iter = mReaders.begin(); iter != mReaders.end(); ++iter) { if ((*iter)->getMaxDesc() > mMaxFd) @@ -647,7 +636,7 @@ void ZM::Select::calcMaxFd() { } } -bool ZM::Select::addReader(CommsBase *comms) { +bool zm::Select::addReader(CommsBase *comms) { if (!comms->isOpen()) { Error("Unable to add closed reader"); return false; @@ -661,7 +650,7 @@ bool ZM::Select::addReader(CommsBase *comms) { return result.second; } -bool ZM::Select::deleteReader(CommsBase *comms) { +bool zm::Select::deleteReader(CommsBase *comms) { if (!comms->isOpen()) { Error("Unable to add closed reader"); return false; @@ -673,12 +662,12 @@ bool ZM::Select::deleteReader(CommsBase *comms) { return false; } -void ZM::Select::clearReaders() { +void zm::Select::clearReaders() { mReaders.clear(); mMaxFd = -1; } -bool ZM::Select::addWriter(CommsBase *comms) { +bool zm::Select::addWriter(CommsBase *comms) { std::pair result = mWriters.insert(comms); if (result.second) { if (comms->getMaxDesc() > mMaxFd) { @@ -688,7 +677,7 @@ bool ZM::Select::addWriter(CommsBase *comms) { return result.second; } -bool ZM::Select::deleteWriter(CommsBase *comms) { +bool zm::Select::deleteWriter(CommsBase *comms) { if (mWriters.erase(comms)) { calcMaxFd(); return true; @@ -696,13 +685,13 @@ bool ZM::Select::deleteWriter(CommsBase *comms) { return false; } -void ZM::Select::clearWriters() { +void zm::Select::clearWriters() { mWriters.clear(); mMaxFd = -1; } -int ZM::Select::wait() { - timeval tempTimeout = mTimeout; +int zm::Select::wait() { + timeval tempTimeout = zm::chrono::duration_cast(mTimeout); timeval *selectTimeout = mHasTimeout ? &tempTimeout : nullptr; fd_set rfds; diff --git a/src/zm_comms.h b/src/zm_comms.h index 9126e5373..7e7329d5d 100644 --- a/src/zm_comms.h +++ b/src/zm_comms.h @@ -22,6 +22,7 @@ #include "zm_exception.h" #include "zm_logger.h" +#include "zm_time.h" #include #include #include @@ -35,7 +36,7 @@ #include #endif -namespace ZM { +namespace zm { class CommsException : public Exception { public: @@ -243,27 +244,27 @@ class Socket : public CommsBase { return nBytes; } - virtual int recv(std::string &msg) const { - char buffer[msg.capacity()]; - int nBytes = 0; - if ((nBytes = ::recv(mSd, buffer, sizeof(buffer), 0)) < 0) { - Debug(1, "Recv of %zd bytes max to string on sd %d failed: %s", sizeof(buffer), mSd, strerror(errno)); + virtual ssize_t recv(std::string &msg) const { + std::vector buffer(msg.capacity()); + ssize_t nBytes; + if ((nBytes = ::recv(mSd, buffer.data(), buffer.size(), 0)) < 0) { + Debug(1, "Recv of %zd bytes max to string on sd %d failed: %s", msg.size(), mSd, strerror(errno)); return nBytes; } buffer[nBytes] = '\0'; - msg = buffer; + msg = {buffer.begin(), buffer.begin() + nBytes}; return nBytes; } - virtual int recv(std::string &msg, size_t maxLen) const { - char buffer[maxLen]; - int nBytes = 0; - if ((nBytes = ::recv(mSd, buffer, sizeof(buffer), 0)) < 0) { + virtual ssize_t recv(std::string &msg, size_t maxLen) const { + std::vector buffer(maxLen); + ssize_t nBytes; + if ((nBytes = ::recv(mSd, buffer.data(), buffer.size(), 0)) < 0) { Debug(1, "Recv of %zd bytes max to string on sd %d failed: %s", maxLen, mSd, strerror(errno)); return nBytes; } buffer[nBytes] = '\0'; - msg = buffer; + msg = {buffer.begin(), buffer.begin() + nBytes}; return nBytes; } @@ -560,13 +561,9 @@ class Select { typedef std::vector CommsList; Select() : mHasTimeout(false), mMaxFd(-1) {} - explicit Select(timeval timeout) : mMaxFd(-1) { setTimeout(timeout); } - explicit Select(int timeout) : mMaxFd(-1) { setTimeout(timeout); } - explicit Select(double timeout) : mMaxFd(-1) { setTimeout(timeout); } + explicit Select(Microseconds timeout) : mMaxFd(-1) { setTimeout(timeout); } - void setTimeout(int timeout); - void setTimeout(double timeout); - void setTimeout(timeval timeout); + void setTimeout(Microseconds timeout); void clearTimeout(); void calcMaxFd(); @@ -590,7 +587,7 @@ class Select { CommsList mReadable; CommsList mWriteable; bool mHasTimeout; - timeval mTimeout; + Microseconds mTimeout; int mMaxFd; }; diff --git a/src/zm_config.cpp b/src/zm_config.cpp index 97e2f8c2a..e2f0e53f0 100644 --- a/src/zm_config.cpp +++ b/src/zm_config.cpp @@ -39,16 +39,15 @@ void zmLoadStaticConfig() { // update the Config hash with those values DIR *configSubFolder = opendir(ZM_CONFIG_SUBDIR); if (configSubFolder) { // subfolder exists and is readable - char glob_pattern[PATH_MAX] = ""; - snprintf(glob_pattern, sizeof(glob_pattern), "%s/*.conf", ZM_CONFIG_SUBDIR); + std::string glob_pattern = stringtf("%s/*.conf", ZM_CONFIG_SUBDIR); glob_t pglob; - int glob_status = glob(glob_pattern, 0, nullptr, &pglob); + int glob_status = glob(glob_pattern.c_str(), 0, nullptr, &pglob); if (glob_status != 0) { if (glob_status < 0) { - Error("Can't glob '%s': %s", glob_pattern, strerror(errno)); + Error("Can't glob '%s': %s", glob_pattern.c_str(), strerror(errno)); } else { - Debug(1, "Can't glob '%s': %d", glob_pattern, glob_status); + Debug(1, "Can't glob '%s': %d", glob_pattern.c_str(), glob_status); } } else { for (unsigned int i = 0; i < pglob.gl_pathc; i++) { @@ -75,7 +74,7 @@ void zmLoadDBConfig() { std::string sql = stringtf("SELECT `Id` FROM `Servers` WHERE `Name`='%s'", staticConfig.SERVER_NAME.c_str()); zmDbRow dbrow; - if (dbrow.fetch(sql.c_str())) { + if (dbrow.fetch(sql)) { staticConfig.SERVER_ID = atoi(dbrow[0]); } else { Fatal("Can't get ServerId for Server %s", staticConfig.SERVER_NAME.c_str()); @@ -87,7 +86,7 @@ void zmLoadDBConfig() { std::string sql = stringtf("SELECT `Name` FROM `Servers` WHERE `Id`='%d'", staticConfig.SERVER_ID); zmDbRow dbrow; - if (dbrow.fetch(sql.c_str())) { + if (dbrow.fetch(sql)) { staticConfig.SERVER_NAME = std::string(dbrow[0]); } else { Fatal("Can't get ServerName for Server ID %d", staticConfig.SERVER_ID); @@ -100,13 +99,10 @@ void zmLoadDBConfig() { } } - snprintf(staticConfig.capture_file_format, sizeof(staticConfig.capture_file_format), "%%s/%%0%dd-capture.jpg", - config.event_image_digits); - snprintf(staticConfig.analyse_file_format, sizeof(staticConfig.analyse_file_format), "%%s/%%0%dd-analyse.jpg", - config.event_image_digits); - snprintf(staticConfig.general_file_format, sizeof(staticConfig.general_file_format), "%%s/%%0%dd-%%s", - config.event_image_digits); - snprintf(staticConfig.video_file_format, sizeof(staticConfig.video_file_format), "%%s/%%s"); + staticConfig.capture_file_format = stringtf("%%s/%%0%dd-capture.jpg", config.event_image_digits); + staticConfig.analyse_file_format = stringtf("%%s/%%0%dd-analyse.jpg", config.event_image_digits); + staticConfig.general_file_format = stringtf("%%s/%%0%dd-%%s", config.event_image_digits); + staticConfig.video_file_format = "%s/%s"; } void process_configfile(char const *configFile) { diff --git a/src/zm_config.h b/src/zm_config.h index daea8e14c..e2ec9781a 100644 --- a/src/zm_config.h +++ b/src/zm_config.h @@ -25,14 +25,6 @@ #include "zm_config_defines.h" #include -#if !defined(PATH_MAX) -#define PATH_MAX 1024 -#endif - -#ifdef HAVE_LIBAVFORMAT -#define ZM_HAS_FFMPEG 1 -#endif // HAVE_LIBAVFORMAT - #define ZM_MAX_IMAGE_WIDTH 2048 // The largest image we imagine ever handling #define ZM_MAX_IMAGE_HEIGHT 1536 // The largest image we imagine ever handling #define ZM_MAX_IMAGE_COLOURS 4 // The largest image we imagine ever handling @@ -42,11 +34,6 @@ #define ZM_SCALE_BASE 100 // The factor by which we bump up 'scale' to simulate FP #define ZM_RATE_BASE 100 // The factor by which we bump up 'rate' to simulate FP -#define ZM_SQL_BATCH_SIZE 50 // Limit the size of multi-row SQL statements -#define ZM_SQL_SML_BUFSIZ 256 // Size of SQL buffer -#define ZM_SQL_MED_BUFSIZ 1024 // Size of SQL buffer -#define ZM_SQL_LGE_BUFSIZ 8192 // Size of SQL buffer - #define ZM_NETWORK_BUFSIZ 32768 // Size of network buffer #define ZM_MAX_FPS 30 // The maximum frame rate we expect to handle @@ -78,10 +65,10 @@ struct StaticConfig { std::string PATH_LOGS; std::string PATH_SWAP; std::string PATH_ARP; - char capture_file_format[PATH_MAX]; - char analyse_file_format[PATH_MAX]; - char general_file_format[PATH_MAX]; - char video_file_format[PATH_MAX]; + std::string capture_file_format; + std::string analyse_file_format; + std::string general_file_format; + std::string video_file_format; }; extern StaticConfig staticConfig; diff --git a/src/zm_config_data.h.in b/src/zm_config_data.h.in index 1209dcbf5..f16135e92 100644 --- a/src/zm_config_data.h.in +++ b/src/zm_config_data.h.in @@ -24,8 +24,4 @@ #define ZM_CONFIG_SUBDIR "@ZM_CONFIG_SUBDIR@" // Path to the ZoneMinder config subfolder #define ZM_VERSION "@VERSION@" // ZoneMinder Version -#define ZM_HAS_V4L1 @ZM_HAS_V4L1@ -#define ZM_HAS_V4L2 @ZM_HAS_V4L2@ -#define ZM_HAS_V4L @ZM_HAS_V4L@ - #endif // ZM_CONFIG_DATA_H diff --git a/src/zm_coord.cpp b/src/zm_coord.cpp deleted file mode 100644 index 0b7ab0e71..000000000 --- a/src/zm_coord.cpp +++ /dev/null @@ -1,22 +0,0 @@ -// -// ZoneMinder Coordinate 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_coord.h" - -// This section deliberately left blank diff --git a/src/zm_coord.h b/src/zm_coord.h deleted file mode 100644 index b7e8fd046..000000000 --- a/src/zm_coord.h +++ /dev/null @@ -1,64 +0,0 @@ -// -// ZoneMinder Coordinate Class Interface, $Date$, $Revision$ -// Copyright (C) 2001-2008 Philip Coombes -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -// - -#ifndef ZM_COORD_H -#define ZM_COORD_H - -#include "zm_define.h" - -// -// Class used for storing an x,y pair, i.e. a coordinate -// -class Coord { -private: - int x, y; - -public: - inline Coord() : x(0), y(0) { } - inline Coord( int p_x, int p_y ) : x(p_x), y(p_y) { } - inline Coord( const Coord &p_coord ) : x(p_coord.x), y(p_coord.y) { } - inline Coord &operator =( const Coord &coord ) { - x = coord.x; - y = coord.y; - return *this; - } - inline int &X(int p_x) { x=p_x; return x; } - inline const int &X() const { return x; } - inline int &Y(int p_y) { y=p_y; return y; } - inline const int &Y() const { return y; } - - inline static Coord Range( const Coord &coord1, const Coord &coord2 ) { - Coord result( (coord1.x-coord2.x)+1, (coord1.y-coord2.y)+1 ); - return result; - } - - inline bool operator==( const Coord &coord ) const { return( x == coord.x && y == coord.y ); } - inline bool operator!=( const Coord &coord ) const { return( x != coord.x || y != coord.y ); } - inline bool operator>( const Coord &coord ) const { return( x > coord.x && y > coord.y ); } - inline bool operator>=( const Coord &coord ) const { return( !(operator<(coord)) ); } - inline bool operator<( const Coord &coord ) const { return( x < coord.x && y < coord.y ); } - inline bool operator<=( const Coord &coord ) const { return( !(operator>(coord)) ); } - inline Coord &operator+=( const Coord &coord ) { x += coord.x; y += coord.y; return( *this ); } - inline Coord &operator-=( const Coord &coord ) { x -= coord.x; y -= coord.y; return( *this ); } - - inline friend Coord operator+( const Coord &coord1, const Coord &coord2 ) { Coord result( coord1 ); result += coord2; return( result ); } - inline friend Coord operator-( const Coord &coord1, const Coord &coord2 ) { Coord result( coord1 ); result -= coord2; return( result ); } -}; - -#endif // ZM_COORD_H diff --git a/src/zm_crypt.cpp b/src/zm_crypt.cpp index 583f5d8c2..5f4139b61 100644 --- a/src/zm_crypt.cpp +++ b/src/zm_crypt.cpp @@ -1,6 +1,7 @@ #include "zm_crypt.h" #include "zm_logger.h" +#include "zm_utils.h" #include "BCrypt.hpp" #include #include @@ -11,13 +12,6 @@ #include #endif -#if HAVE_LIBCRYPTO -#include -#elif HAVE_GNUTLS_GNUTLS_H -#include -#include -#endif - // returns username if valid, "" if not #if HAVE_LIBJWT std::pair verifyToken(std::string jwt_token_str, std::string key) { @@ -135,6 +129,8 @@ std::pair verifyToken(std::string jwt_token_str, std #endif // HAVE_LIBJWT bool verifyPassword(const char *username, const char *input_password, const char *db_password_hash) { + using namespace zm::crypto; + bool password_correct = false; if ( strlen(db_password_hash) < 4 ) { // actually, shoud be more, but this is min. for next code @@ -143,47 +139,13 @@ bool verifyPassword(const char *username, const char *input_password, const char } if ( db_password_hash[0] == '*' ) { // MYSQL PASSWORD - Debug(1, "%s is using an MD5 encoded password", username); - - #ifndef SHA_DIGEST_LENGTH - #define SHA_DIGEST_LENGTH 20 - #endif - -#if HAVE_LIBCRYPTO - unsigned char digest_interim[SHA_DIGEST_LENGTH]; - unsigned char digest_final[SHA_DIGEST_LENGTH]; - SHA_CTX ctx1, ctx2; - - //get first iteration - SHA1_Init(&ctx1); - SHA1_Update(&ctx1, input_password, strlen(input_password)); - SHA1_Final(digest_interim, &ctx1); + Debug(1, "%s is using an SHA1 encoded password", username); - //2nd iteration - SHA1_Init(&ctx2); - SHA1_Update(&ctx2, digest_interim,SHA_DIGEST_LENGTH); - SHA1_Final(digest_final, &ctx2); -#elif HAVE_GNUTLS_GNUTLS_H - unsigned char digest_interim[SHA_DIGEST_LENGTH]; - unsigned char digest_final[SHA_DIGEST_LENGTH]; - //get first iteration - gnutls_hash_fast(GNUTLS_DIG_SHA1, input_password, strlen(input_password), digest_interim); - //2nd iteration - gnutls_hash_fast(GNUTLS_DIG_SHA1, digest_interim, SHA_DIGEST_LENGTH, digest_final); -#else - Error("Authentication Error. ZoneMinder not built with GnuTLS or Openssl"); - return false; -#endif + SHA1::Digest digest = SHA1::GetDigestOf(SHA1::GetDigestOf(input_password)); + std::string hex_digest = '*' + StringToUpper(ByteArrayToHexString(digest)); - char final_hash[SHA_DIGEST_LENGTH * 2 +2]; - final_hash[0] = '*'; - //convert to hex - for ( int i = 0; i < SHA_DIGEST_LENGTH; i++ ) - sprintf(&final_hash[i*2]+1, "%02X", (unsigned int)digest_final[i]); - final_hash[SHA_DIGEST_LENGTH *2 + 1] = 0; - - Debug(1, "Computed password_hash:%s, stored password_hash:%s", final_hash, db_password_hash); - password_correct = (strcmp(db_password_hash, final_hash)==0); + Debug(1, "Computed password_hash: %s, stored password_hash: %s", hex_digest.c_str(), db_password_hash); + password_correct = (strcmp(db_password_hash, hex_digest.c_str()) == 0); } else if ( (db_password_hash[0] == '$') && diff --git a/src/zm_crypt.h b/src/zm_crypt.h index 15dbc9332..b495dd536 100644 --- a/src/zm_crypt.h +++ b/src/zm_crypt.h @@ -20,10 +20,36 @@ #ifndef ZM_CRYPT_H #define ZM_CRYPT_H +#include "zm_config.h" +#include "zm_crypto_gnutls.h" +#include "zm_crypto_openssl.h" +#include "zm_define.h" #include -#include -bool verifyPassword( const char *username, const char *input_password, const char *db_password_hash); +bool verifyPassword(const char *username, const char *input_password, const char *db_password_hash); -std::pair verifyToken(std::string token, std::string key); -#endif // ZM_CRYPT_H \ No newline at end of file +std::pair verifyToken(std::string token, std::string key); + +namespace zm { +namespace crypto { +namespace impl { + +#if defined(HAVE_LIBGNUTLS) +template +using Hash = gnutls::GenericHashImpl; +#elif defined(HAVE_LIBOPENSSL) +template +using Hash = openssl::GenericHashImpl; +#endif +} +} +} + +namespace zm { +namespace crypto { +using MD5 = impl::Hash; +using SHA1 = impl::Hash; +} +} + +#endif // ZM_CRYPT_H diff --git a/src/zm_crypto_generics.h b/src/zm_crypto_generics.h new file mode 100644 index 000000000..9661d4b61 --- /dev/null +++ b/src/zm_crypto_generics.h @@ -0,0 +1,108 @@ +/* + * This file is part of the ZoneMinder Project. See AUTHORS file for Copyright information + * + * 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, see . + */ + +#ifndef ZONEMINDER_SRC_ZM_CRYPTO_GENERICS_H_ +#define ZONEMINDER_SRC_ZM_CRYPTO_GENERICS_H_ + +#include "zm_define.h" +#include "zm_utils.h" +#include +#include + +namespace zm { +namespace crypto { +namespace impl { + +enum class HashAlgorithms { + kMD5, + kSHA1 +}; + +template +struct HashAlgorithm; + +template<> +struct HashAlgorithm { + static constexpr size_t digest_length = 16; +}; + +template<> +struct HashAlgorithm { + static constexpr size_t digest_length = 20; +}; + +template +class GenericHash { + public: + static constexpr size_t DIGEST_LENGTH = HashAlgorithm::digest_length; + using Digest = std::array; + + static Digest GetDigestOf(uint8 const *data, size_t len) { + Impl hash; + hash.UpdateData(data, len); + hash.Finalize(); + return hash.GetDigest(); + } + + template + static Digest GetDigestOf(Ts &&... pack) { + Impl hash; + UpdateData(hash, std::forward(pack)...); + hash.Finalize(); + return hash.GetDigest(); + } + + void UpdateData(const uint8 *data, size_t length) { + static_cast(*this).DoUpdateData(data, length); + } + void UpdateData(const std::string &str) { + UpdateData(reinterpret_cast(str.c_str()), str.size()); + } + void UpdateData(const char *str) { + UpdateData(reinterpret_cast(str), strlen(str)); + } + template + void UpdateData(Container const &c) { + UpdateData(zm::data(c), zm::size(c)); + } + + void Finalize() { + static_cast(*this).DoFinalize(); + } + + const Digest &GetDigest() const { return digest_; } + + protected: + Digest digest_ = {}; + + private: + template + static void UpdateData(Impl &hash, T const &data) { + hash.UpdateData(data); + } + + template + static void UpdateData(Impl &hash, T const &data, TRest &&... rest) { + hash.UpdateData(data); + UpdateData(hash, std::forward(rest)...); + } +}; +} +} +} + +#endif //ZONEMINDER_SRC_ZM_CRYPTO_GENERICS_H_ diff --git a/src/zm_crypto_gnutls.h b/src/zm_crypto_gnutls.h new file mode 100644 index 000000000..d2c51f35d --- /dev/null +++ b/src/zm_crypto_gnutls.h @@ -0,0 +1,75 @@ +/* + * This file is part of the ZoneMinder Project. See AUTHORS file for Copyright information + * + * 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, see . + */ + +#ifndef ZONEMINDER_SRC_ZM_CRYPTO_GNUTLS_H_ +#define ZONEMINDER_SRC_ZM_CRYPTO_GNUTLS_H_ + +#ifdef HAVE_LIBGNUTLS + +#include "zm_crypto_generics.h" +#include "zm_utils.h" +#include + +namespace zm { +namespace crypto { +namespace impl { +namespace gnutls { + +template +struct HashAlgorithmMapper; + +template<> +struct HashAlgorithmMapper { + static constexpr gnutls_digest_algorithm_t algorithm = GNUTLS_DIG_MD5; +}; + +template<> +struct HashAlgorithmMapper { + static constexpr gnutls_digest_algorithm_t algorithm = GNUTLS_DIG_SHA1; +}; + +template +class GenericHashImpl : public GenericHash, Algorithm> { + public: + GenericHashImpl() { + int32 ret = gnutls_hash_init(&handle_, HashAlgorithmMapper::algorithm); + ASSERT(ret == 0); + }; + + void DoUpdateData(const uint8 *data, size_t length) { + int32 res = gnutls_hash(handle_, data, length); + ASSERT(res == 0); + } + + void DoFinalize() { + gnutls_hash_deinit(handle_, digest_.data()); + } + + private: + gnutls_hash_hd_t handle_ = {}; + + using Base = GenericHash, Algorithm>; + using Base::digest_; +}; +} +} +} +} + +#endif // HAVE_LIBGNUTLS + +#endif // ZONEMINDER_SRC_ZM_CRYPTO_GNUTLS_H_ diff --git a/src/zm_crypto_openssl.h b/src/zm_crypto_openssl.h new file mode 100644 index 000000000..667142531 --- /dev/null +++ b/src/zm_crypto_openssl.h @@ -0,0 +1,108 @@ +/* + * This file is part of the ZoneMinder Project. See AUTHORS file for Copyright information + * + * 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, see . + */ + +#ifndef ZONEMINDER_SRC_ZM_CRYPTO_OPENSSL_H_ +#define ZONEMINDER_SRC_ZM_CRYPTO_OPENSSL_H_ + +#ifdef HAVE_LIBOPENSSL + +#include "zm_crypto_generics.h" +#include "zm_utils.h" +#include + +namespace zm { +namespace crypto { +namespace impl { +namespace openssl { + +typedef EVP_MD const *(*HashCreator)(); + +template +struct HashAlgorithmMapper; + +template<> +struct HashAlgorithmMapper { +// TODO: Remove conditional once Jessie and CentOS 7 are deprecated +// This is needed since GCC 4.8 is faulty (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60199) +#if defined(__GNUC__) && __GNUC__ < 5 + static HashCreator hash_creator() { + static constexpr HashCreator creator = EVP_md5; + return creator; + } +#else + static constexpr HashCreator hash_creator = EVP_md5; +#endif +}; + +template<> +struct HashAlgorithmMapper { +// TODO: Remove conditional once Jessie and CentOS 7 are deprecated +// This is needed since GCC 4.8 is faulty (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60199) +#if defined(__GNUC__) && __GNUC__ < 5 + static HashCreator hash_creator() { + static constexpr HashCreator creator = EVP_sha1; + return creator; + } +#else + static constexpr HashCreator hash_creator = EVP_sha1; +#endif +}; + +template +class GenericHashImpl : public GenericHash, Algorithm> { + public: + GenericHashImpl() { + // TODO: Use EVP_MD_CTX_new once we drop support for Jessie and CentOS 7 (OpenSSL > 1.1.0) + ctx_ = EVP_MD_CTX_create(); +#if defined(__GNUC__) && __GNUC__ < 5 + EVP_DigestInit_ex(ctx_, HashAlgorithmMapper::hash_creator()(), nullptr); +#else + EVP_DigestInit_ex(ctx_, HashAlgorithmMapper::hash_creator(), nullptr); +#endif + }; + + ~GenericHashImpl() { + // TODO: Use EVP_MD_CTX_free once we drop support for Jessie and CentOS 7 (OpenSSL > 1.1.0) + EVP_MD_CTX_destroy(ctx_); + } + + void DoUpdateData(const uint8 *data, size_t length) { + int32 res = EVP_DigestUpdate(ctx_, data, length); + ASSERT(res == 1); + } + + void DoFinalize() { + uint32 length = 0; + int32 res = EVP_DigestFinal_ex(ctx_, digest_.data(), &length); + ASSERT(res == 1); + ASSERT(length == HashAlgorithm::digest_length); + } + + private: + EVP_MD_CTX *ctx_; + + using Base = GenericHash, Algorithm>; + using Base::digest_; +}; +} +} +} +} + +#endif // HAVE_LIBOPENSSL + +#endif // ZONEMINDER_SRC_ZM_CRYPTO_OPENSSL_H_ diff --git a/src/zm_curl_camera.cpp b/src/zm_curl_camera.cpp index a107d78eb..4c9f7c737 100644 --- a/src/zm_curl_camera.cpp +++ b/src/zm_curl_camera.cpp @@ -165,7 +165,7 @@ int cURLCamera::PreCapture() { return( 0 ); } -int cURLCamera::Capture( ZMPacket &zm_packet ) { +int cURLCamera::Capture(std::shared_ptr &zm_packet) { bool frameComplete = false; /* MODE_STREAM specific variables */ @@ -297,27 +297,27 @@ int cURLCamera::Capture( ZMPacket &zm_packet ) { need_more_data = true; } else { /* All good. decode the image */ - zm_packet.image->DecodeJpeg(databuffer.extract(frame_content_length), frame_content_length, colours, subpixelorder); + zm_packet->image->DecodeJpeg(databuffer.extract(frame_content_length), frame_content_length, colours, subpixelorder); frameComplete = true; } } /* Attempt to get more data */ - if(need_more_data) { + if (need_more_data) { nRet = pthread_cond_wait(&data_available_cond,&shareddata_mutex); - if(nRet != 0) { + if (nRet != 0) { Error("Failed waiting for available data condition variable: %s",strerror(nRet)); return -1; } need_more_data = false; } - } else if(mode == MODE_SINGLE) { + } else if (mode == MODE_SINGLE) { /* Check if we have anything */ if (!single_offsets.empty()) { - if( (single_offsets.front() > 0) && (databuffer.size() >= single_offsets.front()) ) { + if ((single_offsets.front() > 0) && (databuffer.size() >= single_offsets.front())) { /* Extract frame */ - zm_packet.image->DecodeJpeg(databuffer.extract(single_offsets.front()), single_offsets.front(), colours, subpixelorder); + zm_packet->image->DecodeJpeg(databuffer.extract(single_offsets.front()), single_offsets.front(), colours, subpixelorder); single_offsets.pop_front(); frameComplete = true; } else { diff --git a/src/zm_curl_camera.h b/src/zm_curl_camera.h index bd2266d5f..302d8f877 100644 --- a/src/zm_curl_camera.h +++ b/src/zm_curl_camera.h @@ -88,7 +88,7 @@ public: int PrimeCapture() override; int PreCapture() override; - int Capture(ZMPacket &p)override; + int Capture(std::shared_ptr &p)override; int PostCapture()override ; size_t data_callback(void *buffer, size_t size, size_t nmemb, void *userdata); diff --git a/src/zm_db.cpp b/src/zm_db.cpp index b296c571b..0d499edbd 100644 --- a/src/zm_db.cpp +++ b/src/zm_db.cpp @@ -117,25 +117,25 @@ void zmDbClose() { } } -MYSQL_RES * zmDbFetch(const char * query) { +MYSQL_RES *zmDbFetch(const std::string &query) { std::lock_guard lck(db_mutex); if (!zmDbConnected) { Error("Not connected."); return nullptr; } - if (mysql_query(&dbconn, query)) { + if (mysql_query(&dbconn, query.c_str())) { Error("Can't run query: %s", mysql_error(&dbconn)); return nullptr; } MYSQL_RES *result = mysql_store_result(&dbconn); if (!result) { - Error("Can't use query result: %s for query %s", mysql_error(&dbconn), query); + Error("Can't use query result: %s for query %s", mysql_error(&dbconn), query.c_str()); } return result; -} // end MYSQL_RES * zmDbFetch(const char * query); +} -zmDbRow *zmDbFetchOne(const char *query) { +zmDbRow *zmDbFetchOne(const std::string &query) { zmDbRow *row = new zmDbRow(); if (row->fetch(query)) { return row; @@ -144,13 +144,13 @@ zmDbRow *zmDbFetchOne(const char *query) { return nullptr; } -MYSQL_RES *zmDbRow::fetch(const char *query) { +MYSQL_RES *zmDbRow::fetch(const std::string &query) { result_set = zmDbFetch(query); if (!result_set) return result_set; int n_rows = mysql_num_rows(result_set); if (n_rows != 1) { - Error("Bogus number of lines return from query, %d returned for query %s.", n_rows, query); + Error("Bogus number of lines return from query, %d returned for query %s.", n_rows, query.c_str()); mysql_free_result(result_set); result_set = nullptr; return result_set; @@ -160,23 +160,23 @@ MYSQL_RES *zmDbRow::fetch(const char *query) { if (!row) { mysql_free_result(result_set); result_set = nullptr; - Error("Error getting row from query %s. Error is %s", query, mysql_error(&dbconn)); + Error("Error getting row from query %s. Error is %s", query.c_str(), mysql_error(&dbconn)); } else { Debug(5, "Success"); } return result_set; } -int zmDbDo(const char *query) { +int zmDbDo(const std::string &query) { std::lock_guard lck(db_mutex); if (!zmDbConnected) return 0; int rc; - while ((rc = mysql_query(&dbconn, query)) and !zm_terminate) { + while ((rc = mysql_query(&dbconn, query.c_str())) and !zm_terminate) { Logger *logger = Logger::fetch(); Logger::Level oldLevel = logger->databaseLevel(); logger->databaseLevel(Logger::NOLOG); - Error("Can't run query %s: %s", query, mysql_error(&dbconn)); + Error("Can't run query %s: %s", query.c_str(), mysql_error(&dbconn)); logger->databaseLevel(oldLevel); if ( (mysql_errno(&dbconn) != ER_LOCK_WAIT_TIMEOUT) ) { return rc; @@ -186,36 +186,36 @@ int zmDbDo(const char *query) { Logger::Level oldLevel = logger->databaseLevel(); logger->databaseLevel(Logger::NOLOG); - Debug(1, "Success running sql query %s", query); + Debug(1, "Success running sql query %s", query.c_str()); logger->databaseLevel(oldLevel); return 1; } -int zmDbDoInsert(const char *query) { +int zmDbDoInsert(const std::string &query) { std::lock_guard lck(db_mutex); if (!zmDbConnected) return 0; int rc; - while ( (rc = mysql_query(&dbconn, query)) and !zm_terminate) { - Error("Can't run query %s: %s", query, mysql_error(&dbconn)); + while ( (rc = mysql_query(&dbconn, query.c_str())) and !zm_terminate) { + Error("Can't run query %s: %s", query.c_str(), mysql_error(&dbconn)); if ( (mysql_errno(&dbconn) != ER_LOCK_WAIT_TIMEOUT) ) return 0; } int id = mysql_insert_id(&dbconn); - Debug(2, "Success running sql insert %s. Resulting id is %d", query, id); + Debug(2, "Success running sql insert %s. Resulting id is %d", query.c_str(), id); return id; } -int zmDbDoUpdate(const char *query) { +int zmDbDoUpdate(const std::string &query) { std::lock_guard lck(db_mutex); if (!zmDbConnected) return 0; int rc; - while ( (rc = mysql_query(&dbconn, query)) and !zm_terminate) { - Error("Can't run query %s: %s", query, mysql_error(&dbconn)); + while ( (rc = mysql_query(&dbconn, query.c_str())) and !zm_terminate) { + Error("Can't run query %s: %s", query.c_str(), mysql_error(&dbconn)); if ( (mysql_errno(&dbconn) != ER_LOCK_WAIT_TIMEOUT) ) return -rc; } int affected = mysql_affected_rows(&dbconn); - Debug(2, "Success running sql update %s. Rows modified %d", query, affected); + Debug(2, "Success running sql update %s. Rows modified %d", query.c_str(), affected); return affected; } @@ -249,18 +249,33 @@ void zmDbQueue::process() { if (mQueue.empty()) { mCondition.wait(lock); } - if (!mQueue.empty()) { + while (!mQueue.empty()) { std::string sql = mQueue.front(); mQueue.pop(); + // My idea for leaving the locking around each sql statement is to allow + // other db writers to get a chance lock.unlock(); - zmDbDo(sql.c_str()); + zmDbDo(sql); lock.lock(); } } } // end void zmDbQueue::process() void zmDbQueue::push(std::string &&sql) { + if (mTerminate) return; std::unique_lock lock(mMutex); mQueue.push(std::move(sql)); mCondition.notify_all(); } + +std::string zmDbEscapeString(const std::string& to_escape) { + // According to docs, size of safer_whatever must be 2 * length + 1 + // due to unicode conversions + null terminator. + std::string escaped((to_escape.length() * 2) + 1, '\0'); + + + size_t escaped_len = mysql_real_escape_string(&dbconn, &escaped[0], to_escape.c_str(), to_escape.length()); + escaped.resize(escaped_len); + + return escaped; +} diff --git a/src/zm_db.h b/src/zm_db.h index c243aa6d6..7343ac75b 100644 --- a/src/zm_db.h +++ b/src/zm_db.h @@ -38,7 +38,6 @@ class zmDbQueue { public: zmDbQueue(); ~zmDbQueue(); - void push(const char *sql) { return push(std::string(sql)); }; void push(std::string &&sql); void process(); void stop(); @@ -50,7 +49,7 @@ class zmDbRow { MYSQL_ROW row; public: zmDbRow() : result_set(nullptr), row(nullptr) { }; - MYSQL_RES *fetch(const char *query); + MYSQL_RES *fetch(const std::string &query); zmDbRow(MYSQL_RES *, MYSQL_ROW *row); ~zmDbRow(); @@ -67,15 +66,15 @@ extern zmDbQueue dbQueue; extern bool zmDbConnected; -extern bool zmDbConnected; - bool zmDbConnect(); void zmDbClose(); -int zmDbDo(const char *query); -int zmDbDoInsert(const char *query); -int zmDbDoUpdate(const char *query); +int zmDbDo(const std::string &query); +int zmDbDoInsert(const std::string &query); +int zmDbDoUpdate(const std::string &query); -MYSQL_RES * zmDbFetch(const char *query); -zmDbRow *zmDbFetchOne(const char *query); +MYSQL_RES * zmDbFetch(const std::string &query); +zmDbRow *zmDbFetchOne(const std::string &query); + +std::string zmDbEscapeString(const std::string& to_escape); #endif // ZM_DB_H diff --git a/src/zm_define.h b/src/zm_define.h index 45ace017e..2c23ab3b9 100644 --- a/src/zm_define.h +++ b/src/zm_define.h @@ -29,6 +29,7 @@ #endif #include +#include typedef std::int64_t int64; typedef std::int32_t int32; diff --git a/src/zm_event.cpp b/src/zm_event.cpp index 103320536..c3bda0a4f 100644 --- a/src/zm_event.cpp +++ b/src/zm_event.cpp @@ -32,8 +32,6 @@ #include #include -//#define USE_PREPARED_SQL 1 - const char * Event::frame_type_names[3] = { "Normal", "Bulk", "Alarm" }; #define MAX_DB_FRAMES 100 @@ -43,14 +41,14 @@ Event::PreAlarmData Event::pre_alarm_data[MAX_PRE_ALARM_FRAMES] = {}; Event::Event( Monitor *p_monitor, - struct timeval p_start_time, + SystemTimePoint p_start_time, const std::string &p_cause, const StringSetMap &p_noteSetMap ) : id(0), monitor(p_monitor), start_time(p_start_time), - end_time({0,0}), + end_time(), cause(p_cause), noteSetMap(p_noteSetMap), frames(0), @@ -72,25 +70,25 @@ Event::Event( std::string notes; createNotes(notes); - timeval now = {}; - gettimeofday(&now, nullptr); + SystemTimePoint now = std::chrono::system_clock::now(); - if ( !start_time.tv_sec ) { + if (start_time.time_since_epoch() == Seconds(0)) { Warning("Event has zero time, setting to now"); start_time = now; - } else if ( start_time.tv_sec > now.tv_sec ) { + } else if (start_time > now) { char buffer[26]; char buffer_now[26]; tm tm_info = {}; + time_t start_time_t = std::chrono::system_clock::to_time_t(start_time); + time_t now_t = std::chrono::system_clock::to_time_t(now); - localtime_r(&start_time.tv_sec, &tm_info); + localtime_r(&start_time_t, &tm_info); strftime(buffer, 26, "%Y:%m:%d %H:%M:%S", &tm_info); - localtime_r(&now.tv_sec, &tm_info); + localtime_r(&now_t, &tm_info); strftime(buffer_now, 26, "%Y:%m:%d %H:%M:%S", &tm_info); - Error("StartDateTime in the future starttime %ld.%06ld >? now %ld.%06ld difference %" PRIi64 "\nstarttime: %s\nnow: %s", - start_time.tv_sec, start_time.tv_usec, now.tv_sec, now.tv_usec, - static_cast(now.tv_sec - start_time.tv_sec), + Error("StartDateTime in the future. Difference: %" PRIi64 " s\nstarttime: %s\nnow: %s", + static_cast(std::chrono::duration_cast(now - start_time).count()), buffer, buffer_now); start_time = now; } @@ -114,7 +112,7 @@ Event::Event( "( %d, %d, 'New Event', from_unixtime( %ld ), %d, %d, '%s', '%s', %d, %d, %d, '%s', %d, '%s' )", monitor->Id(), storage->Id(), - start_time.tv_sec, + static_cast(std::chrono::system_clock::to_time_t(start_time)), monitor->Width(), monitor->Height(), cause.c_str(), @@ -127,7 +125,7 @@ Event::Event( storage->SchemeString().c_str() ); - id = zmDbDoInsert(sql.c_str()); + id = zmDbDoInsert(sql); if ( !SetPath(storage) ) { // Try another @@ -140,7 +138,7 @@ Event::Event( Debug(1, "%s", sql.c_str()); storage = nullptr; - MYSQL_RES *result = zmDbFetch(sql.c_str()); + 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])); @@ -159,7 +157,7 @@ Event::Event( if ( monitor->ServerId() ) sql += stringtf(" OR ServerId != %u", monitor->ServerId()); - result = zmDbFetch(sql.c_str()); + result = zmDbFetch(sql); if ( result ) { for ( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) { storage = new Storage(atoi(dbrow[0])); @@ -177,7 +175,7 @@ Event::Event( 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.c_str()); + zmDbDo(sql); } // end if ! setPath(Storage) Debug(1, "Using storage area at %s", path.c_str()); @@ -213,14 +211,14 @@ Event::Event( 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.c_str()); + zmDbDo(sql); } } else { sql = stringtf("UPDATE Events SET Videoed=1, DefaultVideo = '%s' WHERE Id=%" PRIu64, video_name.c_str(), id); - zmDbDo(sql.c_str()); + zmDbDo(sql); } } // end if GetOptVideoWriter -} // Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string &p_cause, const StringSetMap &p_noteSetMap, bool p_videoEvent ) +} 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. @@ -233,37 +231,36 @@ Event::~Event() { } // endtime is set in AddFrame, so SHOULD be set to the value of the last frame timestamp. - if ( !end_time.tv_sec ) { - Warning("Empty endtime for event. Should not happen. Setting to now."); - gettimeofday(&end_time, nullptr); + if (end_time.time_since_epoch() == Seconds(0)) { + Warning("Empty endtime for event. Should not happen. Setting to now."); + end_time = std::chrono::system_clock::now(); } - struct DeltaTimeval delta_time; - DELTA_TIMEVAL(delta_time, end_time, start_time, DT_PREC_2); - Debug(2, "start_time: %" PRIi64 ".% " PRIi64 " end_time: %" PRIi64 ".%" PRIi64, - static_cast(start_time.tv_sec), - static_cast(start_time.tv_usec), - static_cast(end_time.tv_sec), - static_cast(end_time.tv_usec)); - if (frame_data.size()) WriteDbFrames(); + FPSeconds delta_time = end_time - start_time; + Debug(2, "start_time: %.2f end_time: %.2f", + std::chrono::duration_cast(start_time.time_since_epoch()).count(), + std::chrono::duration_cast(end_time.time_since_epoch()).count()); - // Should not be static because we might be multi-threaded - char sql[ZM_SQL_LGE_BUFSIZ]; - snprintf(sql, sizeof(sql), - "UPDATE Events SET Name='%s%" PRIu64 "', EndDateTime = from_unixtime(%ld), Length = %s%ld.%02ld, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d WHERE Id = %" PRIu64 " AND Name='New Event'", - monitor->EventPrefix(), id, end_time.tv_sec, - delta_time.positive?"":"-", delta_time.sec, delta_time.fsec, + if (frame_data.size()){ + WriteDbFrames(); + } + + std::string sql = stringtf( + "UPDATE Events SET Name='%s%" PRIu64 "', EndDateTime = from_unixtime(%ld), Length = %.2f, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d WHERE Id = %" PRIu64 " AND Name='New Event'", + monitor->EventPrefix(), id, std::chrono::system_clock::to_time_t(end_time), + delta_time.count(), frames, alarm_frames, - tot_score, (int)(alarm_frames?(tot_score/alarm_frames):0), max_score, + tot_score, static_cast(alarm_frames ? (tot_score / alarm_frames) : 0), max_score, id); + if (!zmDbDoUpdate(sql)) { // Name might have been changed during recording, so just do the update without changing the name. - snprintf(sql, sizeof(sql), - "UPDATE Events SET EndDateTime = from_unixtime(%ld), Length = %s%ld.%02ld, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d WHERE Id = %" PRIu64, - end_time.tv_sec, - delta_time.positive?"":"-", delta_time.sec, delta_time.fsec, + sql = stringtf( + "UPDATE Events SET EndDateTime = from_unixtime(%ld), Length = %.2f, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d WHERE Id = %" PRIu64, + std::chrono::system_clock::to_time_t(end_time), + delta_time.count(), frames, alarm_frames, - tot_score, (int)(alarm_frames?(tot_score/alarm_frames):0), max_score, + tot_score, static_cast(alarm_frames ? (tot_score / alarm_frames) : 0), max_score, id); zmDbDoUpdate(sql); } // end if no changed rows due to Name change during recording @@ -283,37 +280,31 @@ void Event::createNotes(std::string ¬es) { } } // void Event::createNotes(std::string ¬es) -bool Event::WriteFrameImage( - Image *image, - struct timeval timestamp, - const char *event_file, - bool alarm_frame) const { - +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)) ? config.jpeg_alarm_file_quality : 0; // quality to use, zero is default bool rc; - if ( !config.timestamp_on_capture ) { + SystemTimePoint jpeg_timestamp = monitor->Exif() ? timestamp : SystemTimePoint(); + + if (!config.timestamp_on_capture) { // stash the image we plan to use in another pointer regardless if timestamped. // exif is only timestamp at present this switches on or off for write Image *ts_image = new Image(*image); - monitor->TimestampImage(ts_image, ×tamp); - rc = ts_image->WriteJpeg(event_file, thisquality, - (monitor->Exif() ? timestamp : (timeval){0,0})); - delete(ts_image); + monitor->TimestampImage(ts_image, timestamp); + rc = ts_image->WriteJpeg(event_file, thisquality, jpeg_timestamp); + delete ts_image; } else { - rc = image->WriteJpeg(event_file, thisquality, - (monitor->Exif() ? timestamp : (timeval){0,0})); + rc = image->WriteJpeg(event_file, thisquality, jpeg_timestamp); } return rc; -} // end Event::WriteFrameImage( Image *image, struct timeval timestamp, const char *event_file, bool alarm_frame ) +} -bool Event::WritePacket(ZMPacket &packet) { - - if ( videoStore->writePacket(&packet) < 0 ) +bool Event::WritePacket(const std::shared_ptr&packet) { + if (videoStore->writePacket(packet) < 0) return false; return true; } // bool Event::WriteFrameVideo @@ -358,76 +349,26 @@ void Event::updateNotes(const StringSetMap &newNoteSetMap) { } // end if have old notes } // end if have new notes - if ( update ) { + if (update) { std::string notes; createNotes(notes); Debug(2, "Updating notes for event %" PRIu64 ", '%s'", id, notes.c_str()); -#if USE_PREPARED_SQL - static MYSQL_STMT *stmt = 0; - char notesStr[ZM_SQL_MED_BUFSIZ] = ""; - unsigned long notesLen = 0; - - if ( !stmt ) { - const char *sql = "UPDATE `Events` SET `Notes` = ? WHERE `Id` = ?"; - - stmt = mysql_stmt_init(&dbconn); - if ( mysql_stmt_prepare(stmt, sql, strlen(sql)) ) { - Fatal("Unable to prepare sql '%s': %s", sql, mysql_stmt_error(stmt)); - } - - /* Get the parameter count from the statement */ - if ( mysql_stmt_param_count(stmt) != 2 ) { - Error("Unexpected parameter count %ld in sql '%s'", mysql_stmt_param_count(stmt), sql); - } - - MYSQL_BIND bind[2]; - memset(bind, 0, sizeof(bind)); - - /* STRING PARAM */ - bind[0].buffer_type = MYSQL_TYPE_STRING; - bind[0].buffer = (char *)notesStr; - bind[0].buffer_length = sizeof(notesStr); - bind[0].is_null = 0; - bind[0].length = ¬esLen; - - bind[1].buffer_type= MYSQL_TYPE_LONG; - bind[1].buffer= (char *)&id; - bind[1].is_null= 0; - bind[1].length= 0; - - /* Bind the buffers */ - if ( mysql_stmt_bind_param(stmt, bind) ) { - Error("Unable to bind sql '%s': %s", sql, mysql_stmt_error(stmt)); - } - } // end if ! stmt - - strncpy(notesStr, notes.c_str(), sizeof(notesStr)); - - if ( mysql_stmt_execute(stmt) ) { - Error("Unable to execute sql '%s': %s", sql, mysql_stmt_error(stmt)); - } -#else - char sql[ZM_SQL_LGE_BUFSIZ]; - static char escapedNotes[ZM_SQL_MED_BUFSIZ]; - - mysql_real_escape_string(&dbconn, escapedNotes, notes.c_str(), notes.length()); - - snprintf(sql, sizeof(sql), "UPDATE `Events` SET `Notes` = '%s' WHERE `Id` = %" PRIu64, escapedNotes, id); + std::string sql = stringtf("UPDATE `Events` SET `Notes` = '%s' WHERE `Id` = %" PRIu64, + zmDbEscapeString(notes).c_str(), id); dbQueue.push(std::move(sql)); -#endif } // end if update } // void Event::updateNotes(const StringSetMap &newNoteSetMap) -void Event::AddPacket(ZMPacket *packet) { - +void Event::AddPacket(const std::shared_ptr&packet) { have_video_keyframe = have_video_keyframe || ( ( packet->codec_type == AVMEDIA_TYPE_VIDEO ) && ( packet->keyframe || monitor->GetOptVideoWriter() == Monitor::ENCODE) ); Debug(2, "have_video_keyframe %d codec_type %d == video? %d packet keyframe %d", have_video_keyframe, packet->codec_type, (packet->codec_type == AVMEDIA_TYPE_VIDEO), packet->keyframe); ZM_DUMP_PACKET(packet->packet, "Adding to event"); + if (videoStore) { if (have_video_keyframe) { videoStore->writePacket(packet); @@ -436,10 +377,11 @@ void Event::AddPacket(ZMPacket *packet) { } //FIXME if it fails, we should write a jpeg } - if ((packet->codec_type == AVMEDIA_TYPE_VIDEO) or packet->image) - AddFrame(packet->image, *(packet->timestamp), packet->zone_stats, packet->score, packet->analysis_image); - end_time = *packet->timestamp; - return; + + if ((packet->codec_type == AVMEDIA_TYPE_VIDEO) or packet->image) { + AddFrame(packet->image, packet->timestamp, packet->zone_stats, packet->score, packet->analysis_image); + } + end_time = packet->timestamp; } void Event::WriteDbFrames() { @@ -453,14 +395,12 @@ void Event::WriteDbFrames() { while (frame_data.size()) { Frame *frame = frame_data.front(); frame_data.pop(); - frame_insert_sql += stringtf("\n( %" PRIu64 ", %d, '%s', from_unixtime( %ld ), %s%ld.%02ld, %d ),", - id, frame->frame_id, - frame_type_names[frame->type], - frame->timestamp.tv_sec, - frame->delta.positive ? "" : "-", - frame->delta.sec, - frame->delta.fsec, - frame->score); + frame_insert_sql += stringtf("\n( %" PRIu64 ", %d, '%s', from_unixtime( %ld ), %.2f, %d ),", + id, frame->frame_id, + frame_type_names[frame->type], + std::chrono::system_clock::to_time_t(frame->timestamp), + std::chrono::duration_cast(frame->delta).count(), + frame->score); if (config.record_event_stats and frame->zone_stats.size()) { for (ZoneStats &stats : frame->zone_stats) { stats_insert_sql += stringtf("\n(%" PRIu64 ",%d,%u,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%u),", @@ -474,10 +414,10 @@ void Event::WriteDbFrames() { stats.alarm_blobs_, stats.min_blob_size_, stats.max_blob_size_, - stats.alarm_box_.LoX(), - stats.alarm_box_.LoY(), - stats.alarm_box_.HiX(), - stats.alarm_box_.HiY(), + stats.alarm_box_.Lo().x_, + stats.alarm_box_.Lo().y_, + stats.alarm_box_.Hi().x_, + stats.alarm_box_.Hi().y_, stats.score_); } // end foreach zone stats } // end if recording stats @@ -485,7 +425,7 @@ void Event::WriteDbFrames() { } // end while frames // The -1 is for the extra , added for values above frame_insert_sql.erase(frame_insert_sql.size()-1); - //zmDbDo(frame_insert_sql.c_str()); + //zmDbDo(frame_insert_sql); dbQueue.push(std::move(frame_insert_sql)); if (stats_insert_sql.size() > 208) { // The -1 is for the extra , added for values above @@ -495,13 +435,12 @@ void Event::WriteDbFrames() { } } // end void Event::WriteDbFrames() -void Event::AddFrame( - Image *image, - struct timeval timestamp, - const std::vector &zone_stats, - int score, - Image *alarm_image) { - if (!timestamp.tv_sec) { +void Event::AddFrame(Image *image, + SystemTimePoint timestamp, + const std::vector &zone_stats, + int score, + Image *alarm_image) { + if (timestamp.time_since_epoch() == Seconds(0)) { Warning("Not adding new frame, zero timestamp"); return; } @@ -527,17 +466,21 @@ void Event::AddFrame( if (image) { if (save_jpegs & 1) { - std::string event_file = stringtf(staticConfig.capture_file_format, path.c_str(), frames); + std::string event_file = stringtf(staticConfig.capture_file_format.c_str(), path.c_str(), frames); Debug(1, "Writing capture frame %d to %s", frames, event_file.c_str()); if (!WriteFrameImage(image, timestamp, event_file.c_str())) { Error("Failed to write frame image"); } } // end if save_jpegs + 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)) { write_to_db = true; // web ui might show this as thumbnail, so db needs to know about it. + Debug(1, "Writing snapshot"); WriteFrameImage(image, timestamp, snapshot_file.c_str()); + } else { + Debug(1, "Not Writing snapshot"); } // We are writing an Alarm frame @@ -546,11 +489,14 @@ void Event::AddFrame( 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()); + } else { + Debug(1, "Not Writing alarm image"); } if (alarm_image and (save_jpegs & 2)) { - std::string event_file = stringtf(staticConfig.analyse_file_format, path.c_str(), frames); + std::string event_file = stringtf(staticConfig.analyse_file_format.c_str(), path.c_str(), frames); Debug(1, "Writing analysis frame %d", frames); if (!WriteFrameImage(alarm_image, timestamp, event_file.c_str(), true)) { Error("Failed to write analysis frame image"); @@ -572,55 +518,50 @@ void Event::AddFrame( or ( monitor_state == Monitor::PREALARM ); if (db_frame) { - - struct DeltaTimeval delta_time; - DELTA_TIMEVAL(delta_time, timestamp, start_time, DT_PREC_2); - Debug(1, "Frame delta is %" PRIi64 ".%" PRIi64 " - %" PRIi64 ".%" PRIi64 " = %lu.%lu, score %u zone_stats.size %zu", - static_cast(start_time.tv_sec), static_cast(start_time.tv_usec), - static_cast(timestamp.tv_sec), static_cast(timestamp.tv_usec), - delta_time.sec, delta_time.fsec, + Microseconds delta_time = std::chrono::duration_cast(timestamp - start_time); + Debug(1, "Frame delta is %.2f s - %.2f s = %.2f s, score %u zone_stats.size %zu", + FPSeconds(timestamp.time_since_epoch()).count(), + FPSeconds(start_time.time_since_epoch()).count(), + FPSeconds(delta_time).count(), score, zone_stats.size()); // The idea is to write out 1/sec frame_data.push(new Frame(id, frames, frame_type, timestamp, delta_time, score, zone_stats)); double fps = monitor->get_capture_fps(); - if ( write_to_db - or - ( frame_data.size() >= MAX_DB_FRAMES ) - or - ( frame_type == BULK ) - or - ( fps and (frame_data.size() > 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)); + if (write_to_db + or + (frame_data.size() >= MAX_DB_FRAMES) + or + (frame_type == BULK) + or + (fps and (frame_data.size() > 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(); last_db_frame = frames; - char sql[ZM_SQL_MED_BUFSIZ]; - snprintf(sql, sizeof(sql), - "UPDATE Events SET Length = %s%ld.%02ld, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d WHERE Id = %" PRIu64, - ( delta_time.positive?"":"-" ), - delta_time.sec, delta_time.fsec, - frames, + std::string sql = stringtf( + "UPDATE Events SET Length = %.2f, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d WHERE Id = %" PRIu64, + FPSeconds(delta_time).count(), + frames, alarm_frames, tot_score, - (int)(alarm_frames?(tot_score/alarm_frames):0), + static_cast(alarm_frames ? (tot_score / alarm_frames) : 0), max_score, - id - ); + id); dbQueue.push(std::move(sql)); - } else { - Debug(1, "Not Adding %zu frames to DB because write_to_db:%d or frames > analysis fps %f or BULK", - frame_data.size(), write_to_db, fps); + } else { + Debug(1, "Not Adding %zu frames to DB because write_to_db:%d or frames > analysis fps %f or BULK", + frame_data.size(), write_to_db, fps); } // end if frame_type == BULK } // end if db_frame - if (score > (int)max_score) + if (score > (int) max_score) { max_score = score; + } end_time = timestamp; -} // end void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *alarm_image) +} bool Event::SetPath(Storage *storage) { scheme = storage->Scheme(); @@ -632,8 +573,10 @@ bool Event::SetPath(Storage *storage) { return false; } + time_t start_time_t = std::chrono::system_clock::to_time_t(start_time); + tm stime = {}; - localtime_r(&start_time.tv_sec, &stime); + localtime_r(&start_time_t, &stime); if (scheme == Storage::DEEP) { int dt_parts[6]; diff --git a/src/zm_event.h b/src/zm_event.h index 7d0a334c5..8b8e49322 100644 --- a/src/zm_event.h +++ b/src/zm_event.h @@ -23,6 +23,8 @@ #include "zm_config.h" #include "zm_define.h" #include "zm_storage.h" +#include "zm_time.h" +#include "zm_utils.h" #include "zm_zone.h" #include @@ -68,8 +70,8 @@ class Event { uint64_t id; Monitor *monitor; - struct timeval start_time; - struct timeval end_time; + SystemTimePoint start_time; + SystemTimePoint end_time; std::string cause; StringSetMap noteSetMap; int frames; @@ -95,12 +97,10 @@ class Event { static bool OpenFrameSocket(int); static bool ValidateFrameSocket(int); - Event( - Monitor *p_monitor, - struct timeval p_start_time, - const std::string &p_cause, - const StringSetMap &p_noteSetMap - ); + Event(Monitor *p_monitor, + SystemTimePoint p_start_time, + const std::string &p_cause, + const StringSetMap &p_noteSetMap); ~Event(); uint64_t Id() const { return id; } @@ -108,46 +108,38 @@ class Event { int Frames() const { return frames; } int AlarmFrames() const { return alarm_frames; } - const struct timeval &StartTime() const { return start_time; } - const struct timeval &EndTime() const { return end_time; } + SystemTimePoint StartTime() const { return start_time; } + SystemTimePoint EndTime() const { return end_time; } - void AddPacket(ZMPacket *p); - bool WritePacket(ZMPacket &p); + void AddPacket(const std::shared_ptr &p); + bool WritePacket(const std::shared_ptr &p); bool SendFrameImage(const Image *image, bool alarm_frame=false); - bool WriteFrameImage( - Image *image, - struct timeval timestamp, - const char *event_file, - bool alarm_frame=false - ) const; + bool WriteFrameImage(Image *image, SystemTimePoint timestamp, const char *event_file, bool alarm_frame = false) const; void updateNotes(const StringSetMap &stringSetMap); - void AddFrame( - Image *image, - struct timeval timestamp, - const std::vector &stats, - int score=0, - Image *alarm_image=nullptr - ); + void AddFrame(Image *image, + SystemTimePoint timestamp, + const std::vector &stats, + int score = 0, + Image *alarm_image = nullptr); private: void WriteDbFrames(); bool SetPath(Storage *storage); public: - static const char *getSubPath(tm time) { - static char subpath[PATH_MAX] = ""; - snprintf(subpath, sizeof(subpath), "%02d/%02d/%02d/%02d/%02d/%02d", - time.tm_year-100, time.tm_mon+1, time.tm_mday, - time.tm_hour, time.tm_min, time.tm_sec); - return subpath; - } - static const char *getSubPath(time_t *time) { - 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(); @@ -173,7 +165,7 @@ class Event { } static void AddPreAlarmFrame( Image *image, - struct timeval timestamp, + SystemTimePoint timestamp, int score=0, Image *alarm_frame=nullptr ) { diff --git a/src/zm_eventstream.cpp b/src/zm_eventstream.cpp index 7a35f6892..4bad2d61a 100644 --- a/src/zm_eventstream.cpp +++ b/src/zm_eventstream.cpp @@ -27,7 +27,6 @@ #include "zm_storage.h" #include #include -#include #ifdef __FreeBSD__ #include @@ -40,12 +39,14 @@ const std::string EventStream::StreamMode_Strings[4] = { "Gapless" }; -bool EventStream::loadInitialEventData(int monitor_id, time_t event_time) { +constexpr Milliseconds EventStream::STREAM_PAUSE_WAIT; + +bool EventStream::loadInitialEventData(int monitor_id, SystemTimePoint event_time) { std::string sql = stringtf("SELECT `Id` FROM `Events` WHERE " "`MonitorId` = %d AND unix_timestamp(`EndDateTime`) > %ld " - "ORDER BY `Id` ASC LIMIT 1", monitor_id, event_time); + "ORDER BY `Id` ASC LIMIT 1", monitor_id, std::chrono::system_clock::to_time_t(event_time)); - MYSQL_RES *result = zmDbFetch(sql.c_str()); + MYSQL_RES *result = zmDbFetch(sql); if (!result) exit(-1); @@ -62,23 +63,26 @@ bool EventStream::loadInitialEventData(int monitor_id, time_t event_time) { loadEventData(init_event_id); - if ( event_time ) { + if (event_time.time_since_epoch() == Seconds(0)) { curr_stream_time = event_time; curr_frame_id = 1; // curr_frame_id is 1-based - if ( event_time >= event_data->start_time ) { + 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 (unsigned int i = 0; i < event_data->frame_count; i++) { //Info( "eft %d > et %d", event_data->frames[i].timestamp, event_time ); - if ( event_data->frames[i].timestamp >= event_time ) { - curr_frame_id = i+1; - Debug(3, "Set curr_stream_time:%.2f, curr_frame_id:%ld", curr_stream_time, curr_frame_id); + if (event_data->frames[i].timestamp >= event_time) { + curr_frame_id = i + 1; + Debug(3, "Set curr_stream_time: %.2f, curr_frame_id: %ld", + FPSeconds(curr_stream_time.time_since_epoch()).count(), + curr_frame_id); break; } } // end foreach frame Debug(3, "Skipping %ld 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(event_time), static_cast(event_data->start_time)); + static_cast(std::chrono::duration_cast(event_time.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(event_data->start_time.time_since_epoch()).count())); } } // end if have a start time return true; @@ -113,7 +117,7 @@ bool EventStream::loadEventData(uint64_t event_id) { "(SELECT max(`Delta`)-min(`Delta`) FROM `Frames` WHERE `EventId`=`Events`.`Id`) AS FramesDuration, " "`DefaultVideo`, `Scheme`, `SaveJPEGs`, `Orientation`+0 FROM `Events` WHERE `Id` = %" PRIu64, event_id); - MYSQL_RES *result = zmDbFetch(sql.c_str()); + MYSQL_RES *result = zmDbFetch(sql); if (!result) { exit(-1); } @@ -136,11 +140,12 @@ bool EventStream::loadEventData(uint64_t event_id) { event_data->monitor_id = atoi(dbrow[0]); event_data->storage_id = dbrow[1] ? atoi(dbrow[1]) : 0; event_data->frame_count = dbrow[2] == nullptr ? 0 : atoi(dbrow[2]); - event_data->start_time = atoi(dbrow[3]); - event_data->end_time = dbrow[4] ? atoi(dbrow[4]) : 0; - event_data->duration = event_data->end_time - event_data->start_time; - event_data->frames_duration = dbrow[5] ? atof(dbrow[5]) : 0.0; - strncpy(event_data->video_file, dbrow[6], sizeof(event_data->video_file)-1); + event_data->start_time = SystemTimePoint(Seconds(atoi(dbrow[3]))); + event_data->end_time = dbrow[4] ? SystemTimePoint(Seconds(atoi(dbrow[4]))) : SystemTimePoint(); + event_data->duration = std::chrono::duration_cast(event_data->end_time - event_data->start_time); + event_data->frames_duration = + std::chrono::duration_cast(dbrow[5] ? FPSeconds(atof(dbrow[5])) : FPSeconds(0.0)); + event_data->video_file = std::string(dbrow[6]); std::string scheme_str = std::string(dbrow[7]); if ( scheme_str == "Deep" ) { event_data->scheme = Storage::DEEP; @@ -172,52 +177,56 @@ bool EventStream::loadEventData(uint64_t event_id) { if ( event_data->scheme == Storage::DEEP ) { tm event_time = {}; - localtime_r(&event_data->start_time, &event_time); + time_t start_time_t = std::chrono::system_clock::to_time_t(event_data->start_time); + localtime_r(&start_time_t, &event_time); - if ( storage_path[0] == '/' ) - snprintf(event_data->path, sizeof(event_data->path), - "%s/%u/%02d/%02d/%02d/%02d/%02d/%02d", - storage_path, event_data->monitor_id, - event_time.tm_year-100, event_time.tm_mon+1, event_time.tm_mday, - event_time.tm_hour, event_time.tm_min, event_time.tm_sec); - else - snprintf(event_data->path, sizeof(event_data->path), - "%s/%s/%u/%02d/%02d/%02d/%02d/%02d/%02d", - staticConfig.PATH_WEB.c_str(), storage_path, event_data->monitor_id, - event_time.tm_year-100, event_time.tm_mon+1, event_time.tm_mday, - event_time.tm_hour, event_time.tm_min, event_time.tm_sec); - } else if ( event_data->scheme == Storage::MEDIUM ) { + if (storage_path[0] == '/') { + event_data->path = stringtf("%s/%u/%02d/%02d/%02d/%02d/%02d/%02d", + storage_path, event_data->monitor_id, + event_time.tm_year - 100, event_time.tm_mon + 1, event_time.tm_mday, + event_time.tm_hour, event_time.tm_min, event_time.tm_sec); + } else { + event_data->path = stringtf("%s/%s/%u/%02d/%02d/%02d/%02d/%02d/%02d", + staticConfig.PATH_WEB.c_str(), storage_path, event_data->monitor_id, + event_time.tm_year - 100, event_time.tm_mon + 1, event_time.tm_mday, + event_time.tm_hour, event_time.tm_min, event_time.tm_sec); + } + } else if (event_data->scheme == Storage::MEDIUM) { tm event_time = {}; - localtime_r(&event_data->start_time, &event_time); - if ( storage_path[0] == '/' ) - snprintf(event_data->path, sizeof(event_data->path), - "%s/%u/%04d-%02d-%02d/%" PRIu64, - storage_path, event_data->monitor_id, - event_time.tm_year+1900, event_time.tm_mon+1, event_time.tm_mday, - event_data->event_id); - else - snprintf(event_data->path, sizeof(event_data->path), - "%s/%s/%u/%04d-%02d-%02d/%" PRIu64, - staticConfig.PATH_WEB.c_str(), storage_path, event_data->monitor_id, - event_time.tm_year+1900, event_time.tm_mon+1, event_time.tm_mday, - event_data->event_id); + time_t start_time_t = std::chrono::system_clock::to_time_t(event_data->start_time); + localtime_r(&start_time_t, &event_time); + if (storage_path[0] == '/') { + event_data->path = stringtf("%s/%u/%04d-%02d-%02d/%" PRIu64, + storage_path, event_data->monitor_id, + event_time.tm_year + 1900, event_time.tm_mon + 1, event_time.tm_mday, + event_data->event_id); + } else { + event_data->path = stringtf("%s/%s/%u/%04d-%02d-%02d/%" PRIu64, + staticConfig.PATH_WEB.c_str(), storage_path, event_data->monitor_id, + event_time.tm_year + 1900, event_time.tm_mon + 1, event_time.tm_mday, + event_data->event_id); + } } else { - if ( storage_path[0] == '/' ) - snprintf(event_data->path, sizeof(event_data->path), "%s/%u/%" PRIu64, - storage_path, event_data->monitor_id, event_data->event_id); - else - snprintf(event_data->path, sizeof(event_data->path), "%s/%s/%u/%" PRIu64, - staticConfig.PATH_WEB.c_str(), storage_path, event_data->monitor_id, - event_data->event_id); + if (storage_path[0] == '/') { + event_data->path = stringtf("%s/%u/%" PRIu64, storage_path, event_data->monitor_id, event_data->event_id); + } else { + event_data->path = stringtf("%s/%s/%u/%" PRIu64, + staticConfig.PATH_WEB.c_str(), storage_path, event_data->monitor_id, + event_data->event_id); + } } - updateFrameRate((event_data->frame_count and event_data->duration) ? (double)event_data->frame_count/event_data->duration : 1); + double fps = 1.0; + if ((event_data->frame_count and event_data->duration != Seconds(0))) { + fps = static_cast(event_data->frame_count) / FPSeconds(event_data->duration).count(); + } + updateFrameRate(fps); sql = stringtf("SELECT `FrameId`, unix_timestamp(`TimeStamp`), `Delta` " "FROM `Frames` WHERE `EventId` = %" PRIu64 " ORDER BY `FrameId` ASC", event_id); - result = zmDbFetch(sql.c_str()); + result = zmDbFetch(sql); if (!result) { exit(-1); } @@ -226,46 +235,47 @@ bool EventStream::loadEventData(uint64_t event_id) { event_data->frames = new FrameData[event_data->frame_count]; int last_id = 0; - double last_timestamp = event_data->start_time; - double last_delta = 0.0; + SystemTimePoint last_timestamp = event_data->start_time; + Microseconds last_delta = Seconds(0); while ( ( dbrow = mysql_fetch_row(result) ) ) { int id = atoi(dbrow[0]); //timestamp = atof(dbrow[1]); - double delta = atof(dbrow[2]); + Microseconds delta = std::chrono::duration_cast(FPSeconds(atof(dbrow[2]))); int id_diff = id - last_id; - double frame_delta = id_diff ? (delta-last_delta)/id_diff : (delta-last_delta); + Microseconds frame_delta = + std::chrono::duration_cast(id_diff ? (delta - last_delta) / id_diff : (delta - last_delta)); + // Fill in data between bulk frames - if ( id_diff > 1 ) { - for ( int i = last_id+1; i < id; i++ ) { + if (id_diff > 1) { + for (int i = last_id + 1; i < id; i++) { // Delta is the time since last frame, no since beginning of Event - event_data->frames[i-1].delta = frame_delta; - event_data->frames[i-1].timestamp = last_timestamp + ((i-last_id)*frame_delta); - event_data->frames[i-1].offset = event_data->frames[i-1].timestamp - event_data->start_time; - event_data->frames[i-1].in_db = false; - Debug(3, "Frame %d timestamp:(%f), offset(%f) delta(%f), in_db(%d)", - i, - event_data->frames[i-1].timestamp, - event_data->frames[i-1].offset, - event_data->frames[i-1].delta, - event_data->frames[i-1].in_db - ); + event_data->frames[i - 1].delta = frame_delta; + event_data->frames[i - 1].timestamp = last_timestamp + ((i - last_id) * frame_delta); + event_data->frames[i - 1].offset = + std::chrono::duration_cast(event_data->frames[i - 1].timestamp - event_data->start_time); + event_data->frames[i - 1].in_db = false; + Debug(3, "Frame %d timestamp (%f s), offset (%f s) delta (%f s), in_db (%d)", + i, + FPSeconds(event_data->frames[i - 1].timestamp.time_since_epoch()).count(), + FPSeconds(event_data->frames[i - 1].offset).count(), + FPSeconds(event_data->frames[i - 1].delta).count(), + event_data->frames[i - 1].in_db); } } - event_data->frames[id-1].timestamp = event_data->start_time + delta; - event_data->frames[id-1].offset = delta; - event_data->frames[id-1].delta = frame_delta; - event_data->frames[id-1].in_db = true; + event_data->frames[id - 1].timestamp = event_data->start_time + delta; + event_data->frames[id - 1].offset = delta; + event_data->frames[id - 1].delta = frame_delta; + event_data->frames[id - 1].in_db = true; last_id = id; last_delta = delta; last_timestamp = event_data->frames[id-1].timestamp; - Debug(3, "Frame %d timestamp:(%f), offset(%f) delta(%f), in_db(%d)", - id, - event_data->frames[id-1].timestamp, - event_data->frames[id-1].offset, - event_data->frames[id-1].delta, - event_data->frames[id-1].in_db - ); + Debug(3, "Frame %d timestamp (%f s), offset (%f s), delta(%f s), in_db(%d)", + id, + FPSeconds(event_data->frames[id - 1].timestamp.time_since_epoch()).count(), + FPSeconds(event_data->frames[id - 1].offset).count(), + FPSeconds(event_data->frames[id - 1].delta).count(), + event_data->frames[id - 1].in_db); } // Incomplete events might not have any frame data event_data->last_frame_id = last_id; @@ -276,17 +286,17 @@ bool EventStream::loadEventData(uint64_t event_id) { } mysql_free_result(result); - if ( event_data->video_file[0] || (monitor->GetOptVideoWriter() > 0) ) { - if ( !event_data->video_file[0] ) { - snprintf(event_data->video_file, sizeof(event_data->video_file), "%" PRIu64 "-%s", event_data->event_id, "video.mp4"); + if (!event_data->video_file.empty() || (monitor->GetOptVideoWriter() > 0)) { + if (event_data->video_file.empty()) { + event_data->video_file = stringtf("%" PRIu64 "-%s", event_data->event_id, "video.mp4"); } - std::string filepath = std::string(event_data->path) + "/" + std::string(event_data->video_file); + + std::string filepath = event_data->path + "/" + event_data->video_file; Debug(1, "Loading video file from %s", filepath.c_str()); - if ( ffmpeg_input ) - delete ffmpeg_input; + delete ffmpeg_input; ffmpeg_input = new FFmpeg_Input(); - if ( 0 > ffmpeg_input->Open(filepath.c_str()) ) { + if (ffmpeg_input->Open(filepath.c_str()) < 0) { Warning("Unable to open ffmpeg_input %s", filepath.c_str()); delete ffmpeg_input; ffmpeg_input = nullptr; @@ -300,8 +310,12 @@ 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 Frames Duration: %.2f", - event_data->event_id, event_data->frame_count, event_data->last_frame_id, event_data->duration, event_data->frames_duration); + Debug(2, "Event: %" PRIu64 ", Frames: %ld, Last Frame ID (%ld, Duration: %.2f s Frames Duration: %.2f s", + event_data->event_id, + event_data->frame_count, + event_data->last_frame_id, + FPSeconds(event_data->duration).count(), + FPSeconds(event_data->frames_duration).count()); return true; } // bool EventStream::loadEventData( int event_id ) @@ -316,7 +330,7 @@ void EventStream::processCommand(const CmdMsg *msg) { // Set paused flag paused = true; replay_rate = ZM_RATE_BASE; - last_frame_sent = TV_2_FLOAT(now); + last_frame_sent = now; break; case CMD_PLAY : Debug(1, "Got PLAY command"); @@ -503,25 +517,25 @@ void EventStream::processCommand(const CmdMsg *msg) { break; case CMD_SEEK : { - // offset is in seconds + 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]; - int 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]; - int 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]; - - double offset = (double)int_part + (double)(dec_part / (double)1000000); - if ( offset < 0.0 ) { + FPSeconds offset = FPSeconds(int_part + dec_part / 1000000.0); + if (offset < Seconds(0)) { Warning("Invalid offset, not seeking"); break; } + // This should get us close, but not all frames will have the same duration - curr_frame_id = (int)(event_data->frame_count*offset/event_data->duration)+1; - if ( event_data->frames[curr_frame_id-1].offset > offset ) { - while ( (curr_frame_id --) && ( event_data->frames[curr_frame_id-1].offset > offset ) ) { - } - } else if ( event_data->frames[curr_frame_id-1].offset < offset ) { - while ( (curr_frame_id ++) && ( event_data->frames[curr_frame_id-1].offset > offset ) ) { - } + curr_frame_id = (int) (event_data->frame_count * offset / event_data->duration) + 1; + if (event_data->frames[curr_frame_id - 1].offset > offset) { + while ((curr_frame_id--) && (event_data->frames[curr_frame_id - 1].offset > offset)) {} + } else if (event_data->frames[curr_frame_id - 1].offset < offset) { + while ((curr_frame_id++) && (event_data->frames[curr_frame_id - 1].offset > offset)) {} } + if ( curr_frame_id < 1 ) { curr_frame_id = 1; } else if ( (unsigned long)curr_frame_id > event_data->last_frame_id ) { @@ -529,8 +543,10 @@ void EventStream::processCommand(const CmdMsg *msg) { } curr_stream_time = event_data->frames[curr_frame_id-1].timestamp; - Debug(1, "Got SEEK command, to %f (new current frame id: %ld offset %f)", - offset, curr_frame_id, event_data->frames[curr_frame_id-1].offset); + Debug(1, "Got SEEK command, to %f s (new current frame id: %ld offset %f s)", + FPSeconds(offset).count(), + curr_frame_id, + FPSeconds(event_data->frames[curr_frame_id - 1].offset).count()); send_frame = true; break; } @@ -547,12 +563,12 @@ void EventStream::processCommand(const CmdMsg *msg) { struct { uint64_t event_id; - double duration; - double progress; + Microseconds duration; + Microseconds progress; int rate; int zoom; bool paused; - } status_data; + } status_data = {}; status_data.event_id = event_data->event_id; status_data.duration = event_data->duration; @@ -561,13 +577,12 @@ void EventStream::processCommand(const CmdMsg *msg) { status_data.zoom = zoom; status_data.paused = paused; Debug(2, "Event:%" PRIu64 ", Duration %f, Paused:%d, progress:%f Rate:%d, Zoom:%d", - status_data.event_id, - status_data.duration, - status_data.paused, - status_data.progress, - status_data.rate, - status_data.zoom - ); + status_data.event_id, + FPSeconds(status_data.duration).count(), + status_data.paused, + FPSeconds(status_data.progress).count(), + status_data.rate, + status_data.zoom); DataMsg status_msg; status_msg.msg_type = MSG_DATA_EVENT; @@ -580,11 +595,17 @@ void EventStream::processCommand(const CmdMsg *msg) { //exit(-1); } } - // quit after sending a status, if this was a quit request - if ( (MsgCommand)msg->msg_data[0] == CMD_QUIT ) - exit(0); - updateFrameRate((event_data->frame_count and event_data->duration) ? (double)event_data->frame_count/event_data->duration : 1); + // quit after sending a status, if this was a quit request + if (static_cast(msg->msg_data[0]) == CMD_QUIT) { + exit(0); + } + + double fps = 1.0; + if ((event_data->frame_count and event_data->duration != Seconds(0))) { + fps = static_cast(event_data->frame_count) / FPSeconds(event_data->duration).count(); + } + updateFrameRate(fps); } // void EventStream::processCommand(const CmdMsg *msg) bool EventStream::checkEventLoaded() { @@ -595,7 +616,7 @@ bool EventStream::checkEventLoaded() { "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 ) { - if ( !event_data->end_time ) { + 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 ) @@ -616,7 +637,7 @@ bool EventStream::checkEventLoaded() { if ( forceEventChange || ( (mode != MODE_SINGLE) && (mode != MODE_NONE) ) ) { Debug(1, "Checking for next event %s", sql.c_str()); - MYSQL_RES *result = zmDbFetch(sql.c_str()); + MYSQL_RES *result = zmDbFetch(sql); if (!result) { exit(-1); } @@ -666,32 +687,30 @@ bool EventStream::checkEventLoaded() { } // void EventStream::checkEventLoaded() Image * EventStream::getImage( ) { - static char filepath[PATH_MAX]; - - snprintf(filepath, sizeof(filepath), staticConfig.capture_file_format, event_data->path, curr_frame_id); - Debug(2, "EventStream::getImage path(%s) from %s frame(%ld) ", filepath, event_data->path, curr_frame_id); - Image *image = new Image(filepath); + 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); + Image *image = new Image(path.c_str()); return image; } -bool EventStream::sendFrame(int delta_us) { +bool EventStream::sendFrame(Microseconds delta_us) { Debug(2, "Sending frame %ld", curr_frame_id); - static char filepath[PATH_MAX]; - static struct stat filestat; + 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 ) { - snprintf(filepath, sizeof(filepath), staticConfig.capture_file_format, event_data->path, curr_frame_id); - } else if ( event_data->SaveJPEGs & 2 ) { - snprintf(filepath, sizeof(filepath), staticConfig.analyse_file_format, event_data->path, curr_frame_id); - if ( stat(filepath, &filestat) < 0 ) { - Debug(1, "analyze file %s not found will try to stream from other", filepath); - snprintf(filepath, sizeof(filepath), staticConfig.capture_file_format, event_data->path, curr_frame_id); - if ( stat(filepath, &filestat) < 0 ) { - Debug(1, "capture file %s not found either", filepath); - filepath[0] = 0; + 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) { + 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()); + filepath = stringtf(staticConfig.capture_file_format.c_str(), event_data->path.c_str(), curr_frame_id); + if (stat(filepath.c_str(), &filestat) < 0) { + Debug(1, "capture file %s not found either", filepath.c_str()); + filepath = ""; } } } else if ( !ffmpeg_input ) { @@ -699,9 +718,8 @@ bool EventStream::sendFrame(int delta_us) { return false; } -#if HAVE_LIBAVCODEC if ( type == STREAM_MPEG ) { - Image image(filepath); + Image image(filepath.c_str()); Image *send_image = prepareImage(&image); @@ -711,32 +729,30 @@ bool EventStream::sendFrame(int delta_us) { fprintf(stdout, "Content-type: %s\r\n\r\n", vid_stream->MimeType()); vid_stream->OpenStream(); } - /* double pts = */ vid_stream->EncodeFrame(send_image->Buffer(), send_image->Size(), config.mpeg_timed_frames, delta_us*1000); - } else -#endif // HAVE_LIBAVCODEC - { - - - bool send_raw = (type == STREAM_JPEG) && ((scale>=ZM_SCALE_BASE)&&(zoom==ZM_SCALE_BASE)) && filepath[0]; + vid_stream->EncodeFrame(send_image->Buffer(), + send_image->Size(), + config.mpeg_timed_frames, + delta_us.count() * 1000); + } else { + bool send_raw = (type == STREAM_JPEG) && ((scale >= ZM_SCALE_BASE) && (zoom == ZM_SCALE_BASE)) && !filepath.empty(); fprintf(stdout, "--" BOUNDARY "\r\n"); - if ( send_raw ) { - if ( !send_file(filepath) ) { - Error("Can't send %s: %s", filepath, strerror(errno)); + if (send_raw) { + if (!send_file(filepath)) { + Error("Can't send %s: %s", filepath.c_str(), strerror(errno)); return false; } } else { Image *image = nullptr; - if ( filepath[0] ) { - image = new Image(filepath); + if (!filepath.empty()) { + image = new Image(filepath.c_str()); } else if ( ffmpeg_input ) { // Get the frame from the mp4 input FrameData *frame_data = &event_data->frames[curr_frame_id-1]; - AVFrame *frame = ffmpeg_input->get_frame( - ffmpeg_input->get_video_stream_id(), - frame_data->offset); + AVFrame *frame = + ffmpeg_input->get_frame(ffmpeg_input->get_video_stream_id(), FPSeconds(frame_data->offset).count()); if ( frame ) { image = new Image(frame); //av_frame_free(&frame); @@ -811,7 +827,7 @@ bool EventStream::sendFrame(int delta_us) { fputs("\r\n", stdout); fflush(stdout); - last_frame_sent = TV_2_FLOAT(now); + last_frame_sent = now; return true; } // bool EventStream::sendFrame( int delta_us ) @@ -828,17 +844,21 @@ void EventStream::runStream() { exit(0); } - updateFrameRate((event_data->frame_count and event_data->duration) ? (double)event_data->frame_count/event_data->duration : 1); - gettimeofday(&start, nullptr); - uint64_t start_usec = start.tv_sec * 1000000 + start.tv_usec; - uint64_t last_frame_offset = 0; + double fps = 1.0; + if ((event_data->frame_count and event_data->duration != Seconds(0))) { + fps = static_cast(event_data->frame_count) / FPSeconds(event_data->duration).count(); + } + updateFrameRate(fps); - double time_to_event = 0; + start = std::chrono::system_clock::now(); + + SystemTimePoint::duration last_frame_offset = Seconds(0); + SystemTimePoint::duration time_to_event = Seconds(0); while ( !zm_terminate ) { - gettimeofday(&now, nullptr); + now = std::chrono::system_clock::now(); - int delta_us = 0; + Microseconds delta = Microseconds(0); send_frame = false; if ( connkey ) { @@ -848,7 +868,7 @@ void EventStream::runStream() { } // Update modified time of the socket .lock file so that we can tell which ones are stale. - if ( now.tv_sec - last_comm_update.tv_sec > 3600 ) { + if (now - last_comm_update > Hours(1)) { touch(sock_path_lock); last_comm_update = now; } @@ -874,8 +894,7 @@ void EventStream::runStream() { send_frame = true; } else if ( !send_frame ) { // We are paused, not stepping and doing nothing, meaning that comms didn't set send_frame to true - double time_since_last_send = TV_2_FLOAT(now) - last_frame_sent; - if ( time_since_last_send > MAX_STREAM_DELAY ) { + if (now - last_frame_sent > MAX_STREAM_DELAY) { // Send keepalive Debug(2, "Sending keepalive frame"); send_frame = true; @@ -883,39 +902,47 @@ void EventStream::runStream() { } // end if streaming stepping or doing nothing // time_to_event > 0 means that we are not in the event - if ( ( time_to_event > 0 ) and ( mode == MODE_ALL ) ) { - double time_since_last_send = TV_2_FLOAT(now) - last_frame_sent; - Debug(1, "Time since last send = %f = %f - %f", time_since_last_send, TV_2_FLOAT(now), last_frame_sent); - if ( time_since_last_send > 1 /* second */ ) { - static char frame_text[64]; + if (time_to_event > Seconds(0) and mode == MODE_ALL) { + SystemTimePoint::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]; - snprintf(frame_text, sizeof(frame_text), "Time to %s event = %d seconds", - (replay_rate > 0 ? "next" : "previous" ), (int)time_to_event); - if ( !sendTextFrame(frame_text) ) + snprintf(frame_text, sizeof(frame_text), "Time to %s event = %f s", + (replay_rate > 0 ? "next" : "previous"), + FPSeconds(time_to_event).count()); + + if (!sendTextFrame(frame_text)) { zm_terminate = true; + } + send_frame = false; // In case keepalive was set } // FIXME ICON But we are not paused. We are somehow still in the event? - double sleep_time = (replay_rate>0?1:-1) * ((1.0L * replay_rate * STREAM_PAUSE_WAIT)/(ZM_RATE_BASE * 1000000)); + Milliseconds sleep_time = std::chrono::duration_cast( + (replay_rate > 0 ? 1 : -1) * ((1.0L * replay_rate * STREAM_PAUSE_WAIT) / ZM_RATE_BASE)); //double sleep_time = (replay_rate * STREAM_PAUSE_WAIT)/(ZM_RATE_BASE * 1000000); //// ZM_RATE_BASE == 100, and 1x replay_rate is 100 //double sleep_time = ((replay_rate/ZM_RATE_BASE) * STREAM_PAUSE_WAIT)/1000000; - if ( !sleep_time ) { - sleep_time += STREAM_PAUSE_WAIT/1000000; + if (sleep_time == Seconds(0)) { + sleep_time += STREAM_PAUSE_WAIT; } + curr_stream_time += sleep_time; time_to_event -= sleep_time; - Debug(2, "Sleeping (%dus) because we are not at the next event yet, adding %f", STREAM_PAUSE_WAIT, sleep_time); - usleep(STREAM_PAUSE_WAIT); + Debug(2, "Sleeping (%" PRIi64 " ms) because we are not at the next event yet, adding %" PRIi64 " ms", + static_cast(Milliseconds(STREAM_PAUSE_WAIT).count()), + static_cast(Milliseconds(sleep_time).count())); + std::this_thread::sleep_for(STREAM_PAUSE_WAIT); //curr_stream_time += (1.0L * replay_rate * STREAM_PAUSE_WAIT)/(ZM_RATE_BASE * 1000000); //} continue; } // end if !in_event - if ( send_frame ) { - if ( !sendFrame(delta_us) ) { + if (send_frame) { + if (!sendFrame(delta)) { zm_terminate = true; break; } @@ -923,23 +950,30 @@ void EventStream::runStream() { curr_stream_time = frame_data->timestamp; - if ( !paused ) { - + if (!paused) { // delta is since the last frame - delta_us = (unsigned int)(frame_data->delta * 1000000); - Debug(3, "frame delta %uus ", delta_us); + delta = std::chrono::duration_cast(frame_data->delta); + Debug(3, "frame delta %" PRIi64 "us ", + static_cast(std::chrono::duration_cast(delta).count())); + // if effective > base we should speed up frame delivery - delta_us = (unsigned int)((delta_us * base_fps)/effective_fps); - Debug(3, "delta %u = base_fps(%f)/effective fps(%f)", delta_us, base_fps, effective_fps); + delta = std::chrono::duration_cast((delta * base_fps) / effective_fps); + Debug(3, "delta %" PRIi64 " us = base_fps (%f) / effective_fps (%f)", + static_cast(std::chrono::duration_cast(delta).count()), + base_fps, + effective_fps); + // but must not exceed maxfps - delta_us = std::max(delta_us, int(1000000/maxfps)); - Debug(3, "delta %u = base_fps(%f)/effective fps(%f) from 30fps", delta_us, base_fps, effective_fps); + delta = std::max(delta, Microseconds(lround(Microseconds::period::den / maxfps))); + Debug(3, "delta %" PRIi64 " us = base_fps (%f) /effective_fps (%f) from 30fps", + static_cast(std::chrono::duration_cast(delta).count()), + base_fps, + effective_fps); // +/- 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 - gettimeofday(&now, nullptr); - uint64_t now_usec = (now.tv_sec * 1000000 + now.tv_usec); + now = std::chrono::system_clock::now(); // we incremented by replay_rate, so might have jumped past frame_count if ( (mode == MODE_SINGLE) && ( @@ -950,8 +984,8 @@ void EventStream::runStream() { ) { Debug(2, "Have mode==MODE_SINGLE and at end of event, looping back to start"); curr_frame_id = 1; - // Have to reset start_usec to now when replaying - start_usec = now_usec; + // Have to reset start to now when replaying + start = now; } frame_data = &event_data->frames[curr_frame_id-1]; @@ -963,43 +997,53 @@ void EventStream::runStream() { // 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 ) { + if (last_frame_offset != Seconds(0)) { // We assume that we are going forward and the next frame is in the future. - delta_us = frame_data->offset * 1000000 - (now_usec-start_usec); - // - (now_usec - start_usec); - Debug(2, "New delta_us now %" PRIu64 " - start %" PRIu64 " = %" PRIu64 " offset %f - elapsed = %dusec", - now_usec, start_usec, static_cast(now_usec - start_usec), frame_data->offset * 1000000, delta_us); + delta = std::chrono::duration_cast(frame_data->offset - (now - start)); + + Debug(2, "New delta: now - start = %" PRIu64 " us offset %" PRIi64 " us- elapsed = %" PRIu64 " us", + static_cast(std::chrono::duration_cast(now - start).count()), + static_cast(std::chrono::duration_cast(frame_data->offset).count()), + static_cast(std::chrono::duration_cast(delta).count())); } else { Debug(2, "No last frame_offset, no sleep"); - delta_us = 0; + delta = Seconds(0); } - last_frame_offset = frame_data->offset * 1000000; + last_frame_offset = frame_data->offset; - if ( send_frame && (type != STREAM_MPEG) ) { - if ( delta_us > 0 ) { - if ( delta_us > MAX_SLEEP_USEC ) { - Debug(1, "Limiting sleep to %d because calculated sleep is too long %d", MAX_SLEEP_USEC, delta_us); - delta_us = MAX_SLEEP_USEC; + 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(std::chrono::duration_cast(MAX_SLEEP).count()), + static_cast(std::chrono::duration_cast(delta).count())); + delta = MAX_SLEEP; } - usleep(delta_us); - Debug(3, "Done sleeping: %d usec", delta_us); + + std::this_thread::sleep_for(delta); + Debug(3, "Done sleeping: %" PRIi64 " us", + static_cast(std::chrono::duration_cast(delta).count())); } } } else { - delta_us = ((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*(replay_rate?abs(replay_rate*2):2))); + delta = std::chrono::duration_cast(FPSeconds( + ZM_RATE_BASE / ((base_fps ? base_fps : 1) * (replay_rate ? abs(replay_rate * 2) : 2)))); - Debug(2, "Sleeping %d because 1000000 * ZM_RATE_BASE(%d) / ( base_fps (%f), replay_rate(%d)", - delta_us, - ZM_RATE_BASE, - (base_fps ? base_fps : 1), - (replay_rate ? abs(replay_rate*2) : 0) - ); - if ( delta_us > 0 ) { - if ( delta_us > MAX_SLEEP_USEC ) { - Debug(1, "Limiting sleep to %d because calculated sleep is too long %d", MAX_SLEEP_USEC, delta_us); - delta_us = MAX_SLEEP_USEC; + Debug(2, "Sleeping %" PRIi64 " us because ZM_RATE_BASE (%d) / ( base_fps (%f) * replay_rate (%d)", + static_cast(std::chrono::duration_cast(delta).count()), + ZM_RATE_BASE, + (base_fps ? base_fps : 1), + (replay_rate ? abs(replay_rate * 2) : 0)); + + if (delta != Seconds(0)) { + if (delta > MAX_SLEEP) { + Debug(1, "Limiting sleep to %" PRIi64 " ms because calculated sleep is too long %" PRIi64, + static_cast(std::chrono::duration_cast(MAX_SLEEP).count()), + static_cast(std::chrono::duration_cast(delta).count())); + delta = MAX_SLEEP; } - usleep(delta_us); + + std::this_thread::sleep_for(delta); } // We are paused, so might be stepping //if ( step != 0 )// Adding 0 is cheaper than an if 0 @@ -1016,43 +1060,47 @@ void EventStream::runStream() { if ( replay_rate > 0 ) { // This doesn't make sense unless we have hit the end of the event. time_to_event = event_data->frames[0].timestamp - curr_stream_time; - Debug(1, "replay rate(%d) time_to_event(%f)=frame timestamp:%f - curr_stream_time(%f)", - replay_rate, time_to_event, - event_data->frames[0].timestamp, - curr_stream_time); + Debug(1, "replay rate (%d) time_to_event (%f s) = frame timestamp (%f s) - curr_stream_time (%f s)", + replay_rate, + FPSeconds(time_to_event).count(), + FPSeconds(event_data->frames[0].timestamp.time_since_epoch()).count(), + FPSeconds(curr_stream_time.time_since_epoch()).count()); } else if ( replay_rate < 0 ) { time_to_event = curr_stream_time - event_data->frames[event_data->frame_count-1].timestamp; - Debug(1, "replay rate(%d) time_to_event(%f)=curr_stream_time(%f)-frame timestamp:%f", - replay_rate, time_to_event, curr_stream_time, event_data->frames[event_data->frame_count-1].timestamp); + Debug(1, "replay rate (%d), time_to_event(%f s) = curr_stream_time (%f s) - frame timestamp (%f s)", + replay_rate, + FPSeconds(time_to_event).count(), + FPSeconds(curr_stream_time.time_since_epoch()).count(), + FPSeconds(event_data->frames[event_data->frame_count - 1].timestamp.time_since_epoch()).count()); } // end if forward or reverse } // end if checkEventLoaded } // end while ! zm_terminate -#if HAVE_LIBAVCODEC - if ( type == STREAM_MPEG ) + if (type == STREAM_MPEG) { delete vid_stream; -#endif // HAVE_LIBAVCODEC + } closeComms(); } // end void EventStream::runStream() -bool EventStream::send_file(const char *filepath) { +bool EventStream::send_file(const std::string &filepath) { static unsigned char temp_img_buffer[ZM_MAX_IMAGE_SIZE]; int img_buffer_size = 0; uint8_t *img_buffer = temp_img_buffer; - FILE *fdj = NULL; - fdj = fopen(filepath, "rb"); + FILE *fdj = nullptr; + fdj = fopen(filepath.c_str(), "rb"); if ( !fdj ) { - Error("Can't open %s: %s", filepath, strerror(errno)); - return false; + 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 ) { fclose(fdj); /* Close the file handle */ - Error("Failed getting information about file %s: %s", filepath, strerror(errno)); + Error("Failed getting information about file %s: %s", filepath.c_str(), strerror(errno)); return false; } if ( !filestat.st_size ) { @@ -1081,7 +1129,7 @@ bool EventStream::send_file(const char *filepath) { } return send_buffer(img_buffer, img_buffer_size); -} // end bool EventStream::send_file(const char * filepath) +} bool EventStream::send_buffer(uint8_t* buffer, int size) { if ( 0 > fprintf(stdout, "Content-Length: %d\r\n\r\n", size) ) { diff --git a/src/zm_eventstream.h b/src/zm_eventstream.h index 54b7687ab..387748b08 100644 --- a/src/zm_eventstream.h +++ b/src/zm_eventstream.h @@ -26,15 +26,11 @@ #include "zm_storage.h" #include "zm_stream.h" -#ifdef __cplusplus extern "C" { -#endif -#include "libavformat/avformat.h" -#include "libavformat/avio.h" -#include "libavcodec/avcodec.h" -#ifdef __cplusplus +#include +#include +#include } -#endif class EventStream : public StreamBase { public: @@ -44,10 +40,10 @@ class EventStream : public StreamBase { protected: struct FrameData { //unsigned long id; - double timestamp; - double offset; - double delta; - bool in_db; + SystemTimePoint timestamp; + Microseconds offset; + Microseconds delta; + bool in_db; }; struct EventData { @@ -56,21 +52,21 @@ class EventStream : public StreamBase { 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 - time_t start_time; - time_t end_time; - double duration; - double frames_duration; - char path[PATH_MAX]; + SystemTimePoint start_time; + SystemTimePoint end_time; + Microseconds duration; + Microseconds frames_duration; + std::string path; int n_frames; // # of frame rows returned from database FrameData *frames; - char video_file[PATH_MAX]; + std::string video_file; Storage::Schemes scheme; int SaveJPEGs; Monitor::Orientation Orientation; }; protected: - static const int STREAM_PAUSE_WAIT = 250000; // Microseconds + static constexpr Milliseconds STREAM_PAUSE_WAIT = Milliseconds(250); static const StreamMode DEFAULT_MODE = MODE_SINGLE; @@ -78,32 +74,32 @@ class EventStream : public StreamBase { bool forceEventChange; long curr_frame_id; - double curr_stream_time; + SystemTimePoint curr_stream_time; bool send_frame; - struct timeval start; // clock time when started the event + SystemTimePoint 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(int monitor_id, time_t event_time); + bool loadInitialEventData(int monitor_id, SystemTimePoint event_time); bool checkEventLoaded(); void processCommand(const CmdMsg *msg) override; - bool sendFrame(int delta_us); + bool sendFrame(Microseconds delta); public: EventStream() : mode(DEFAULT_MODE), forceEventChange(false), curr_frame_id(0), - curr_stream_time(0.0), send_frame(false), event_data(nullptr), storage(nullptr), ffmpeg_input(nullptr) {} + ~EventStream() { if ( event_data ) { if ( event_data->frames ) { @@ -128,7 +124,7 @@ class EventStream : public StreamBase { void runStream() override; Image *getImage(); private: - bool send_file(const char *filepath); + bool send_file(const std::string &filepath); bool send_buffer(uint8_t * buffer, int size); Storage *storage; FFmpeg_Input *ffmpeg_input; diff --git a/src/zm_ffmpeg.cpp b/src/zm_ffmpeg.cpp index dcb22ed82..bb7362b74 100644 --- a/src/zm_ffmpeg.cpp +++ b/src/zm_ffmpeg.cpp @@ -21,13 +21,12 @@ #include "zm_logger.h" #include "zm_rgb.h" +#include "zm_utils.h" extern "C" { -#include "libavutil/pixdesc.h" +#include } -#if HAVE_LIBAVCODEC || HAVE_LIBAVUTIL || HAVE_LIBSWSCALE - void log_libav_callback(void *ptr, int level, const char *fmt, va_list vargs) { Logger *log = Logger::fetch(); int log_level = 0; @@ -99,7 +98,6 @@ void FFMPEGDeInit() { bInit = false; } -#if HAVE_LIBAVUTIL enum _AVPIXELFORMAT GetFFMPEGPixelFormat(unsigned int p_colours, unsigned p_subpixelorder) { enum _AVPIXELFORMAT pf; @@ -141,59 +139,7 @@ enum _AVPIXELFORMAT GetFFMPEGPixelFormat(unsigned int p_colours, unsigned p_subp return pf; } -/* The following is copied directly from newer ffmpeg. */ -#if LIBAVUTIL_VERSION_CHECK(52, 7, 0, 17, 100) -#else -static int parse_key_value_pair(AVDictionary **pm, const char **buf, - const char *key_val_sep, const char *pairs_sep, - int flags) -{ - char *key = av_get_token(buf, key_val_sep); - char *val = nullptr; - int ret; - if (key && *key && strspn(*buf, key_val_sep)) { - (*buf)++; - val = av_get_token(buf, pairs_sep); - } - - if (key && *key && val && *val) - ret = av_dict_set(pm, key, val, flags); - else - ret = AVERROR(EINVAL); - - av_freep(&key); - av_freep(&val); - - return ret; -} - -int av_dict_parse_string(AVDictionary **pm, const char *str, - const char *key_val_sep, const char *pairs_sep, - int flags) { - if (!str) - return 0; - - /* ignore STRDUP flags */ - flags &= ~(AV_DICT_DONT_STRDUP_KEY | AV_DICT_DONT_STRDUP_VAL); - - while (*str) { - int ret; - if ( (ret = parse_key_value_pair(pm, &str, key_val_sep, pairs_sep, flags)) < 0) - return ret; - - if (*str) - str++; - } - - return 0; -} -#endif -#endif // HAVE_LIBAVUTIL - -#endif // HAVE_LIBAVCODEC || HAVE_LIBAVUTIL || HAVE_LIBSWSCALE - -#if HAVE_LIBAVUTIL #if LIBAVUTIL_VERSION_CHECK(56, 0, 0, 17, 100) int64_t av_rescale_delta(AVRational in_tb, int64_t in_ts, AVRational fs_tb, int duration, int64_t *last, AVRational out_tb){ int64_t a, b, this_thing; @@ -218,7 +164,6 @@ simple_round: return av_rescale_q(this_thing, fs_tb, out_tb); } #endif -#endif static void zm_log_fps(double d, const char *postfix) { uint64_t v = lrintf(d * 100); @@ -233,26 +178,42 @@ static void zm_log_fps(double d, const char *postfix) { } } -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) void zm_dump_codecpar(const AVCodecParameters *par) { - Debug(1, "Dumping codecpar codec_type(%d %s) codec_id(%d %s) codec_tag(%" PRIu32 ") width(%d) height(%d) bit_rate(%" PRIu64 ") format(%d %s)", - par->codec_type, + Debug(1, "Dumping codecpar codec_type %d %s codec_id %d %s codec_tag %" PRIu32 + " width %d height %d bit_rate%" PRIu64 " bpcs %d bprs %d format%d %s" + " extradata:%d:%s profile %d level %d field order %d color_range %d" + " color_primaries %d color_trc %d color_space %d location %d video_delay %d", + static_cast(par->codec_type), av_get_media_type_string(par->codec_type), - par->codec_id, + static_cast(par->codec_id), avcodec_get_name(par->codec_id), par->codec_tag, par->width, par->height, par->bit_rate, + par->bits_per_coded_sample, + par->bits_per_raw_sample, par->format, - (((AVPixelFormat)par->format == AV_PIX_FMT_NONE) ? "none" : av_get_pix_fmt_name((AVPixelFormat)par->format)) + (((AVPixelFormat)par->format == AV_PIX_FMT_NONE) ? "none" : av_get_pix_fmt_name((AVPixelFormat)par->format)), + par->extradata_size, ByteArrayToHexString(nonstd::span{ + par->extradata, + static_cast::size_type>(par->extradata_size) + }).c_str(), + par->profile, + par->level, + static_cast(par->field_order), + static_cast(par->color_range), + static_cast(par->color_primaries), + static_cast(par->color_trc), + static_cast(par->color_space), + static_cast(par->chroma_location), + static_cast(par->video_delay) ); } -#endif void zm_dump_codec(const AVCodecContext *codec) { - Debug(1, "Dumping codec_context codec_type(%d %s) codec_id(%d %s) width(%d) height(%d) timebase(%d/%d) format(%s) " - "gop_size %d max_b_frames %d me_cmp %d me_range %d qmin %d qmax %d", + Debug(1, "Dumping codec_context codec_type %d %s codec_id %d %s width %d height %d timebase %d/%d format %s profile %d level %d " + "gop_size %d has_b_frames %d max_b_frames %d me_cmp %d me_range %d qmin %d qmax %d bit_rate %ld extradata:%d:%s", codec->codec_type, av_get_media_type_string(codec->codec_type), codec->codec_id, @@ -261,17 +222,22 @@ void zm_dump_codec(const AVCodecContext *codec) { codec->height, codec->time_base.num, codec->time_base.den, -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) (codec->pix_fmt == AV_PIX_FMT_NONE ? "none" : av_get_pix_fmt_name(codec->pix_fmt)), -#else - "unsupported on avconv", -#endif + codec->profile, + codec->level, codec->gop_size, + codec->has_b_frames, codec->max_b_frames, codec->me_cmp, codec->me_range, codec->qmin, - codec->qmax + codec->qmax, + codec->bit_rate, + codec->extradata_size, + ByteArrayToHexString(nonstd::span{ + codec->extradata, + static_cast::size_type>(codec->extradata_size) + }).c_str() ); } @@ -281,11 +247,7 @@ void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output) int flags = (is_output ? ic->oformat->flags : ic->iformat->flags); AVStream *st = ic->streams[i]; AVDictionaryEntry *lang = av_dict_get(st->metadata, "language", nullptr, 0); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) AVCodecParameters *codec = st->codecpar; -#else - AVCodecContext *codec = st->codec; -#endif Debug(1, " Stream #%d:%d", index, i); @@ -300,16 +262,10 @@ void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output) st->time_base.num, st->time_base.den ); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) Debug(1, "codec: %s %s", avcodec_get_name(st->codecpar->codec_id), av_get_media_type_string(st->codecpar->codec_type) ); -#else - char buf[256]; - avcodec_string(buf, sizeof(buf), st->codec, is_output); - Debug(1, "codec: %s", buf); -#endif if (st->sample_aspect_ratio.num && // default av_cmp_q(st->sample_aspect_ratio, codec->sample_aspect_ratio) @@ -323,6 +279,9 @@ void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output) Debug(1, ", SAR %d:%d DAR %d:%d", st->sample_aspect_ratio.num, st->sample_aspect_ratio.den, display_aspect_ratio.num, display_aspect_ratio.den); + } else { + Debug(1, ", SAR %d:%d ", + st->sample_aspect_ratio.num, st->sample_aspect_ratio.den); } if (codec->codec_type == AVMEDIA_TYPE_VIDEO) { @@ -336,6 +295,8 @@ void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output) } else if (codec->codec_type == AVMEDIA_TYPE_AUDIO) { Debug(1, "profile %d channels %d sample_rate %d", codec->profile, codec->channels, codec->sample_rate); + } else { + Debug(1, "Unknown codec type %d", codec->codec_type); } if (st->disposition & AV_DISPOSITION_DEFAULT) @@ -393,127 +354,37 @@ enum AVPixelFormat fix_deprecated_pix_fmt(enum AVPixelFormat fmt) { } } -#if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100) -#else -unsigned int zm_av_packet_ref(AVPacket *dst, AVPacket *src) { - av_new_packet(dst,src->size); - memcpy(dst->data, src->data, src->size); - dst->flags = src->flags; - dst->pts = src->pts; - dst->dts = src->dts; - dst->duration = src->duration; - dst->stream_index = src->stream_index; - return 0; -} -const char *avcodec_get_name(enum AVCodecID id) { - const AVCodecDescriptor *cd; - if ( id == AV_CODEC_ID_NONE) - return "none"; - cd = avcodec_descriptor_get(id); - if (cd) - return cd->name; - AVCodec *codec; - codec = avcodec_find_decoder(id); - if (codec) - return codec->name; - codec = avcodec_find_encoder(id); - if (codec) - return codec->name; - return "unknown codec"; -} - -void av_packet_rescale_ts( - AVPacket *pkt, - AVRational src_tb, - AVRational dst_tb - ) { - if ( pkt->pts != AV_NOPTS_VALUE) - pkt->pts = av_rescale_q(pkt->pts, src_tb, dst_tb); - if ( pkt->dts != AV_NOPTS_VALUE) - pkt->dts = av_rescale_q(pkt->dts, src_tb, dst_tb); - if ( pkt->duration != AV_NOPTS_VALUE) - pkt->duration = av_rescale_q(pkt->duration, src_tb, dst_tb); -} -#endif - bool is_video_stream(const AVStream * stream) { - #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) - #else - #if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) - if (stream->codec->codec_type == AVMEDIA_TYPE_VIDEO) - #else - if (stream->codec->codec_type == CODEC_TYPE_VIDEO) - #endif - #endif - { + if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { return true; } - #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - Debug(2, "Not a video type %d != %d", stream->codecpar->codec_type, AVMEDIA_TYPE_VIDEO); - #endif + Debug(2, "Not a video type %d != %d", stream->codecpar->codec_type, AVMEDIA_TYPE_VIDEO); return false; } bool is_video_context(const AVCodecContext *codec_context) { - return - #if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) - (codec_context->codec_type == AVMEDIA_TYPE_VIDEO); - #else - (codec_context->codec_type == CODEC_TYPE_VIDEO); - #endif + return codec_context->codec_type == AVMEDIA_TYPE_VIDEO; } -bool is_audio_stream(const AVStream * stream) { - #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - if (stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) - #else - #if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) - if (stream->codec->codec_type == AVMEDIA_TYPE_AUDIO) - #else - if (stream->codec->codec_type == CODEC_TYPE_AUDIO) - #endif - #endif - { - return true; - } - return false; +bool is_audio_stream(const AVStream *stream) { + return stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO; } bool is_audio_context(const AVCodecContext *codec_context) { - return - #if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) - (codec_context->codec_type == AVMEDIA_TYPE_AUDIO); - #else - (codec_context->codec_type == CODEC_TYPE_AUDIO); - #endif + return codec_context->codec_type == AVMEDIA_TYPE_AUDIO; } int zm_receive_packet(AVCodecContext *context, AVPacket &packet) { -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) int ret = avcodec_receive_packet(context, &packet); if ((ret < 0) and (AVERROR_EOF != ret)) { Error("Error encoding (%d) (%s)", ret, av_err2str(ret)); } return ret; // 1 or 0 -#else - int got_packet = 0; - int ret = avcodec_encode_audio2(context, &packet, nullptr, &got_packet); - if (ret < 0) { - Error("Error encoding (%d) (%s)", ret, av_err2str(ret)); - return ret; - } - return got_packet; // 1 -#endif } // end int zm_receive_packet(AVCodecContext *context, AVPacket &packet) -int zm_send_packet_receive_frame( - AVCodecContext *context, - AVFrame *frame, - AVPacket &packet) { +int zm_send_packet_receive_frame(AVCodecContext *context, AVFrame *frame, AVPacket &packet) { int ret; -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) if ((ret = avcodec_send_packet(context, &packet)) < 0) { Error("Unable to send packet %s, continuing", av_make_error_string(ret).c_str()); @@ -532,74 +403,41 @@ int zm_send_packet_receive_frame( } // In this api the packet is always consumed, so return packet.bytes return packet.size; -# else - int frameComplete = 0; - if (is_video_context(context)) { - ret = zm_avcodec_decode_video(context, frame, &frameComplete, &packet); - Debug(2, "ret from decode_video %d, framecomplete %d", ret, frameComplete); - } else { - ret = avcodec_decode_audio4(context, frame, &frameComplete, &packet); - Debug(2, "ret from decode_audio %d, framecomplete %d", ret, frameComplete); - } - if (ret < 0) { - Error("Unable to decode frame: %s", av_make_error_string(ret).c_str()); - return ret; - } - return frameComplete ? ret : 0; -#endif } // end int zm_send_packet_receive_frame(AVCodecContext *context, AVFrame *frame, AVPacket &packet) /* Returns < 0 on error, 0 if codec not ready, 1 on success */ int zm_send_frame_receive_packet(AVCodecContext *ctx, AVFrame *frame, AVPacket &packet) { int ret; - #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - if (( (ret = avcodec_send_frame(ctx, frame)) < 0 ) and frame) { - Error("Could not send frame (error '%s')", - av_make_error_string(ret).c_str()); - return ret; - } + if (((ret = avcodec_send_frame(ctx, frame)) < 0) and frame) { + Error("Could not send frame (error '%s')", + av_make_error_string(ret).c_str()); + return ret; + } - if ((ret = avcodec_receive_packet(ctx, &packet)) < 0) { - if (AVERROR(EAGAIN) == ret) { - // The codec may need more samples than it has, perfectly valid - Debug(2, "Codec not ready to give us a packet"); - return 0; - } else if (frame) { - // May get EOF if frame is NULL because it signals flushing - Error("Could not recieve packet (error %d = '%s')", ret, - av_make_error_string(ret).c_str()); - } - zm_av_packet_unref(&packet); - return ret; - } - #else - int data_present; - if ((ret = avcodec_encode_audio2( - ctx, &packet, frame, &data_present)) < 0) { - Error("Could not encode frame (error '%s')", - av_make_error_string(ret).c_str()); - zm_av_packet_unref(&packet); - return ret; - } - if (!data_present) { - Debug(2, "Not ready to out a frame yet."); - zm_av_packet_unref(&packet); + if ((ret = avcodec_receive_packet(ctx, &packet)) < 0) { + if (AVERROR(EAGAIN) == ret) { + // The codec may need more samples than it has, perfectly valid + Debug(2, "Codec not ready to give us a packet"); return 0; + } else if (frame) { + // May get EOF if frame is NULL because it signals flushing + Error("Could not recieve packet (error %d = '%s')", ret, + av_make_error_string(ret).c_str()); } - #endif + zm_av_packet_unref(&packet); + return ret; + } return 1; } // end int zm_send_frame_receive_packet void zm_free_codec(AVCodecContext **ctx) { if (*ctx) { avcodec_close(*ctx); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) // We allocate and copy in newer ffmpeg, so need to free it avcodec_free_context(ctx); -#endif - *ctx = NULL; - } // end if + *ctx = nullptr; + } } void zm_packet_copy_rescale_ts(const AVPacket *ipkt, AVPacket *opkt, const AVRational src_tb, const AVRational dst_tb) { @@ -609,19 +447,7 @@ void zm_packet_copy_rescale_ts(const AVPacket *ipkt, AVPacket *opkt, const AVRat av_packet_rescale_ts(opkt, src_tb, dst_tb); } -#if defined(HAVE_LIBSWRESAMPLE) || defined(HAVE_LIBAVRESAMPLE) -int zm_resample_audio( -#if defined(HAVE_LIBSWRESAMPLE) - SwrContext *resample_ctx, -#else -#if defined(HAVE_LIBAVRESAMPLE) - AVAudioResampleContext *resample_ctx, -#endif -#endif - AVFrame *in_frame, - AVFrame *out_frame - ) { -#if defined(HAVE_LIBSWRESAMPLE) +int zm_resample_audio(SwrContext *resample_ctx, AVFrame *in_frame, AVFrame *out_frame) { if (in_frame) { // Resample the in_frame into the audioSampleBuffer until we process the whole // decoded data. Note: pts does not survive resampling or converting @@ -637,56 +463,13 @@ int zm_resample_audio( return 0; } Debug(3, "swr_get_delay %" PRIi64, swr_get_delay(resample_ctx, out_frame->sample_rate)); -#else -#if defined(HAVE_LIBAVRESAMPLE) - if (!in_frame) { - Error("Flushing resampler not supported by AVRESAMPLE"); - return 0; - } - int ret = avresample_convert(resample_ctx, nullptr, 0, 0, in_frame->data, - 0, in_frame->nb_samples); - if (ret < 0) { - Error("Could not resample frame (error '%s')", - av_make_error_string(ret).c_str()); - return 0; - } - int samples_available = avresample_available(resample_ctx); - if (samples_available < out_frame->nb_samples) { - Debug(1, "Not enough samples yet (%d)", samples_available); - return 0; - } - - // Read a frame audio data from the resample fifo - if (avresample_read(resample_ctx, out_frame->data, out_frame->nb_samples) != - out_frame->nb_samples) { - Warning("Error reading resampled audio."); - return 0; - } -#endif -#endif zm_dump_frame(out_frame, "Out frame after resample"); return 1; } -int zm_resample_get_delay( -#if defined(HAVE_LIBSWRESAMPLE) - SwrContext *resample_ctx, -#else -#if defined(HAVE_LIBAVRESAMPLE) - AVAudioResampleContext *resample_ctx, -#endif -#endif - int time_base - ) { -#if defined(HAVE_LIBSWRESAMPLE) +int zm_resample_get_delay(SwrContext *resample_ctx, int time_base) { return swr_get_delay(resample_ctx, time_base); -#else -#if defined(HAVE_LIBAVRESAMPLE) - return avresample_available(resample_ctx); -#endif -#endif } -#endif int zm_add_samples_to_fifo(AVAudioFifo *fifo, AVFrame *frame) { int ret = av_audio_fifo_realloc(fifo, av_audio_fifo_size(fifo) + frame->nb_samples); diff --git a/src/zm_ffmpeg.h b/src/zm_ffmpeg.h index 7431641c6..f3a3296a8 100644 --- a/src/zm_ffmpeg.h +++ b/src/zm_ffmpeg.h @@ -24,26 +24,19 @@ #include "zm_define.h" extern "C" { - -#ifdef HAVE_LIBSWRESAMPLE - #include "libswresample/swresample.h" -#else - #ifdef HAVE_LIBAVRESAMPLE - #include "libavresample/avresample.h" - #endif -#endif +#include // AVUTIL -#if HAVE_LIBAVUTIL_AVUTIL_H -#include "libavutil/avassert.h" +#include #include #include #include #include -#include "libavutil/audio_fifo.h" -#include "libavutil/imgutils.h" +#include +#include +#include #if HAVE_LIBAVUTIL_HWCONTEXT_H - #include "libavutil/hwcontext.h" + #include #endif /* LIBAVUTIL_VERSION_CHECK checks for the right version of libav and FFmpeg @@ -55,75 +48,9 @@ extern "C" { ( (LIBAVUTIL_VERSION_MICRO < 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ (LIBAVUTIL_VERSION_MICRO >= 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) -#if LIBAVUTIL_VERSION_CHECK(50, 29, 0, 29, 0) -#include -#else -#include -#endif - -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) -#include -#endif -#elif HAVE_FFMPEG_AVUTIL_H -#include -#include -#include -#include -#endif /* HAVE_LIBAVUTIL_AVUTIL_H */ - -#if defined(HAVE_LIBAVUTIL_AVUTIL_H) -#if LIBAVUTIL_VERSION_CHECK(51, 42, 0, 74, 100) - #define _AVPIXELFORMAT AVPixelFormat -#else - #define _AVPIXELFORMAT PixelFormat - #define AV_PIX_FMT_NONE PIX_FMT_NONE - #define AV_PIX_FMT_RGB444 PIX_FMT_RGB444 - #define AV_PIX_FMT_RGB555 PIX_FMT_RGB555 - #define AV_PIX_FMT_RGB565 PIX_FMT_RGB565 - #define AV_PIX_FMT_BGR24 PIX_FMT_BGR24 - #define AV_PIX_FMT_RGB24 PIX_FMT_RGB24 - #define AV_PIX_FMT_BGRA PIX_FMT_BGRA - #define AV_PIX_FMT_ARGB PIX_FMT_ARGB - #define AV_PIX_FMT_ABGR PIX_FMT_ABGR - #define AV_PIX_FMT_RGBA PIX_FMT_RGBA - #define AV_PIX_FMT_GRAY8 PIX_FMT_GRAY8 - #define AV_PIX_FMT_YUYV422 PIX_FMT_YUYV422 - #define AV_PIX_FMT_YUV422P PIX_FMT_YUV422P - #define AV_PIX_FMT_YUV411P PIX_FMT_YUV411P - #define AV_PIX_FMT_YUV444P PIX_FMT_YUV444P - #define AV_PIX_FMT_YUV410P PIX_FMT_YUV410P - #define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P - #define AV_PIX_FMT_YUVJ444P PIX_FMT_YUVJ444P - #define AV_PIX_FMT_UYVY422 PIX_FMT_UYVY422 - #define AV_PIX_FMT_YUVJ420P PIX_FMT_YUVJ420P - #define AV_PIX_FMT_YUVJ422P PIX_FMT_YUVJ422P - #define AV_PIX_FMT_UYVY422 PIX_FMT_UYVY422 - #define AV_PIX_FMT_UYYVYY411 PIX_FMT_UYYVYY411 - #define AV_PIX_FMT_BGR565 PIX_FMT_BGR565 - #define AV_PIX_FMT_BGR555 PIX_FMT_BGR555 - #define AV_PIX_FMT_BGR8 PIX_FMT_BGR8 - #define AV_PIX_FMT_BGR4 PIX_FMT_BGR4 - #define AV_PIX_FMT_BGR4_BYTE PIX_FMT_BGR4_BYTE - #define AV_PIX_FMT_RGB8 PIX_FMT_RGB8 - #define AV_PIX_FMT_RGB4 PIX_FMT_RGB4 - #define AV_PIX_FMT_RGB4_BYTE PIX_FMT_RGB4_BYTE - #define AV_PIX_FMT_NV12 PIX_FMT_NV12 - #define AV_PIX_FMT_NV21 PIX_FMT_NV21 - #define AV_PIX_FMT_RGB32_1 PIX_FMT_RGB32_1 - #define AV_PIX_FMT_BGR32_1 PIX_FMT_BGR32_1 - #define AV_PIX_FMT_GRAY16BE PIX_FMT_GRAY16BE - #define AV_PIX_FMT_GRAY16LE PIX_FMT_GRAY16LE - #define AV_PIX_FMT_YUV440P PIX_FMT_YUV440P - #define AV_PIX_FMT_YUVJ440P PIX_FMT_YUVJ440P - #define AV_PIX_FMT_YUVA420P PIX_FMT_YUVA420P - //#define AV_PIX_FMT_VDPAU_H264 PIX_FMT_VDPAU_H264 - //#define AV_PIX_FMT_VDPAU_MPEG1 PIX_FMT_VDPAU_MPEG1 - //#define AV_PIX_FMT_VDPAU_MPEG2 PIX_FMT_VDPAU_MPEG2 -#endif -#endif /* HAVE_LIBAVUTIL_AVUTIL_H */ +#define _AVPIXELFORMAT AVPixelFormat // AVCODEC -#if HAVE_LIBAVCODEC_AVCODEC_H #include /* @@ -136,20 +63,9 @@ extern "C" { ( (LIBAVCODEC_VERSION_MICRO < 100 && LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ (LIBAVCODEC_VERSION_MICRO >= 100 && LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) -#elif HAVE_FFMPEG_AVCODEC_H -#include -#endif /* HAVE_LIBAVCODEC_AVCODEC_H */ - -#if defined(HAVE_LIBAVCODEC_AVCODEC_H) -#if LIBAVCODEC_VERSION_CHECK(54, 25, 0, 51, 100) - #define _AVCODECID AVCodecID -#else - #define _AVCODECID CodecID -#endif -#endif /* HAVE_LIBAVCODEC_AVCODEC_H */ +#define _AVCODECID AVCodecID // AVFORMAT -#if HAVE_LIBAVFORMAT_AVFORMAT_H #include /* LIBAVFORMAT_VERSION_CHECK checks for the right version of libav and FFmpeg @@ -161,28 +77,7 @@ extern "C" { ( (LIBAVFORMAT_VERSION_MICRO < 100 && LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ (LIBAVFORMAT_VERSION_MICRO >= 100 && LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) -#elif HAVE_FFMPEG_AVFORMAT_H -#include -#endif /* HAVE_LIBAVFORMAT_AVFORMAT_H */ - -// AVDEVICE -#if HAVE_LIBAVDEVICE_AVDEVICE_H -#include - -/* LIBAVDEVICE_VERSION_CHECK checks for the right version of libav and FFmpeg - * a is the major version - * b and c the minor and micro versions of libav - * d and e the minor and micro versions of FFmpeg */ -#define LIBAVDEVICE_VERSION_CHECK(a, b, c, d, e) \ - ( (LIBAVDEVICE_VERSION_MICRO < 100 && LIBAVDEVICE_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ - (LIBAVDEVICE_VERSION_MICRO >= 100 && LIBAVDEVICE_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) - -#elif HAVE_FFMPEG_AVDEVICE_H -#include -#endif /* HAVE_LIBAVDEVICE_AVDEVICE_H */ - // SWSCALE -#if HAVE_LIBSWSCALE_SWSCALE_H #include /* LIBSWSCALE_VERSION_CHECK checks for the right version of libav and FFmpeg @@ -193,51 +88,13 @@ extern "C" { ( (LIBSWSCALE_VERSION_MICRO < 100 && LIBSWSCALE_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ (LIBSWSCALE_VERSION_MICRO >= 100 && LIBSWSCALE_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) -#elif HAVE_FFMPEG_SWSCALE_H -#include -#endif /* HAVE_LIBSWSCALE_SWSCALE_H */ - -#ifdef __cplusplus } -#endif - -#if ( HAVE_LIBAVUTIL_AVUTIL_H || HAVE_LIBAVCODEC_AVCODEC_H || HAVE_LIBAVFORMAT_AVFORMAT_H || HAVE_LIBAVDEVICE_AVDEVICE_H ) - -#if !LIBAVFORMAT_VERSION_CHECK(52, 107, 0, 107, 0) - #if defined(AVIO_WRONLY) - #define AVIO_FLAG_WRITE AVIO_WRONLY - #else - #define AVIO_FLAG_WRITE URL_WRONLY - #endif -#endif /* A single function to initialize ffmpeg, to avoid multiple initializations */ void FFMPEGInit(); void FFMPEGDeInit(); -#if HAVE_LIBAVUTIL enum _AVPIXELFORMAT GetFFMPEGPixelFormat(unsigned int p_colours, unsigned p_subpixelorder); -#endif // HAVE_LIBAVUTIL - -#if !LIBAVCODEC_VERSION_CHECK(54, 25, 0, 51, 100) -#define AV_CODEC_ID_NONE CODEC_ID_NONE -#define AV_CODEC_ID_PCM_MULAW CODEC_ID_PCM_MULAW -#define AV_CODEC_ID_PCM_ALAW CODEC_ID_PCM_ALAW -#define AV_CODEC_ID_PCM_S16BE CODEC_ID_PCM_S16BE -#define AV_CODEC_ID_QCELP CODEC_ID_QCELP -#define AV_CODEC_ID_MP2 CODEC_ID_MP2 -#define AV_CODEC_ID_MP3 CODEC_ID_MP3 -#define AV_CODEC_ID_MJPEG CODEC_ID_MJPEG -#define AV_CODEC_ID_H261 CODEC_ID_H261 -#define AV_CODEC_ID_MPEG1VIDEO CODEC_ID_MPEG1VIDEO -#define AV_CODEC_ID_MPEG2VIDEO CODEC_ID_MPEG2VIDEO -#define AV_CODEC_ID_MPEG2TS CODEC_ID_MPEG2TS -#define AV_CODEC_ID_H263 CODEC_ID_H263 -#define AV_CODEC_ID_H264 CODEC_ID_H264 -#define AV_CODEC_ID_MPEG4 CODEC_ID_MPEG4 -#define AV_CODEC_ID_AAC CODEC_ID_AAC -#define AV_CODEC_ID_AMR_NB CODEC_ID_AMR_NB -#endif /* * Some versions of libav does not contain this definition. @@ -250,34 +107,15 @@ enum _AVPIXELFORMAT GetFFMPEGPixelFormat(unsigned int p_colours, unsigned p_subp * C++ friendly version of av_err2str taken from http://libav-users.943685.n4.nabble.com/Libav-user-g-4-7-2-fails-to-compile-av-err2str-td4656417.html. * Newer g++ versions fail with "error: taking address of temporary array" when using native libav version. */ -#ifdef __cplusplus - inline static const std::string av_make_error_string(int errnum) { static char errbuf[AV_ERROR_MAX_STRING_SIZE]; -#if LIBAVUTIL_VERSION_CHECK(50, 13, 0, 13, 0) av_strerror(errnum, errbuf, AV_ERROR_MAX_STRING_SIZE); -#else - snprintf(errbuf, AV_ERROR_MAX_STRING_SIZE, "libav error %d", errnum); -#endif return (std::string)errbuf; } #undef av_err2str #define av_err2str(errnum) av_make_error_string(errnum).c_str() - /* The following is copied directly from newer ffmpeg */ - #if LIBAVUTIL_VERSION_CHECK(52, 7, 0, 17, 100) - #else - int av_dict_parse_string(AVDictionary **pm, const char *str, - const char *key_val_sep, const char *pairs_sep, - int flags); - #endif - -#endif // __cplusplus - - -#endif // ( HAVE_LIBAVUTIL_AVUTIL_H || HAVE_LIBAVCODEC_AVCODEC_H || HAVE_LIBAVFORMAT_AVFORMAT_H || HAVE_LIBAVDEVICE_AVDEVICE_H ) - #ifndef av_rescale_delta /** * Rescale a timestamp while preserving known durations. @@ -305,9 +143,8 @@ static av_always_inline av_const int64_t av_clip64_c(int64_t a, int64_t amin, in void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output); void zm_dump_codec(const AVCodecContext *codec); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) void zm_dump_codecpar(const AVCodecParameters *par); -#endif + #define zm_dump_frame(frame, text) Debug(1, "%s: format %d %s sample_rate %" PRIu32 " nb_samples %d" \ " layout %" PRIu64 " pts %" PRId64, \ text, \ @@ -319,7 +156,6 @@ void zm_dump_codecpar(const AVCodecParameters *par); frame->pts \ ); -#if LIBAVUTIL_VERSION_CHECK(54, 4, 0, 74, 100) #define zm_dump_video_frame(frame, text) Debug(1, "%s: format %d %s %dx%d linesize:%dx%d pts: %" PRId64 " keyframe: %d", \ text, \ frame->format, \ @@ -331,35 +167,10 @@ void zm_dump_codecpar(const AVCodecParameters *par); frame->key_frame \ ); -#else -#define zm_dump_video_frame(frame,text) Debug(1, "%s: format %d %s %dx%d linesize:%dx%d pts: %" PRId64, \ - text, \ - frame->format, \ - "unsupported", \ - frame->width, \ - frame->height, \ - frame->linesize[0], frame->linesize[1], \ - frame->pts \ - ); -#endif - -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) # define AV_PACKET_DURATION_FMT PRId64 -#else -# define AV_PACKET_DURATION_FMT "d" -#endif -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) #define CODEC_TYPE(stream) stream->codecpar->codec_type -#else -#define CODEC_TYPE(stream) stream->codec->codec_type -#endif -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) #define CODEC(stream) stream->codecpar -#else -#define CODEC(stream) stream->codec -#endif - #ifndef DBG_OFF # define ZM_DUMP_PACKET(pkt, text) \ @@ -401,39 +212,10 @@ void zm_dump_codecpar(const AVCodecParameters *par); # define ZM_DUMP_STREAM_PACKET(stream, pkt, text) #endif -#if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100) - #define zm_av_packet_unref(packet) av_packet_unref(packet) - #define zm_av_packet_ref(dst, src) av_packet_ref(dst, src) -#else - unsigned int zm_av_packet_ref( AVPacket *dst, AVPacket *src ); - #define zm_av_packet_unref( packet ) av_free_packet( packet ) - const char *avcodec_get_name(AVCodecID id); +#define zm_av_packet_unref(packet) av_packet_unref(packet) +#define zm_av_packet_ref(dst, src) av_packet_ref(dst, src) - void av_packet_rescale_ts(AVPacket *pkt, AVRational src_tb, AVRational dst_tb); -#endif -#if LIBAVCODEC_VERSION_CHECK(57, 24, 1, 45, 101) -#define zm_avcodec_decode_video(context, rawFrame, frameComplete, packet) \ - avcodec_send_packet(context, packet); \ - avcodec_receive_frame(context, rawFrame); -#else -#if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0) - #define zm_avcodec_decode_video(context, rawFrame, frameComplete, packet) \ - avcodec_decode_video2(context, rawFrame, frameComplete, packet) -#else - #define zm_avcodec_decode_video(context, rawFrame, frameComplete, packet) \ - avcodec_decode_video(context, rawFrame, frameComplete, packet->data, packet->size) -#endif -#endif - -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - #define zm_av_frame_alloc() av_frame_alloc() -#else - #define zm_av_frame_alloc() avcodec_alloc_frame() -#endif - -#if ! LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - #define av_frame_free( input_avframe ) av_freep( input_avframe ) -#endif +#define zm_av_frame_alloc() av_frame_alloc() int check_sample_fmt(AVCodec *codec, enum AVSampleFormat sample_fmt); enum AVPixelFormat fix_deprecated_pix_fmt(enum AVPixelFormat ); @@ -450,30 +232,8 @@ int zm_send_frame_receive_packet(AVCodecContext *context, AVFrame *frame, AVPack void zm_packet_copy_rescale_ts(const AVPacket *ipkt, AVPacket *opkt, const AVRational src_tb, const AVRational dst_tb); -#if defined(HAVE_LIBSWRESAMPLE) || defined(HAVE_LIBAVRESAMPLE) -int zm_resample_audio( -#if defined(HAVE_LIBSWRESAMPLE) - SwrContext *resample_ctx, -#else -#if defined(HAVE_LIBAVRESAMPLE) - AVAudioResampleContext *resample_ctx, -#endif -#endif - AVFrame *in_frame, - AVFrame *out_frame - ); -int zm_resample_get_delay( -#if defined(HAVE_LIBSWRESAMPLE) - SwrContext *resample_ctx, -#else -#if defined(HAVE_LIBAVRESAMPLE) - AVAudioResampleContext *resample_ctx, -#endif -#endif - int time_base - ); - -#endif +int zm_resample_audio(SwrContext *resample_ctx, AVFrame *in_frame, AVFrame *out_frame); +int zm_resample_get_delay(SwrContext *resample_ctx, int time_base); int zm_add_samples_to_fifo(AVAudioFifo *fifo, AVFrame *frame); int zm_get_samples_from_fifo(AVAudioFifo *fifo, AVFrame *frame); diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index b0c1401e7..1484d5234 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -25,20 +25,12 @@ #include "zm_signal.h" #include "zm_utils.h" -#if HAVE_LIBAVFORMAT - extern "C" { -#include "libavutil/time.h" -#if HAVE_LIBAVUTIL_HWCONTEXT_H - #include "libavutil/hwcontext.h" -#endif - -#include "libavutil/pixdesc.h" +#include } -#include +TimePoint start_read_time; -time_t start_read_time; #if HAVE_LIBAVUTIL_HWCONTEXT_H #if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0) static enum AVPixelFormat hw_pix_fmt; @@ -146,9 +138,7 @@ FfmpegCamera::FfmpegCamera( #endif #endif -#if HAVE_LIBSWSCALE mConvertContext = nullptr; -#endif /* Has to be located inside the constructor so other components such as zma * will receive correct colours and subpixel order */ if ( colours == ZM_COLOUR_RGB32 ) { @@ -173,7 +163,7 @@ FfmpegCamera::~FfmpegCamera() { } int FfmpegCamera::PrimeCapture() { - start_read_time = time(nullptr); + start_read_time = std::chrono::steady_clock::now(); if ( mCanCapture ) { Debug(1, "Priming capture from %s, Closing", mPath.c_str()); Close(); @@ -189,10 +179,10 @@ int FfmpegCamera::PreCapture() { return 0; } -int FfmpegCamera::Capture(ZMPacket &zm_packet) { +int FfmpegCamera::Capture(std::shared_ptr &zm_packet) { if (!mCanCapture) return -1; - start_read_time = time(nullptr); + start_read_time = std::chrono::steady_clock::now(); int ret; AVFormatContext *formatContextPtr; @@ -235,15 +225,12 @@ int FfmpegCamera::Capture(ZMPacket &zm_packet) { AVStream *stream = formatContextPtr->streams[packet.stream_index]; ZM_DUMP_STREAM_PACKET(stream, packet, "ffmpeg_camera in"); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - zm_packet.codec_type = stream->codecpar->codec_type; -#else - zm_packet.codec_type = stream->codec->codec_type; -#endif + zm_packet->codec_type = stream->codecpar->codec_type; + bytes += packet.size; - zm_packet.set_packet(&packet); - zm_packet.stream = stream; - zm_packet.pts = av_rescale_q(packet.pts, stream->time_base, AV_TIME_BASE_Q); + zm_packet->set_packet(&packet); + zm_packet->stream = stream; + zm_packet->pts = av_rescale_q(packet.pts, stream->time_base, AV_TIME_BASE_Q); if ( packet.pts != AV_NOPTS_VALUE ) { if ( stream == mVideoStream ) { if (mFirstVideoPTS == AV_NOPTS_VALUE) @@ -272,10 +259,6 @@ int FfmpegCamera::OpenFfmpeg() { error_count = 0; - // Open the input, not necessarily a file -#if !LIBAVFORMAT_VERSION_CHECK(53, 2, 0, 4, 0) - if (av_open_input_file(&mFormatContext, mPath.c_str(), nullptr, 0, nullptr) != 0) -#else // Handle options AVDictionary *opts = nullptr; ret = av_dict_parse_string(&opts, Options().c_str(), "=", ",", 0); @@ -285,7 +268,7 @@ int FfmpegCamera::OpenFfmpeg() { // Set transport method as specified by method field, rtpUni is default std::string protocol = mPath.substr(0, 4); - StringToUpper(protocol); + protocol = StringToUpper(protocol); if ( protocol == "RTSP" ) { const std::string method = Method(); if ( method == "rtpMulti" ) { @@ -316,18 +299,14 @@ int FfmpegCamera::OpenFfmpeg() { ret = avformat_open_input(&mFormatContext, mPath.c_str(), nullptr, &opts); if ( ret != 0 ) -#endif { Error("Unable to open input %s due to: %s", mPath.c_str(), av_make_error_string(ret).c_str()); -#if !LIBAVFORMAT_VERSION_CHECK(53, 17, 0, 25, 0) - av_close_input_file(mFormatContext); -#else + if ( mFormatContext ) { avformat_close_input(&mFormatContext); mFormatContext = nullptr; } -#endif av_dict_free(&opts); return -1; @@ -339,11 +318,8 @@ int FfmpegCamera::OpenFfmpeg() { av_dict_free(&opts); Debug(1, "Finding stream info"); -#if !LIBAVFORMAT_VERSION_CHECK(53, 6, 0, 6, 0) - ret = av_find_stream_info(mFormatContext); -#else ret = avformat_find_stream_info(mFormatContext, nullptr); -#endif + if ( ret < 0 ) { Error("Unable to find stream info from %s due to: %s", mPath.c_str(), av_make_error_string(ret).c_str()); @@ -354,10 +330,10 @@ int FfmpegCamera::OpenFfmpeg() { // The one we want Might not be the first mVideoStreamId = -1; mAudioStreamId = -1; - for ( unsigned int i=0; i < mFormatContext->nb_streams; i++ ) { + for (unsigned int i=0; i < mFormatContext->nb_streams; i++) { AVStream *stream = mFormatContext->streams[i]; - if ( is_video_stream(stream) ) { - if ( mVideoStreamId == -1 ) { + if (is_video_stream(stream)) { + if (mVideoStreamId == -1) { mVideoStreamId = i; mVideoStream = mFormatContext->streams[i]; // if we break, then we won't find the audio stream @@ -365,8 +341,8 @@ int FfmpegCamera::OpenFfmpeg() { } else { Debug(2, "Have another video stream."); } - } else if ( is_audio_stream(stream) ) { - if ( mAudioStreamId == -1 ) { + } else if (is_audio_stream(stream)) { + if (mAudioStreamId == -1) { mAudioStreamId = i; mAudioStream = mFormatContext->streams[i]; } else { @@ -375,7 +351,7 @@ int FfmpegCamera::OpenFfmpeg() { } } // end foreach stream - if ( mVideoStreamId == -1 ) { + if (mVideoStreamId == -1) { Error("Unable to locate video stream in %s", mPath.c_str()); return -1; } @@ -384,60 +360,44 @@ int FfmpegCamera::OpenFfmpeg() { mVideoStreamId, mAudioStreamId); AVCodec *mVideoCodec = nullptr; - if ( mVideoStream-> -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - codecpar -#else - codec -#endif - ->codec_id == AV_CODEC_ID_H264 ) { - if ( (mVideoCodec = avcodec_find_decoder_by_name("h264_mmal")) == nullptr ) { + if (mVideoStream->codecpar->codec_id == AV_CODEC_ID_H264) { + if ((mVideoCodec = avcodec_find_decoder_by_name("h264_mmal")) == nullptr) { Debug(1, "Failed to find decoder (h264_mmal)"); } else { Debug(1, "Success finding decoder (h264_mmal)"); } } - if ( !mVideoCodec ) { - mVideoCodec = avcodec_find_decoder(mVideoStream-> -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - codecpar -#else - codec -#endif - ->codec_id); - if ( !mVideoCodec ) { + if (!mVideoCodec) { + mVideoCodec = avcodec_find_decoder(mVideoStream->codecpar->codec_id); + if (!mVideoCodec) { // Try and get the codec from the codec context Error("Can't find codec for video stream from %s", mPath.c_str()); return -1; } } -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) mVideoCodecContext = avcodec_alloc_context3(mVideoCodec); - avcodec_parameters_to_context(mVideoCodecContext, - mFormatContext->streams[mVideoStreamId]->codecpar); -#else - mVideoCodecContext = mFormatContext->streams[mVideoStreamId]->codec; -#endif + avcodec_parameters_to_context(mVideoCodecContext, mFormatContext->streams[mVideoStreamId]->codecpar); + #ifdef CODEC_FLAG2_FAST mVideoCodecContext->flags2 |= CODEC_FLAG2_FAST | CODEC_FLAG_LOW_DELAY; #endif zm_dump_stream_format(mFormatContext, mVideoStreamId, 0, 0); - if ( use_hwaccel && (hwaccel_name != "") ) { + if (use_hwaccel && (hwaccel_name != "")) { #if HAVE_LIBAVUTIL_HWCONTEXT_H - // 3.2 doesn't seem to have all the bits in place, so let's require 3.3 and up - #if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0) + // 3.2 doesn't seem to have all the bits in place, so let's require 3.4 and up + #if LIBAVCODEC_VERSION_CHECK(57, 107, 0, 107, 0) // Print out available types enum AVHWDeviceType type = AV_HWDEVICE_TYPE_NONE; - while ( (type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE ) + while ((type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE) Debug(1, "%s", av_hwdevice_get_type_name(type)); const char *hw_name = hwaccel_name.c_str(); type = av_hwdevice_find_type_by_name(hw_name); - if ( type == AV_HWDEVICE_TYPE_NONE ) { + if (type == AV_HWDEVICE_TYPE_NONE) { Debug(1, "Device type %s is not supported.", hw_name); } else { Debug(1, "Found hwdevice %s", av_hwdevice_get_type_name(type)); @@ -445,14 +405,14 @@ int FfmpegCamera::OpenFfmpeg() { #if LIBAVUTIL_VERSION_CHECK(56, 22, 0, 14, 0) // Get hw_pix_fmt - for ( int i = 0;; i++ ) { + for (int i = 0;; i++) { const AVCodecHWConfig *config = avcodec_get_hw_config(mVideoCodec, i); - if ( !config ) { + if (!config) { Debug(1, "Decoder %s does not support config %d.", mVideoCodec->name, i); break; } - if ( (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) + if ((config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) && (config->device_type == type) ) { hw_pix_fmt = config->pix_fmt; @@ -471,7 +431,7 @@ int FfmpegCamera::OpenFfmpeg() { #else hw_pix_fmt = find_fmt_by_hw_type(type); #endif - if ( hw_pix_fmt != AV_PIX_FMT_NONE ) { + if (hw_pix_fmt != AV_PIX_FMT_NONE) { Debug(1, "Selected hw_pix_fmt %d %s", hw_pix_fmt, av_get_pix_fmt_name(hw_pix_fmt)); @@ -484,7 +444,7 @@ int FfmpegCamera::OpenFfmpeg() { if ( ret < 0 and hwaccel_device != "" ) { ret = av_hwdevice_ctx_create(&hw_device_ctx, type, nullptr, nullptr, 0); } - if ( ret < 0 ) { + if (ret < 0) { Error("Failed to create hwaccel device. %s", av_make_error_string(ret).c_str()); hw_pix_fmt = AV_PIX_FMT_NONE; } else { @@ -503,16 +463,13 @@ int FfmpegCamera::OpenFfmpeg() { #endif } // end if hwaccel_name -#if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0) - ret = avcodec_open(mVideoCodecContext, mVideoCodec); -#else ret = avcodec_open2(mVideoCodecContext, mVideoCodec, &opts); -#endif + e = nullptr; - while ( (e = av_dict_get(opts, "", e, AV_DICT_IGNORE_SUFFIX)) != nullptr ) { + while ((e = av_dict_get(opts, "", e, AV_DICT_IGNORE_SUFFIX)) != nullptr) { Warning("Option %s not recognized by ffmpeg", e->key); } - if ( ret < 0 ) { + if (ret < 0) { Error("Unable to open codec for video stream from %s", mPath.c_str()); av_dict_free(&opts); return -1; @@ -533,33 +490,15 @@ int FfmpegCamera::OpenFfmpeg() { if ( mAudioStreamId >= 0 ) { AVCodec *mAudioCodec = nullptr; - if ( (mAudioCodec = avcodec_find_decoder( -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - mAudioStream->codecpar->codec_id -#else - mAudioStream->codec->codec_id -#endif - )) == nullptr ) { + if (!(mAudioCodec = avcodec_find_decoder(mAudioStream->codecpar->codec_id))) { Debug(1, "Can't find codec for audio stream from %s", mPath.c_str()); } else { -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) mAudioCodecContext = avcodec_alloc_context3(mAudioCodec); - avcodec_parameters_to_context( - mAudioCodecContext, - mAudioStream->codecpar - ); -#else - mAudioCodecContext = mAudioStream->codec; -#endif + avcodec_parameters_to_context(mAudioCodecContext, mAudioStream->codecpar); zm_dump_stream_format((mSecondFormatContext?mSecondFormatContext:mFormatContext), mAudioStreamId, 0, 0); // Open the codec -#if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0) - if ( avcodec_open(mAudioCodecContext, mAudioCodec) < 0 ) -#else - if ( avcodec_open2(mAudioCodecContext, mAudioCodec, nullptr) < 0 ) -#endif - { + if (avcodec_open2(mAudioCodecContext, mAudioCodec, nullptr) < 0) { Error("Unable to open codec for audio stream from %s", mPath.c_str()); return -1; } // end if opened @@ -585,16 +524,12 @@ int FfmpegCamera::Close() { if ( mVideoCodecContext ) { avcodec_close(mVideoCodecContext); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) avcodec_free_context(&mVideoCodecContext); -#endif mVideoCodecContext = nullptr; // Freed by av_close_input_file } if ( mAudioCodecContext ) { avcodec_close(mAudioCodecContext); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) avcodec_free_context(&mAudioCodecContext); -#endif mAudioCodecContext = nullptr; // Freed by av_close_input_file } @@ -605,11 +540,7 @@ int FfmpegCamera::Close() { #endif if ( mFormatContext ) { -#if !LIBAVFORMAT_VERSION_CHECK(53, 17, 0, 25, 0) - av_close_input_file(mFormatContext); -#else avformat_close_input(&mFormatContext); -#endif mFormatContext = nullptr; } @@ -621,14 +552,13 @@ int FfmpegCamera::FfmpegInterruptCallback(void *ctx) { Debug(1, "Received terminate in cb"); return zm_terminate; } - time_t now = time(nullptr); - if (now - start_read_time > 10) { - Debug(1, "timeout in ffmpeg camera now %" PRIi64 " - %" PRIi64 " > 10", - static_cast(now), - static_cast(start_read_time)); + + TimePoint now = std::chrono::steady_clock::now(); + if (now - start_read_time > Seconds(10)) { + Debug(1, "timeout in ffmpeg camera now %" PRIi64 " - %" PRIi64 " > 10 s", + static_cast(std::chrono::duration_cast(now.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(start_read_time.time_since_epoch()).count())); return 1; } return 0; } - -#endif // HAVE_LIBAVFORMAT diff --git a/src/zm_ffmpeg_camera.h b/src/zm_ffmpeg_camera.h index e6f55b634..e320d3b31 100644 --- a/src/zm_ffmpeg_camera.h +++ b/src/zm_ffmpeg_camera.h @@ -22,6 +22,8 @@ #include "zm_camera.h" +#include + #if HAVE_LIBAVUTIL_HWCONTEXT_H typedef struct DecodeContext { AVBufferRef *hw_device_ref; @@ -60,9 +62,7 @@ class FfmpegCamera : public Camera { int Close() override; bool mCanCapture; -#if HAVE_LIBSWSCALE struct SwsContext *mConvertContext; -#endif int error_count; @@ -93,7 +93,7 @@ class FfmpegCamera : public Camera { int PrimeCapture() override; int PreCapture() override; - int Capture(ZMPacket &p) override; + int Capture(std::shared_ptr &p) override; int PostCapture() override; private: static int FfmpegInterruptCallback(void*ctx); diff --git a/src/zm_ffmpeg_input.cpp b/src/zm_ffmpeg_input.cpp index 7b78cc598..17ce88eab 100644 --- a/src/zm_ffmpeg_input.cpp +++ b/src/zm_ffmpeg_input.cpp @@ -89,12 +89,8 @@ int FFmpeg_Input::Open(const char *filepath) { } streams[i].frame_count = 0; -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) streams[i].context = avcodec_alloc_context3(nullptr); avcodec_parameters_to_context(streams[i].context, input_format_context->streams[i]->codecpar); -#else - streams[i].context = input_format_context->streams[i]->codec; -#endif if ( !(streams[i].codec = avcodec_find_decoder(streams[i].context->codec_id)) ) { Error("Could not find input codec"); @@ -108,9 +104,7 @@ int FFmpeg_Input::Open(const char *filepath) { if ( error < 0 ) { Error("Could not open input codec (error '%s')", av_make_error_string(error).c_str()); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) avcodec_free_context(&streams[i].context); -#endif avformat_close_input(&input_format_context); input_format_context = nullptr; return error; @@ -129,21 +123,15 @@ int FFmpeg_Input::Close( ) { if ( streams ) { for ( unsigned int i = 0; i < input_format_context->nb_streams; i += 1 ) { avcodec_close(streams[i].context); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) avcodec_free_context(&streams[i].context); streams[i].context = nullptr; -#endif } delete[] streams; streams = nullptr; } if ( input_format_context ) { -#if !LIBAVFORMAT_VERSION_CHECK(53, 17, 0, 25, 0) - av_close_input_file(input_format_context); -#else avformat_close_input(&input_format_context); -#endif input_format_context = nullptr; } return 1; @@ -281,4 +269,4 @@ AVFrame *FFmpeg_Input::get_frame(int stream_id, double at) { } return get_frame(stream_id); -} // end AVFrame *FFmpeg_Input::get_frame( int stream_id, struct timeval at) +} diff --git a/src/zm_ffmpeg_input.h b/src/zm_ffmpeg_input.h index 5ca5f07dc..6ccc87682 100644 --- a/src/zm_ffmpeg_input.h +++ b/src/zm_ffmpeg_input.h @@ -3,17 +3,11 @@ #include "zm_define.h" -#ifdef __cplusplus extern "C" { -#endif - -#include "libavformat/avformat.h" -#include "libavformat/avio.h" -#include "libavcodec/avcodec.h" - -#ifdef __cplusplus +#include +#include +#include } -#endif class FFmpeg_Input { diff --git a/src/zm_ffmpeg_output.cpp b/src/zm_ffmpeg_output.cpp index b031399b9..ec677f265 100644 --- a/src/zm_ffmpeg_output.cpp +++ b/src/zm_ffmpeg_output.cpp @@ -52,12 +52,8 @@ int FFmpeg_Output::Open( const char *filepath ) { } streams[i].frame_count = 0; -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - streams[i].context = avcodec_alloc_context3( NULL ); - avcodec_parameters_to_context( streams[i].context, input_format_context->streams[i]->codecpar ); -#else - streams[i].context = input_format_context->streams[i]->codec; -#endif + streams[i].context = avcodec_alloc_context3(nullptr); + avcodec_parameters_to_context(streams[i].context, input_format_context->streams[i]->codecpar); if ( !(streams[i].codec = avcodec_find_decoder(streams[i].context->codec_id)) ) { Error( "Could not find input codec\n"); @@ -70,9 +66,7 @@ int FFmpeg_Output::Open( const char *filepath ) { if ((error = avcodec_open2( streams[i].context, streams[i].codec, NULL)) < 0) { Error( "Could not open input codec (error '%s')\n", av_make_error_string(error).c_str() ); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) avcodec_free_context( &streams[i].context ); -#endif avformat_close_input(&input_format_context); return error; } @@ -117,7 +111,6 @@ AVFrame *FFmpeg_Output::get_frame( int stream_id ) { AVCodecContext *context = streams[packet.stream_index].context; -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) ret = avcodec_send_packet( context, &packet ); if ( ret < 0 ) { av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); @@ -160,15 +153,6 @@ AVFrame *FFmpeg_Output::get_frame( int stream_id ) { #endif frameComplete = 1; -# else - ret = zm_avcodec_decode_video( streams[packet.stream_index].context, frame, &frameComplete, &packet ); - if ( ret < 0 ) { - av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); - Error( "Unable to decode frame at frame %d: %s, continuing", streams[packet.stream_index].frame_count, errbuf ); - zm_av_packet_unref( &packet ); - continue; - } -#endif } // end if it's the right stream zm_av_packet_unref( &packet ); diff --git a/src/zm_ffmpeg_output.h b/src/zm_ffmpeg_output.h index 76afad0d7..9ab4ea403 100644 --- a/src/zm_ffmpeg_output.h +++ b/src/zm_ffmpeg_output.h @@ -1,17 +1,11 @@ #ifndef ZM_FFMPEG_INPUT_H #define ZM_FFMPEG_INPUT_H -#ifdef __cplusplus extern "C" { -#endif - -#include "libavformat/avformat.h" -#include "libavformat/avio.h" -#include "libavcodec/avcodec.h" - -#ifdef __cplusplus +#include +#include +#include } -#endif class FFmpeg_Output { diff --git a/src/zm_fifo.cpp b/src/zm_fifo.cpp index 731909166..319c35c31 100644 --- a/src/zm_fifo.cpp +++ b/src/zm_fifo.cpp @@ -29,33 +29,26 @@ #define RAW_BUFFER 512 #define PIPE_SIZE 1024*1024 -void Fifo::file_create_if_missing( - const char * path, - bool is_fifo, - bool delete_fake_fifo - ) { - static struct stat st; - if ( stat(path, &st) == 0 ) { - if ( (!is_fifo) || S_ISFIFO(st.st_mode) || !delete_fake_fifo ) +void Fifo::file_create_if_missing(const std::string &path, bool is_fifo, bool delete_fake_fifo) { + struct stat st = {}; + + if (stat(path.c_str(), &st) == 0) { + if ((!is_fifo) || S_ISFIFO(st.st_mode) || !delete_fake_fifo) return; - Debug(5, "Supposed to be a fifo pipe but isn't, unlinking: %s", path); - unlink(path); + Debug(5, "Supposed to be a fifo pipe but isn't, unlinking: %s", path.c_str()); + unlink(path.c_str()); } - int fd; if (!is_fifo) { - Debug(5, "Creating non fifo file as requested: %s", path); - fd = ::open(path, O_CREAT|O_WRONLY, S_IRUSR|S_IWUSR); + Debug(5, "Creating non fifo file as requested: %s", path.c_str()); + int fd = ::open(path.c_str(), O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR); ::close(fd); return; } - Debug(5, "Making fifo file of: %s", path); - mkfifo(path, S_IRUSR|S_IWUSR); + Debug(5, "Making fifo file of: %s", path.c_str()); + mkfifo(path.c_str(), S_IRUSR | S_IWUSR); } -void Fifo::fifo_create_if_missing( - const char * path, - bool delete_fake_fifo - ) { +void Fifo::fifo_create_if_missing(const std::string &path, bool delete_fake_fifo) { file_create_if_missing(path, true, delete_fake_fifo); } @@ -63,7 +56,8 @@ Fifo::~Fifo() { close(); } bool Fifo::open() { - fifo_create_if_missing(path.c_str()); + fifo_create_if_missing(path); + if (!on_blocking_abort) { if ( (outfile = fopen(path.c_str(), "wb")) == nullptr ) { Error("Can't open %s for writing: %s", path.c_str(), strerror(errno)); diff --git a/src/zm_fifo.h b/src/zm_fifo.h index bd7f23490..5a660c6bb 100644 --- a/src/zm_fifo.h +++ b/src/zm_fifo.h @@ -32,11 +32,8 @@ class Fifo { int raw_fd; public: - static void file_create_if_missing( - const char * path, - bool is_fifo, - bool delete_fake_fifo = true - ); + static void file_create_if_missing(const std::string &path, bool is_fifo, bool delete_fake_fifo = true); + static void fifo_create_if_missing(const std::string &path, bool delete_fake_fifo = true); Fifo() : on_blocking_abort(true), @@ -51,12 +48,6 @@ class Fifo { {} ~Fifo(); - static void fifo_create_if_missing( - const char * path, - bool delete_fake_fifo = true); - - - static bool writePacket(std::string filename, const ZMPacket &packet); static bool write(std::string filename, uint8_t *data, size_t size); diff --git a/src/zm_fifo_debug.cpp b/src/zm_fifo_debug.cpp index bb82f5ec8..1b1950e3c 100644 --- a/src/zm_fifo_debug.cpp +++ b/src/zm_fifo_debug.cpp @@ -28,7 +28,7 @@ #define RAW_BUFFER 512 static bool zm_fifodbg_inited = false; FILE *zm_fifodbg_log_fd = nullptr; -char zm_fifodbg_log[PATH_MAX] = ""; +std::string zm_fifodbg_log; static bool zmFifoDbgOpen() { if ( zm_fifodbg_log_fd ) @@ -36,7 +36,7 @@ static bool zmFifoDbgOpen() { zm_fifodbg_log_fd = nullptr; signal(SIGPIPE, SIG_IGN); Fifo::fifo_create_if_missing(zm_fifodbg_log); - int fd = open(zm_fifodbg_log, O_WRONLY|O_NONBLOCK|O_TRUNC); + int fd = open(zm_fifodbg_log.c_str(), O_WRONLY | O_NONBLOCK | O_TRUNC); if ( fd < 0 ) return false; int res = flock(fd, LOCK_EX | LOCK_NB); @@ -54,8 +54,7 @@ static bool zmFifoDbgOpen() { int zmFifoDbgInit(Monitor *monitor) { zm_fifodbg_inited = true; - snprintf(zm_fifodbg_log, sizeof(zm_fifodbg_log), "%s/dbgpipe-%u.log", - staticConfig.PATH_SOCKS.c_str(), monitor->Id()); + zm_fifodbg_log = stringtf("%s/dbgpipe-%u.log", staticConfig.PATH_SOCKS.c_str(), monitor->Id()); zmFifoDbgOpen(); return 1; } diff --git a/src/zm_fifo_stream.cpp b/src/zm_fifo_stream.cpp index 3035ad6d0..51d88b663 100644 --- a/src/zm_fifo_stream.cpp +++ b/src/zm_fifo_stream.cpp @@ -29,9 +29,9 @@ #define RAW_BUFFER 512 bool FifoStream::sendRAWFrames() { static unsigned char buffer[RAW_BUFFER]; - int fd = open(stream_path, O_RDONLY); + int fd = open(stream_path.c_str(), O_RDONLY); if ( fd < 0 ) { - Error("Can't open %s: %s", stream_path, strerror(errno)); + Error("Can't open %s: %s", stream_path.c_str(), strerror(errno)); return false; } while ( (bytes_read = read(fd, buffer, RAW_BUFFER)) ) { @@ -56,9 +56,9 @@ bool FifoStream::sendRAWFrames() { bool FifoStream::sendMJEGFrames() { static unsigned char buffer[ZM_MAX_IMAGE_SIZE]; - int fd = open(stream_path, O_RDONLY); + int fd = open(stream_path.c_str(), O_RDONLY); if ( fd < 0 ) { - Error("Can't open %s: %s", stream_path, strerror(errno)); + Error("Can't open %s: %s", stream_path.c_str(), strerror(errno)); return false; } total_read = 0; @@ -92,33 +92,31 @@ bool FifoStream::sendMJEGFrames() { } fprintf(stdout, "\r\n\r\n"); fflush(stdout); - last_frame_sent = TV_2_FLOAT(now); + last_frame_sent = now; frame_count++; return true; } -void FifoStream::setStreamStart(const char * path) { - stream_path = strdup(path); +void FifoStream::setStreamStart(const std::string &path) { + stream_path = path; } -void FifoStream::setStreamStart(int monitor_id, const char * format) { - char diag_path[PATH_MAX]; +void FifoStream::setStreamStart(int monitor_id, const char *format) { + std::string diag_path; std::shared_ptr monitor = Monitor::Load(monitor_id, false, Monitor::QUERY); - if ( !strcmp(format, "reference") ) { - snprintf(diag_path, sizeof(diag_path), "%s/diagpipe-r-%u.jpg", - staticConfig.PATH_SOCKS.c_str(), monitor->Id()); + if (!strcmp(format, "reference")) { + diag_path = stringtf("%s/diagpipe-r-%u.jpg", staticConfig.PATH_SOCKS.c_str(), monitor->Id()); stream_type = MJPEG; - } else if ( !strcmp(format, "delta") ) { - snprintf(diag_path, sizeof(diag_path), "%s/diagpipe-d-%u.jpg", - staticConfig.PATH_SOCKS.c_str(), monitor->Id()); + } else if (!strcmp(format, "delta")) { + diag_path = stringtf("%s/diagpipe-d-%u.jpg", staticConfig.PATH_SOCKS.c_str(), monitor->Id()); stream_type = MJPEG; } else { - if ( strcmp(format, "raw") ) { + if (strcmp(format, "raw")) { Warning("Unknown or unspecified format. Defaulting to raw"); } - snprintf(diag_path, sizeof(diag_path), "%s/dbgpipe-%u.log", - staticConfig.PATH_SOCKS.c_str(), monitor->Id()); + + diag_path = stringtf("%s/dbgpipe-%u.log", staticConfig.PATH_SOCKS.c_str(), monitor->Id()); stream_type = RAW; } @@ -126,45 +124,48 @@ void FifoStream::setStreamStart(int monitor_id, const char * format) { } void FifoStream::runStream() { - if ( stream_type == MJPEG ) { + if (stream_type == MJPEG) { fprintf(stdout, "Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY "\r\n\r\n"); } else { fprintf(stdout, "Content-Type: text/html\r\n\r\n"); } /* only 1 person can read from a fifo at a time, so use a lock */ - char lock_file[PATH_MAX]; - snprintf(lock_file, sizeof(lock_file), "%s.rlock", stream_path); + std::string lock_file = stringtf("%s.rlock", stream_path.c_str()); Fifo::file_create_if_missing(lock_file, false); - Debug(1, "Locking %s", lock_file); + Debug(1, "Locking %s", lock_file.c_str()); - int fd_lock = open(lock_file, O_RDONLY); - if ( fd_lock < 0 ) { - Error("Can't open %s: %s", lock_file, strerror(errno)); + int fd_lock = open(lock_file.c_str(), O_RDONLY); + if (fd_lock < 0) { + Error("Can't open %s: %s", lock_file.c_str(), strerror(errno)); return; } + int res = flock(fd_lock, LOCK_EX | LOCK_NB); - while ( (res < 0 and errno == EAGAIN) and (! zm_terminate) ) { - Warning("Flocking problem on %s: - %s", lock_file, strerror(errno)); + while ((res < 0 and errno == EAGAIN) and (!zm_terminate)) { + Warning("Flocking problem on %s: - %s", lock_file.c_str(), strerror(errno)); sleep(1); res = flock(fd_lock, LOCK_EX | LOCK_NB); } - if ( res < 0 ) { - Error("Flocking problem on %d != %d %s: - %s", EAGAIN, res, lock_file, strerror(errno)); + + if (res < 0) { + Error("Flocking problem on %d != %d %s: - %s", EAGAIN, res, lock_file.c_str(), strerror(errno)); close(fd_lock); return; } - while ( !zm_terminate ) { - gettimeofday(&now, nullptr); + while (!zm_terminate) { + now = std::chrono::system_clock::now(); checkCommandQueue(); - if ( stream_type == MJPEG ) { - if ( !sendMJEGFrames() ) + + if (stream_type == MJPEG) { + if (!sendMJEGFrames()) zm_terminate = true; } else { - if ( !sendRAWFrames() ) + if (!sendRAWFrames()) zm_terminate = true; } } + close(fd_lock); } diff --git a/src/zm_fifo_stream.h b/src/zm_fifo_stream.h index 446108d2c..911980d48 100644 --- a/src/zm_fifo_stream.h +++ b/src/zm_fifo_stream.h @@ -25,15 +25,10 @@ class Monitor; class FifoStream : public StreamBase { private: - char * stream_path; + std::string stream_path; int total_read; int bytes_read; unsigned int frame_count; - static void file_create_if_missing( - const char * path, - bool is_fifo, - bool delete_fake_fifo = true - ); protected: typedef enum { UNKNOWN, MJPEG, RAW } StreamType; @@ -44,13 +39,13 @@ class FifoStream : public StreamBase { public: FifoStream() : - stream_path(nullptr), total_read(0), bytes_read(0), frame_count(0), stream_type(UNKNOWN) {} - void setStreamStart(const char * path); + + void setStreamStart(const std::string &path); void setStreamStart(int monitor_id, const char * format); void runStream() override; }; diff --git a/src/zm_file_camera.cpp b/src/zm_file_camera.cpp index af494ce38..fd07515ad 100644 --- a/src/zm_file_camera.cpp +++ b/src/zm_file_camera.cpp @@ -21,7 +21,6 @@ #include "zm_packet.h" #include -#include FileCamera::FileCamera( const Monitor *monitor, @@ -49,20 +48,20 @@ FileCamera::FileCamera( p_capture, p_record_audio) { - strncpy( path, p_path, sizeof(path)-1 ); - if ( capture ) { + path = std::string(p_path); + if (capture) { Initialise(); } } FileCamera::~FileCamera() { - if ( capture ) { + if (capture) { Terminate(); } } void FileCamera::Initialise() { - if ( !path[0] ) { + if (path.empty()) { Fatal("No path specified for file image"); } } @@ -71,9 +70,9 @@ void FileCamera::Terminate() { } int FileCamera::PreCapture() { - struct stat statbuf; - if ( stat(path, &statbuf) < 0 ) { - Error("Can't stat %s: %s", path, strerror(errno)); + struct stat statbuf = {}; + if (stat(path.c_str(), &statbuf) < 0) { + Error("Can't stat %s: %s", path.c_str(), strerror(errno)); return -1; } bytes += statbuf.st_size; @@ -81,14 +80,14 @@ int FileCamera::PreCapture() { // This waits until 1 second has passed since it was modified. Effectively limiting fps to 60. // Which is kinda bogus. If we were writing to this jpg constantly faster than we are monitoring it here // we would never break out of this loop - while ( (time(nullptr) - statbuf.st_mtime) < 1 ) { - usleep(100000); + while ((time(nullptr) - statbuf.st_mtime) < 1) { + std::this_thread::sleep_for(Milliseconds(100)); } return 0; } -int FileCamera::Capture( ZMPacket &zm_packet ) { - return zm_packet.image->ReadJpeg(path, colours, subpixelorder) ? 1 : -1; +int FileCamera::Capture(std::shared_ptr &zm_packet) { + return zm_packet->image->ReadJpeg(path, colours, subpixelorder) ? 1 : -1; } int FileCamera::PostCapture() { diff --git a/src/zm_file_camera.h b/src/zm_file_camera.h index 11a5cfe3f..cc5d38acd 100644 --- a/src/zm_file_camera.h +++ b/src/zm_file_camera.h @@ -27,33 +27,31 @@ // accessed using a single file which contains the latest jpeg data // class FileCamera : public Camera { -protected: - char path[PATH_MAX]; - -public: - FileCamera( - const Monitor *monitor, - const char *p_path, - int p_width, - int p_height, - int p_colours, - int p_brightness, - int p_contrast, - int p_hue, - int p_colour, - bool p_capture, - bool p_record_audio - ); - ~FileCamera(); - - const char *Path() const { return path; } + public: + FileCamera(const Monitor *monitor, + const char *p_path, + int p_width, + int p_height, + int p_colours, + int p_brightness, + int p_contrast, + int p_hue, + int p_colour, + bool p_capture, + bool p_record_audio); + ~FileCamera() override; void Initialise(); void Terminate(); int PreCapture() override; - int Capture(ZMPacket &p) override; + int Capture(std::shared_ptr &p) override; int PostCapture() override; int Close() override { return 0; }; + + const std::string &Path() const { return path; } + + private: + std::string path; }; #endif // ZM_FILE_CAMERA_H diff --git a/src/zm_font.h b/src/zm_font.h index b606108b2..e1f03d7d1 100644 --- a/src/zm_font.h +++ b/src/zm_font.h @@ -20,7 +20,6 @@ #include "zm_define.h" #include -#include #include "span.hpp" #include #include diff --git a/src/zm_frame.cpp b/src/zm_frame.cpp index f6c0e296d..aadd20f8d 100644 --- a/src/zm_frame.cpp +++ b/src/zm_frame.cpp @@ -3,8 +3,8 @@ Frame::Frame(event_id_t p_event_id, int p_frame_id, FrameType p_type, - struct timeval p_timestamp, - struct DeltaTimeval &p_delta, + SystemTimePoint p_timestamp, + Microseconds p_delta, int p_score, std::vector p_stats) : event_id(p_event_id), diff --git a/src/zm_frame.h b/src/zm_frame.h index 8f5a7e9cc..77c7a0398 100644 --- a/src/zm_frame.h +++ b/src/zm_frame.h @@ -23,8 +23,6 @@ #include "zm_event.h" #include "zm_time.h" #include "zm_zone.h" - -#include #include enum FrameType { @@ -41,8 +39,8 @@ class Frame { Frame(event_id_t p_event_id, int p_frame_id, FrameType p_type, - struct timeval p_timestamp, - struct DeltaTimeval &p_delta, + SystemTimePoint p_timestamp, + Microseconds p_delta, int p_score, std::vector p_stats ); @@ -50,8 +48,8 @@ class Frame { event_id_t event_id; int frame_id; FrameType type; - struct timeval timestamp; - struct DeltaTimeval delta; + SystemTimePoint timestamp; + Microseconds delta; int score; std::vector zone_stats; }; diff --git a/src/zm_group.cpp b/src/zm_group.cpp index b2fc43e5f..9ed7a1093 100644 --- a/src/zm_group.cpp +++ b/src/zm_group.cpp @@ -20,6 +20,7 @@ #include "zm_group.h" #include "zm_logger.h" +#include "zm_utils.h" #include Group::Group() { @@ -39,27 +40,27 @@ Group::Group(const MYSQL_ROW &dbrow) { /* If a zero or invalid p_id is passed, then the old default path will be assumed. */ Group::Group(unsigned int p_id) { - id = 0; + id = 0; - if ( p_id ) { - char sql[ZM_SQL_SML_BUFSIZ]; - snprintf(sql, sizeof(sql), "SELECT `Id`, `ParentId`, `Name` FROM `Group` WHERE `Id`=%u", p_id); - Debug(2,"Loading Group for %u using %s", p_id, sql); - zmDbRow dbrow; - if ( !dbrow.fetch(sql) ) { - Error("Unable to load group for id %u: %s", p_id, mysql_error(&dbconn)); - } else { - unsigned int index = 0; - id = atoi(dbrow[index++]); - parent_id = dbrow[index] ? atoi(dbrow[index]): 0; index++; - strncpy(name, dbrow[index++], sizeof(name)-1); - Debug(1, "Loaded Group area %d '%s'", id, this->Name()); - } - } - if ( ! id ) { - Debug(1,"No id passed to Group constructor."); - strcpy(name, "Default"); - } + if (p_id) { + std::string sql = stringtf("SELECT `Id`, `ParentId`, `Name` FROM `Group` WHERE `Id`=%u", p_id); + Debug(2, "Loading Group for %u using %s", p_id, sql.c_str()); + zmDbRow dbrow; + if (!dbrow.fetch(sql)) { + Error("Unable to load group for id %u: %s", p_id, mysql_error(&dbconn)); + } else { + unsigned int index = 0; + id = atoi(dbrow[index++]); + parent_id = dbrow[index] ? atoi(dbrow[index]) : 0; + index++; + strncpy(name, dbrow[index++], sizeof(name) - 1); + Debug(1, "Loaded Group area %d '%s'", id, this->Name()); + } + } + if (!id) { + Debug(1, "No id passed to Group constructor."); + strcpy(name, "Default"); + } } Group::~Group() { diff --git a/src/zm_image.cpp b/src/zm_image.cpp index 909b4db15..a837138bc 100644 --- a/src/zm_image.cpp +++ b/src/zm_image.cpp @@ -226,14 +226,9 @@ Image::Image(const AVFrame *frame) { imagePixFormat = AV_PIX_FMT_RGBA; //(AVPixelFormat)frame->format; -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) 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); -#else - linesize = FFALIGN(av_image_get_linesize(AV_PIX_FMT_RGBA, width, 0), 1); - size = avpicture_get_size(AV_PIX_FMT_RGB0, width, height); -#endif padding = 0; buffer = nullptr; @@ -250,7 +245,7 @@ int Image::PopulateFrame(AVFrame *frame) { width, height, linesize, colours, size, av_get_pix_fmt_name(imagePixFormat) ); - AVBufferRef *ref = av_buffer_create(buffer, size, + AVBufferRef *ref = av_buffer_create(buffer, size, dont_free, /* Free callback */ nullptr, /* opaque */ 0 /* flags */ @@ -259,7 +254,7 @@ int Image::PopulateFrame(AVFrame *frame) { Warning("Failed to create av_buffer"); } frame->buf[0] = ref; -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) + // From what I've read, we should align the linesizes to 32bit so that ffmpeg can use SIMD instructions too. int size = av_image_fill_arrays( frame->data, frame->linesize, @@ -271,10 +266,7 @@ int Image::PopulateFrame(AVFrame *frame) { av_make_error_string(size).c_str()); return size; } -#else - avpicture_fill((AVPicture *)frame, buffer, - imagePixFormat, width, height); -#endif + frame->width = width; frame->height = height; frame->format = imagePixFormat; @@ -576,7 +568,7 @@ void Image::Initialise() { if ( res == FontLoadError::kFileNotFound ) { Panic("Invalid font location: %s", config.font_file_location); } else if ( res == FontLoadError::kInvalidFile ) { - Panic("Invalid font file."); + Panic("Invalid font file."); } initialised = true; } @@ -781,7 +773,7 @@ void Image::Assign(const Image &image) { return; } } else { - if ( new_size > allocation || !buffer ) { + if (new_size > allocation || !buffer) { // DumpImgBuffer(); This is also done in AllocImgBuffer AllocImgBuffer(new_size); } @@ -820,10 +812,10 @@ Image *Image::HighlightEdges( /* Set image to all black */ high_image->Clear(); - unsigned int lo_x = limits ? limits->Lo().X() : 0; - unsigned int lo_y = limits ? limits->Lo().Y() : 0; - unsigned int hi_x = limits ? limits->Hi().X() : width-1; - unsigned int hi_y = limits ? limits->Hi().Y() : height-1; + unsigned int lo_x = limits ? limits->Lo().x_ : 0; + unsigned int lo_y = limits ? limits->Lo().y_ : 0; + unsigned int hi_x = limits ? limits->Hi().x_ : width - 1; + unsigned int hi_y = limits ? limits->Hi().y_ : height - 1; if ( p_colours == ZM_COLOUR_GRAY8 ) { for ( unsigned int y = lo_y; y <= hi_y; y++ ) { @@ -941,7 +933,7 @@ bool Image::WriteRaw(const char *filename) const { return true; } -bool Image::ReadJpeg(const char *filename, unsigned int p_colours, unsigned int p_subpixelorder) { +bool Image::ReadJpeg(const std::string &filename, unsigned int p_colours, unsigned int p_subpixelorder) { unsigned int new_width, new_height, new_colours, new_subpixelorder; struct jpeg_decompress_struct *cinfo = readjpg_dcinfo; @@ -954,8 +946,8 @@ bool Image::ReadJpeg(const char *filename, unsigned int p_colours, unsigned int } FILE *infile; - if ( (infile = fopen(filename, "rb")) == nullptr ) { - Error("Can't open %s: %s", filename, strerror(errno)); + if ( (infile = fopen(filename.c_str(), "rb")) == nullptr ) { + Error("Can't open %s: %s", filename.c_str(), strerror(errno)); return false; } @@ -1068,24 +1060,27 @@ cinfo->out_color_space = JCS_RGB; // Multiple calling formats to permit inclusion (or not) of non blocking, quality_override and timestamp (exif), with suitable defaults. // Note quality=zero means default -bool Image::WriteJpeg(const char *filename, int quality_override) const { - return Image::WriteJpeg(filename, quality_override, (timeval){0,0}, false); +bool Image::WriteJpeg(const std::string &filename, int quality_override) const { + return Image::WriteJpeg(filename, quality_override, {}, false); } -bool Image::WriteJpeg(const char *filename) const { - return Image::WriteJpeg(filename, 0, (timeval){0,0}, false); +bool Image::WriteJpeg(const std::string &filename) const { + return Image::WriteJpeg(filename, 0, {}, false); } -bool Image::WriteJpeg(const char *filename, bool on_blocking_abort) const { - return Image::WriteJpeg(filename, 0, (timeval){0,0}, on_blocking_abort); +bool Image::WriteJpeg(const std::string &filename, bool on_blocking_abort) const { + return Image::WriteJpeg(filename, 0, {}, on_blocking_abort); } -bool Image::WriteJpeg(const char *filename, struct timeval timestamp) const { +bool Image::WriteJpeg(const std::string &filename, SystemTimePoint timestamp) const { return Image::WriteJpeg(filename, 0, timestamp, false); } -bool Image::WriteJpeg(const char *filename, int quality_override, struct timeval timestamp) const { +bool Image::WriteJpeg(const std::string &filename, int quality_override, SystemTimePoint timestamp) const { return Image::WriteJpeg(filename, quality_override, timestamp, false); } -bool Image::WriteJpeg(const char *filename, int quality_override, struct timeval timestamp, bool on_blocking_abort) const { +bool Image::WriteJpeg(const std::string &filename, + 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); @@ -1119,17 +1114,17 @@ bool Image::WriteJpeg(const char *filename, int quality_override, struct timeval } } - if ( !on_blocking_abort ) { - if ( (outfile = fopen(filename, "wb")) == nullptr ) { - Error("Can't open %s for writing: %s", filename, strerror(errno)); + if (!on_blocking_abort) { + if ((outfile = fopen(filename.c_str(), "wb")) == nullptr) { + Error("Can't open %s for writing: %s", filename.c_str(), strerror(errno)); return false; } } else { - raw_fd = open(filename, O_WRONLY|O_NONBLOCK|O_CREAT|O_TRUNC,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); - if ( raw_fd < 0 ) + raw_fd = open(filename.c_str(), O_WRONLY | O_NONBLOCK | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (raw_fd < 0) return false; outfile = fdopen(raw_fd, "wb"); - if ( outfile == nullptr ) { + if (outfile == nullptr) { close(raw_fd); return false; } @@ -1204,7 +1199,7 @@ cinfo->out_color_space = JCS_RGB; } // If we have a non-zero time (meaning a parameter was passed in), then form a simple exif segment with that time as DateTimeOriginal and SubsecTimeOriginal // No timestamp just leave off the exif section. - if ( timestamp.tv_sec ) { + if (timestamp.time_since_epoch() > Seconds(0)) { #define EXIFTIMES_MS_OFFSET 0x36 // three decimal digits for milliseconds #define EXIFTIMES_MS_LEN 0x03 #define EXIFTIMES_OFFSET 0x3E // 19 characters format '2015:07:21 13:14:45' not including quotes @@ -1213,9 +1208,15 @@ cinfo->out_color_space = JCS_RGB; // This is a lot of stuff to allocate on the stack. Recommend char *timebuf[64]; char timebuf[64], msbuf[64]; + tm timestamp_tm = {}; - strftime(timebuf, sizeof timebuf, "%Y:%m:%d %H:%M:%S", localtime_r(×tamp.tv_sec, ×tamp_tm)); - snprintf(msbuf, sizeof msbuf, "%06d",(int)(timestamp.tv_usec)); // we only use milliseconds because that's all defined in exif, but this is the whole microseconds because we have it + time_t timestamp_t = std::chrono::system_clock::to_time_t(timestamp); + strftime(timebuf, sizeof timebuf, "%Y:%m:%d %H:%M:%S", localtime_r(×tamp_t, ×tamp_tm)); + Seconds ts_sec = std::chrono::duration_cast(timestamp.time_since_epoch()); + Microseconds ts_usec = std::chrono::duration_cast(timestamp.time_since_epoch() - ts_sec); + // we only use milliseconds because that's all defined in exif, but this is the whole microseconds because we have it + snprintf(msbuf, sizeof msbuf, "%06d", static_cast(ts_usec.count())); + unsigned char exiftimes[82] = { 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x69, 0x87, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -1508,7 +1509,7 @@ bool Image::Crop( unsigned int lo_x, unsigned int lo_y, unsigned int hi_x, unsig } bool Image::Crop(const Box &limits) { - return Crop(limits.LoX(), limits.LoY(), limits.HiX(), limits.HiY()); + return Crop(limits.Lo().x_, limits.Lo().y_, limits.Hi().x_, limits.Hi().y_); } /* Far from complete */ @@ -1730,11 +1731,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 ) { -#ifdef ZM_IMAGE_PROFILING - struct timespec start,end,diff; - unsigned long long executetime; - unsigned long milpixels; -#endif uint8_t* new_buffer; if ( !( @@ -1752,19 +1748,21 @@ void Image::Blend( const Image &image, int transparency ) { new_buffer = AllocBuffer(size); #ifdef ZM_IMAGE_PROFILING - clock_gettime(CLOCK_THREAD_CPUTIME_ID,&start); + TimePoint start = std::chrono::steady_clock::now(); #endif /* Do the blending */ (*blend)(buffer, image.buffer, new_buffer, size, transparency); #ifdef ZM_IMAGE_PROFILING - clock_gettime(CLOCK_THREAD_CPUTIME_ID,&end); - TimespecDiff(&start,&end,&diff); + TimePoint end = std::chrono::steady_clock::now(); - executetime = (1000000000ull * diff.tv_sec) + diff.tv_nsec; - milpixels = (unsigned long)((long double)size)/((((long double)executetime)/1000)); - Debug(5, "Blend: %u colours blended in %llu nanoseconds, %lu million colours/s\n",size,executetime,milpixels); + std::chrono::nanoseconds diff = std::chrono::duration_cast(end - start); + uint64 mil_pixels = static_cast(size / (static_cast(diff.count()) / 1000)); + Debug(5, "Blend: %u colours blended in %" PRIi64 " nanoseconds, % " PRIi64 " million colours/s\n", + size, + static_cast(diff.count()), + mil_pixels); #endif AssignDirect(width, height, colours, subpixelorder, new_buffer, size, ZM_BUFTYPE_ZM); @@ -1868,12 +1866,6 @@ Image *Image::Highlight( unsigned int n_images, Image *images[], const Rgb thres /* New function to allow buffer re-using instead of allocationg memory for the delta image every time */ void Image::Delta(const Image &image, Image* targetimage) const { -#ifdef ZM_IMAGE_PROFILING - struct timespec start,end,diff; - unsigned long long executetime; - unsigned long milpixels; -#endif - if ( !(width == image.width && height == image.height && colours == image.colours && subpixelorder == image.subpixelorder) ) { Panic( "Attempt to get delta of different sized images, expected %dx%dx%d %d, got %dx%dx%d %d", width, height, colours, subpixelorder, image.width, image.height, image.colours, image.subpixelorder); @@ -1886,7 +1878,7 @@ void Image::Delta(const Image &image, Image* targetimage) const { } #ifdef ZM_IMAGE_PROFILING - clock_gettime(CLOCK_THREAD_CPUTIME_ID,&start); + TimePoint start = std::chrono::steady_clock::now(); #endif switch ( colours ) { @@ -1923,16 +1915,18 @@ void Image::Delta(const Image &image, Image* targetimage) const { } #ifdef ZM_IMAGE_PROFILING - clock_gettime(CLOCK_THREAD_CPUTIME_ID,&end); - TimespecDiff(&start,&end,&diff); + TimePoint end = std::chrono::steady_clock::now(); - executetime = (1000000000ull * diff.tv_sec) + diff.tv_nsec; - milpixels = (unsigned long)((long double)pixels)/((((long double)executetime)/1000)); - Debug(5, "Delta: %u delta pixels generated in %llu nanoseconds, %lu million pixels/s",pixels,executetime,milpixels); + std::chrono::nanoseconds diff = std::chrono::duration_cast(end - start); + uint64 mil_pixels = static_cast(size / (static_cast(diff.count()) / 1000)); + Debug(5, "Delta: %u delta pixels generated in %" PRIi64 " nanoseconds, % " PRIi64 " million pixels/s", + size, + static_cast(diff.count()), + mil_pixels); #endif } -const Coord Image::centreCoord( const char *text, int size=1 ) const { +const Vector2 Image::centreCoord(const char *text, int size = 1) const { int index = 0; int line_no = 0; int text_len = strlen(text); @@ -1957,7 +1951,7 @@ const Coord Image::centreCoord( const char *text, int size=1 ) const { uint16_t char_height = font_variant.GetCharHeight(); int x = (width - (max_line_len * char_width )) / 2; int y = (height - (line_no * char_height) ) / 2; - return Coord(x, y); + return {x, y}; } /* RGB32 compatible: complete */ @@ -2007,7 +2001,7 @@ https://lemire.me/blog/2018/02/21/iterating-over-set-bits-quickly/ */ void Image::Annotate( const std::string &text, - const Coord &coord, + const Vector2 &coord, const uint8 size, const Rgb fg_colour, const Rgb bg_colour) { @@ -2031,8 +2025,8 @@ void Image::Annotate( // Calculate initial coordinates of annotation so that everything is displayed even if the // user set coordinates would prevent that. - uint32 x0 = ZM::clamp(static_cast(coord.X()), 0u, x0_max); - uint32 y0 = ZM::clamp(static_cast(coord.Y()), 0u, y0_max); + uint32 x0 = zm::clamp(static_cast(coord.x_), 0u, x0_max); + uint32 y0 = zm::clamp(static_cast(coord.y_), 0u, y0_max); uint32 y = y0; for (const std::string &line : lines) { @@ -2127,17 +2121,18 @@ void Image::Annotate( } } -void Image::Timestamp( const char *label, const time_t when, const Coord &coord, const int size ) { +void Image::Timestamp(const char *label, SystemTimePoint when, const Vector2 &coord, int label_size) { char time_text[64]; tm when_tm = {}; - strftime(time_text, sizeof(time_text), "%y/%m/%d %H:%M:%S", localtime_r(&when, &when_tm)); - if ( label ) { + time_t when_t = std::chrono::system_clock::to_time_t(when); + strftime(time_text, sizeof(time_text), "%y/%m/%d %H:%M:%S", localtime_r(&when_t, &when_tm)); + if (label) { // Assume label is max 64, + ' - ' + 64 chars of time_text char text[132]; snprintf(text, sizeof(text), "%s - %s", label, time_text); - Annotate(text, coord, size); + Annotate(text, coord, label_size); } else { - Annotate(time_text, coord, size); + Annotate(time_text, coord, label_size); } } @@ -2294,10 +2289,10 @@ void Image::Fill( Rgb colour, const Box *limits ) { /* Convert the colour's RGBA subpixel order into the image's subpixel order */ colour = rgb_convert(colour,subpixelorder); - unsigned int lo_x = limits?limits->Lo().X():0; - unsigned int lo_y = limits?limits->Lo().Y():0; - unsigned int hi_x = limits?limits->Hi().X():width-1; - unsigned int hi_y = limits?limits->Hi().Y():height-1; + unsigned int lo_x = limits ? limits->Lo().x_ : 0; + unsigned int lo_y = limits ? limits->Lo().y_ : 0; + unsigned int hi_x = limits ? limits->Hi().x_ : width - 1; + unsigned int hi_y = limits ? limits->Hi().y_ : height - 1; if ( colours == ZM_COLOUR_GRAY8 ) { for ( unsigned int y = lo_y; y <= hi_y; y++ ) { unsigned char *p = &buffer[(y*width)+lo_x]; @@ -2339,10 +2334,10 @@ void Image::Fill( Rgb colour, int density, const Box *limits ) { /* Convert the colour's RGBA subpixel order into the image's subpixel order */ colour = rgb_convert(colour, subpixelorder); - unsigned int lo_x = limits?limits->Lo().X():0; - unsigned int lo_y = limits?limits->Lo().Y():0; - unsigned int hi_x = limits?limits->Hi().X():width-1; - unsigned int hi_y = limits?limits->Hi().Y():height-1; + unsigned int lo_x = limits ? limits->Lo().x_ : 0; + unsigned int lo_y = limits ? limits->Lo().y_ : 0; + unsigned int hi_x = limits ? limits->Hi().x_ : width - 1; + unsigned int hi_y = limits ? limits->Hi().y_ : height - 1; if ( colours == ZM_COLOUR_GRAY8 ) { for ( unsigned int y = lo_y; y <= hi_y; y++ ) { unsigned char *p = &buffer[(y*width)+lo_x]; @@ -2382,17 +2377,18 @@ void Image::Outline( Rgb colour, const Polygon &polygon ) { } /* Convert the colour's RGBA subpixel order into the image's subpixel order */ - colour = rgb_convert(colour,subpixelorder); + colour = rgb_convert(colour, subpixelorder); - int n_coords = polygon.getNumCoords(); - for ( int j = 0, i = n_coords-1; j < n_coords; i = j++ ) { - const Coord &p1 = polygon.getCoord( i ); - const Coord &p2 = polygon.getCoord( j ); + size_t n_coords = polygon.GetVertices().size(); + for (size_t j = 0, i = n_coords - 1; j < n_coords; i = j++) { + const Vector2 &p1 = polygon.GetVertices()[i]; + const Vector2 &p2 = polygon.GetVertices()[j]; - int x1 = p1.X(); - int x2 = p2.X(); - int y1 = p1.Y(); - int y2 = p2.Y(); + // The last pixel we can draw is width/height - 1. Clamp to that value. + int x1 = zm::clamp(p1.x_, 0, static_cast(width - 1)); + int x2 = zm::clamp(p2.x_, 0, static_cast(width - 1)); + int y1 = zm::clamp(p1.y_, 0, static_cast(height - 1)); + int y2 = zm::clamp(p2.y_, 0, static_cast(height - 1)); double dx = x2 - x1; double dy = y2 - y1; @@ -2457,130 +2453,107 @@ void Image::Outline( Rgb colour, const Polygon &polygon ) { } // end foreach coordinate in the polygon } -/* RGB32 compatible: complete */ +// Polygon filling is based on the Scan-line Polygon filling algorithm void Image::Fill(Rgb colour, int density, const Polygon &polygon) { - if ( !(colours == ZM_COLOUR_GRAY8 || colours == ZM_COLOUR_RGB24 || colours == ZM_COLOUR_RGB32 ) ) { + if (!(colours == ZM_COLOUR_GRAY8 || colours == ZM_COLOUR_RGB24 || colours == ZM_COLOUR_RGB32)) { Panic("Attempt to fill image with unexpected colours %d", colours); } /* Convert the colour's RGBA subpixel order into the image's subpixel order */ colour = rgb_convert(colour, subpixelorder); - int n_coords = polygon.getNumCoords(); - int n_global_edges = 0; - Edge global_edges[n_coords]; - for ( int j = 0, i = n_coords-1; j < n_coords; i = j++ ) { - const Coord &p1 = polygon.getCoord(i); - const Coord &p2 = polygon.getCoord(j); + size_t n_coords = polygon.GetVertices().size(); - int x1 = p1.X(); - int x2 = p2.X(); - int y1 = p1.Y(); - int y2 = p2.Y(); + std::vector global_edges; + global_edges.reserve(n_coords); + for (size_t j = 0, i = n_coords - 1; j < n_coords; i = j++) { + const Vector2 &p1 = polygon.GetVertices()[i]; + const Vector2 &p2 = polygon.GetVertices()[j]; - //Debug( 9, "x1:%d,y1:%d x2:%d,y2:%d", x1, y1, x2, y2 ); - if ( y1 == y2 ) + // Do not add horizontal edges to the global edge table. + if (p1.y_ == p2.y_) continue; - double dx = x2 - x1; - double dy = y2 - y1; + Vector2 d = p2 - p1; + + global_edges.emplace_back(std::min(p1.y_, p2.y_), + std::max(p1.y_, p2.y_), + p1.y_ < p2.y_ ? p1.x_ : p2.x_, + d.x_ / static_cast(d.y_)); - global_edges[n_global_edges].min_y = y1= Logger::DEBUG9 ) { - for ( int i = 0; i < n_global_edges; i++ ) { - Debug(9, "%d: min_y: %d, max_y:%d, min_x:%.2f, 1/m:%.2f", - i, global_edges[i].min_y, global_edges[i].max_y, global_edges[i].min_x, global_edges[i]._1_m); - } - } -#endif + std::vector active_edges; + active_edges.reserve(global_edges.size()); - int n_active_edges = 0; - Edge active_edges[n_global_edges]; - int y = global_edges[0].min_y; - do { - for ( int i = 0; i < n_global_edges; i++ ) { - if ( global_edges[i].min_y == y ) { - Debug(9, "Moving global edge"); - active_edges[n_active_edges++] = global_edges[i]; - if ( i < (n_global_edges-1) ) { - //memcpy( &global_edges[i], &global_edges[i+1], sizeof(*global_edges)*(n_global_edges-i) ); - memmove( &global_edges[i], &global_edges[i+1], sizeof(*global_edges)*(n_global_edges-i) ); - i--; - } - n_global_edges--; + int32 scan_line = global_edges[0].min_y; + while (!global_edges.empty() || !active_edges.empty()) { + // Deactivate edges with max_y < current scan line + for (auto it = active_edges.begin(); it != active_edges.end();) { + if (scan_line >= it->max_y) { + it = active_edges.erase(it); } else { - break; + it->min_x += it->_1_m; + ++it; } } - std::sort(active_edges, active_edges + n_active_edges, Edge::CompareX); -#ifndef ZM_DBG_OFF - if ( logLevel() >= Logger::DEBUG9 ) { - for ( int i = 0; i < n_active_edges; i++ ) { - Debug(9, "%d - %d: min_y: %d, max_y:%d, min_x:%.2f, 1/m:%.2f", - y, i, active_edges[i].min_y, active_edges[i].max_y, active_edges[i].min_x, active_edges[i]._1_m ); + + // Activate edges with min_y == current scan line + for (auto it = global_edges.begin(); it != global_edges.end();) { + if (it->min_y == scan_line) { + active_edges.emplace_back(*it); + it = global_edges.erase(it); + } else { + ++it; } } -#endif - if ( !(y%density) ) { - //Debug( 9, "%d", y ); - for ( int i = 0; i < n_active_edges; ) { - int lo_x = int(round(active_edges[i++].min_x)); - int hi_x = int(round(active_edges[i++].min_x)); - if ( colours == ZM_COLOUR_GRAY8 ) { - unsigned char *p = &buffer[(y*width)+lo_x]; - for ( int x = lo_x; x <= hi_x; x++, p++) { - if ( !(x%density) ) { - //Debug( 9, " %d", x ); + + // Not enough edges to perform the fill operation. + // Continue to next line. + if (active_edges.size() < 2) { + continue; + } + std::sort(active_edges.begin(), active_edges.end(), PolygonFill::Edge::CompareX); + + if (!(scan_line % density)) { + for (auto it = active_edges.begin(); it < active_edges.end() - 1; ++it) { + int32 lo_x = static_cast(it->min_x); + int32 hi_x = static_cast(std::next(it)->min_x); + if (colours == ZM_COLOUR_GRAY8) { + uint8 *p = &buffer[(scan_line * width) + lo_x]; + + for (int32 x = lo_x; x <= hi_x; x++, p++) { + if (!(x % density)) { *p = colour; } } - } else if ( colours == ZM_COLOUR_RGB24 ) { - unsigned char *p = &buffer[colours*((y*width)+lo_x)]; - for ( int x = lo_x; x <= hi_x; x++, p += 3) { - if ( !(x%density) ) { - RED_PTR_RGBA(p) = RED_VAL_RGBA(colour); - GREEN_PTR_RGBA(p) = GREEN_VAL_RGBA(colour); - BLUE_PTR_RGBA(p) = BLUE_VAL_RGBA(colour); - } - } - } else if( colours == ZM_COLOUR_RGB32 ) { - Rgb *p = (Rgb*)&buffer[((y*width)+lo_x)<<2]; - for ( int x = lo_x; x <= hi_x; x++, p++) { - if ( !(x%density) ) { - /* Fast, copies the entire pixel in a single pass */ - *p = colour; - } - } - } - } - } - y++; - for ( int i = n_active_edges-1; i >= 0; i-- ) { - if ( y >= active_edges[i].max_y ) { - // Or >= as per sheets - Debug(9, "Deleting active_edge"); - if ( i < (n_active_edges-1) ) { - //memcpy( &active_edges[i], &active_edges[i+1], sizeof(*active_edges)*(n_active_edges-i) ); - memmove( &active_edges[i], &active_edges[i+1], sizeof(*active_edges)*(n_active_edges-i) ); - } - n_active_edges--; - } else { - active_edges[i].min_x += active_edges[i]._1_m; - } - } - } while ( n_global_edges || n_active_edges ); -} + } else if (colours == ZM_COLOUR_RGB24) { + constexpr uint8 bytesPerPixel = 3; + uint8 *ptr = &buffer[((scan_line * width) + lo_x) * bytesPerPixel]; -void Image::Fill(Rgb colour, const Polygon &polygon) { - Fill(colour, 1, polygon); + for (int32 x = lo_x; x <= hi_x; x++, ptr += bytesPerPixel) { + if (!(x % density)) { + RED_PTR_RGBA(ptr) = RED_VAL_RGBA(colour); + GREEN_PTR_RGBA(ptr) = GREEN_VAL_RGBA(colour); + BLUE_PTR_RGBA(ptr) = BLUE_VAL_RGBA(colour); + } + } + } else if (colours == ZM_COLOUR_RGB32) { + constexpr uint8 bytesPerPixel = 4; + Rgb *ptr = reinterpret_cast(&buffer[((scan_line * width) + lo_x) * bytesPerPixel]); + + for (int32 x = lo_x; x <= hi_x; x++, ptr++) { + if (!(x % density)) { + *ptr = colour; + } + } + } + } + } + + scan_line++; + } } void Image::Rotate(int angle) { diff --git a/src/zm_image.h b/src/zm_image.h index 671cc0487..7778747e4 100644 --- a/src/zm_image.h +++ b/src/zm_image.h @@ -20,19 +20,19 @@ #ifndef ZM_IMAGE_H #define ZM_IMAGE_H -#include "zm_coord.h" #include "zm_ffmpeg.h" #include "zm_jpeg.h" #include "zm_logger.h" #include "zm_mem_utils.h" #include "zm_rgb.h" +#include "zm_time.h" +#include "zm_vector2.h" #if HAVE_ZLIB_H #include #endif // HAVE_ZLIB_H class Box; -class Image; class Polygon; #define ZM_BUFTYPE_DONTFREE 0 @@ -96,24 +96,6 @@ class Image { void update_function_pointers(); protected: - struct Edge { - int min_y; - int max_y; - double min_x; - double _1_m; - - static bool CompareYX(const Edge &e1, const Edge &e2) { - if ( e1.min_y == e2.min_y ) - return e1.min_x < e2.min_x; - return e1.min_y < e2.min_y; - } - - static bool CompareX(const Edge &e1, const Edge &e2) { - return e1.min_x < e2.min_x; - } - }; - - inline void AllocImgBuffer(size_t p_bufsize) { if ( buffer ) DumpImgBuffer(); @@ -251,14 +233,17 @@ class Image { bool ReadRaw(const char *filename); bool WriteRaw(const char *filename) const; - bool ReadJpeg(const char *filename, unsigned int p_colours, unsigned int p_subpixelorder); + bool ReadJpeg(const std::string &filename, unsigned int p_colours, unsigned int p_subpixelorder); - bool WriteJpeg(const char *filename) const; - bool WriteJpeg(const char *filename, bool on_blocking_abort) const; - bool WriteJpeg(const char *filename, int quality_override) const; - bool WriteJpeg(const char *filename, struct timeval timestamp) const; - bool WriteJpeg(const char *filename, int quality_override, struct timeval timestamp) const; - bool WriteJpeg(const char *filename, int quality_override, struct timeval timestamp, bool on_blocking_abort) const; + bool WriteJpeg(const std::string &filename) const; + bool WriteJpeg(const std::string &filename, bool on_blocking_abort) const; + bool WriteJpeg(const std::string &filename, int quality_override) const; + bool WriteJpeg(const std::string &filename, SystemTimePoint timestamp) const; + bool WriteJpeg(const std::string &filename, int quality_override, SystemTimePoint timestamp) const; + bool WriteJpeg(const std::string &filename, + int quality_override, + SystemTimePoint timestamp, + bool on_blocking_abort) const; bool DecodeJpeg(const JOCTET *inbuffer, int inbuffer_size, unsigned int p_colours, unsigned int p_subpixelorder); bool EncodeJpeg(JOCTET *outbuffer, int *outbuffer_size, int quality_override=0) const; @@ -280,16 +265,16 @@ class Image { //Image *Delta( const Image &image ) const; void Delta( const Image &image, Image* targetimage) const; - const Coord centreCoord(const char *text, const int size) const; + const Vector2 centreCoord(const char *text, const int size) const; void MaskPrivacy( const unsigned char *p_bitmask, const Rgb pixel_colour=0x00222222 ); void Annotate(const std::string &text, - const Coord &coord, + const Vector2 &coord, uint8 size = 1, Rgb fg_colour = kRGBWhite, Rgb bg_colour = kRGBBlack); Image *HighlightEdges( Rgb colour, unsigned int p_colours, unsigned int p_subpixelorder, const Box *limits=0 ); //Image *HighlightEdges( Rgb colour, const Polygon &polygon ); - void Timestamp( const char *label, const time_t when, const Coord &coord, const int size ); + void Timestamp(const char *label, SystemTimePoint when, const Vector2 &coord, int label_size); void Colourise(const unsigned int p_reqcolours, const unsigned int p_reqsubpixelorder); void DeColourise(); @@ -297,8 +282,8 @@ class Image { void Fill( Rgb colour, const Box *limits=0 ); void Fill( Rgb colour, int density, const Box *limits=0 ); void Outline( Rgb colour, const Polygon &polygon ); - void Fill( Rgb colour, const Polygon &polygon ); - void Fill( Rgb colour, int density, const Polygon &polygon ); + void Fill(Rgb colour, const Polygon &polygon) { Fill(colour, 1, polygon); }; + void Fill(Rgb colour, int density, const Polygon &polygon); void Rotate( int angle ); void Flip( bool leftright ); @@ -311,6 +296,32 @@ class Image { void Deinterlace_4Field(const Image* next_image, unsigned int threshold); }; +// Scan-line polygon fill algorithm +namespace PolygonFill { +class Edge { + public: + Edge() = default; + Edge(int32 min_y, int32 max_y, double min_x, double _1_m) : min_y(min_y), max_y(max_y), min_x(min_x), _1_m(_1_m) {} + + static bool CompareYX(const Edge &e1, const Edge &e2) { + if (e1.min_y == e2.min_y) { + return e1.min_x < e2.min_x; + } + return e1.min_y < e2.min_y; + } + + static bool CompareX(const Edge &e1, const Edge &e2) { + return e1.min_x < e2.min_x; + } + + public: + int32 min_y; + int32 max_y; + double min_x; + double _1_m; +}; +} + #endif // ZM_IMAGE_H /* Blend functions */ diff --git a/src/zm_jpeg.cpp b/src/zm_jpeg.cpp index c3fddec1f..a71bef48e 100644 --- a/src/zm_jpeg.cpp +++ b/src/zm_jpeg.cpp @@ -37,13 +37,13 @@ void zm_jpeg_emit_silence(j_common_ptr cinfo, int msg_level) { } void zm_jpeg_error_exit(j_common_ptr cinfo) { - static char buffer[JMSG_LENGTH_MAX]; - zm_error_ptr zmerr = (zm_error_ptr)cinfo->err; + zm_error_ptr zmerr = (zm_error_ptr) cinfo->err; - (zmerr->pub.format_message)(cinfo, buffer); + char buffer[JMSG_LENGTH_MAX]; + zmerr->pub.format_message(cinfo, buffer); Error("%s", buffer); - if ( ++jpeg_err_count == MAX_JPEG_ERRS ) { + if (++jpeg_err_count == MAX_JPEG_ERRS) { Fatal("Maximum number (%d) of JPEG errors reached, exiting", jpeg_err_count); } @@ -51,25 +51,25 @@ void zm_jpeg_error_exit(j_common_ptr cinfo) { } void zm_jpeg_emit_message(j_common_ptr cinfo, int msg_level) { - static char buffer[JMSG_LENGTH_MAX]; - zm_error_ptr zmerr = (zm_error_ptr)cinfo->err; + char buffer[JMSG_LENGTH_MAX]; + zm_error_ptr zmerr = (zm_error_ptr) cinfo->err; - if ( msg_level < 0 ) { + if (msg_level < 0) { /* It's a warning message. Since corrupt files may generate many warnings, * the policy implemented here is to show only the first warning, * unless trace_level >= 3. */ - if ( zmerr->pub.num_warnings == 0 || zmerr->pub.trace_level >= 3 ) { - (zmerr->pub.format_message)(cinfo, buffer); - if ( !strstr(buffer, "Corrupt JPEG data:") ) + if (zmerr->pub.num_warnings == 0 || zmerr->pub.trace_level >= 3) { + zmerr->pub.format_message(cinfo, buffer); + if (!strstr(buffer, "Corrupt JPEG data:")) Warning("%s", buffer); } /* Always count warnings in num_warnings. */ zmerr->pub.num_warnings++; } else { /* It's a trace message. Show it if trace_level >= msg_level. */ - if ( zmerr->pub.trace_level >= msg_level ) { - (zmerr->pub.format_message)(cinfo, buffer); + if (zmerr->pub.trace_level >= msg_level) { + zmerr->pub.format_message(cinfo, buffer); Debug(msg_level, "%s", buffer); } } diff --git a/src/zm_libvlc_camera.cpp b/src/zm_libvlc_camera.cpp index 59cdc4ccf..beda38498 100644 --- a/src/zm_libvlc_camera.cpp +++ b/src/zm_libvlc_camera.cpp @@ -276,7 +276,7 @@ int LibvlcCamera::PreCapture() { } // Should not return -1 as cancels capture. Always wait for image if available. -int LibvlcCamera::Capture( ZMPacket &zm_packet ) { +int LibvlcCamera::Capture(std::shared_ptr &zm_packet) { // newImage is a mutex/condition based flag to tell us when there is an image available { std::unique_lock lck(mLibvlcData.newImageMutex); @@ -288,9 +288,9 @@ int LibvlcCamera::Capture( ZMPacket &zm_packet ) { return 0; mLibvlcData.mutex.lock(); - zm_packet.image->Assign(width, height, colours, subpixelorder, mLibvlcData.buffer, width * height * mBpp); - zm_packet.packet.stream_index = mVideoStreamId; - zm_packet.stream = mVideoStream; + zm_packet->image->Assign(width, height, colours, subpixelorder, mLibvlcData.buffer, width * height * mBpp); + zm_packet->packet.stream_index = mVideoStreamId; + zm_packet->stream = mVideoStream; mLibvlcData.mutex.unlock(); return 1; diff --git a/src/zm_libvlc_camera.h b/src/zm_libvlc_camera.h index 7d9be27c1..c516a63e0 100644 --- a/src/zm_libvlc_camera.h +++ b/src/zm_libvlc_camera.h @@ -72,7 +72,7 @@ public: int PrimeCapture() override; int PreCapture() override; - int Capture(ZMPacket &p) override; + int Capture(std::shared_ptr &p) override; int PostCapture() override; int Close() override { return 0; }; }; diff --git a/src/zm_libvnc_camera.cpp b/src/zm_libvnc_camera.cpp index faa594a7e..6fb414686 100644 --- a/src/zm_libvnc_camera.cpp +++ b/src/zm_libvnc_camera.cpp @@ -208,21 +208,21 @@ int VncCamera::PreCapture() { return res == TRUE ? 1 : -1; } -int VncCamera::Capture(ZMPacket &zm_packet) { +int VncCamera::Capture(std::shared_ptr &zm_packet) { if (!mVncData.buffer) { Debug(1, "No buffer"); return 0; } - if (!zm_packet.image) { + if (!zm_packet->image) { Debug(1, "Allocating image %dx%d %dcolours = %d", width, height, colours, colours*pixels); - zm_packet.image = new Image(width, height, colours, subpixelorder); + zm_packet->image = new Image(width, height, colours, subpixelorder); } - zm_packet.keyframe = 1; - zm_packet.codec_type = AVMEDIA_TYPE_VIDEO; - zm_packet.packet.stream_index = mVideoStreamId; - zm_packet.stream = mVideoStream; + zm_packet->keyframe = 1; + zm_packet->codec_type = AVMEDIA_TYPE_VIDEO; + zm_packet->packet.stream_index = mVideoStreamId; + zm_packet->stream = mVideoStream; - uint8_t *directbuffer = zm_packet.image->WriteBuffer(width, height, colours, subpixelorder); + uint8_t *directbuffer = zm_packet->image->WriteBuffer(width, height, colours, subpixelorder); Debug(1, "scale src %p, %d, dest %p %d %d %dx%d %dx%d", mVncData.buffer, mRfb->si.framebufferWidth * mRfb->si.framebufferHeight * 4, directbuffer, diff --git a/src/zm_libvnc_camera.h b/src/zm_libvnc_camera.h index caa1bb1da..59c531e83 100644 --- a/src/zm_libvnc_camera.h +++ b/src/zm_libvnc_camera.h @@ -10,11 +10,12 @@ // Older versions of libvncserver defined a max macro in rfb/rfbproto.h // Undef it here so it doesn't collide with std::max -// TODO: Remove this once xenial support is dropped +// TODO: Remove this once CentOS 7 support is dropped #ifdef max #undef max #endif + // Used by vnc callbacks struct VncPrivateData { uint8_t *buffer; @@ -53,7 +54,7 @@ public: int PreCapture() override; int PrimeCapture() override; - int Capture(ZMPacket &packet) override; + int Capture(std::shared_ptr &packet) override; int PostCapture() override; int Close() override; }; diff --git a/src/zm_line.h b/src/zm_line.h new file mode 100644 index 000000000..ec09fc48e --- /dev/null +++ b/src/zm_line.h @@ -0,0 +1,64 @@ +/* + * This file is part of the ZoneMinder Project. See AUTHORS file for Copyright information + * + * 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, see . + */ + +#ifndef ZONEMINDER_SRC_ZM_LINE_H_ +#define ZONEMINDER_SRC_ZM_LINE_H_ + +#include "zm_vector2.h" + +// Represents a part of a line bounded by two end points +class LineSegment { + public: + LineSegment(Vector2 start, Vector2 end) : start_(start), end_(end) {} + + public: + Vector2 start_; + Vector2 end_; +}; + +// Represents an infinite line +class Line { + public: + Line(Vector2 p1, Vector2 p2) : position_(p1), direction_(p2 - p1) {} + explicit Line(LineSegment segment) : Line(segment.start_, segment.end_) {}; + + bool IsPointLeftOfOrColinear(Vector2 p) const { + int32 det = direction_.Determinant(p - position_); + + return det >= 0; + } + + Vector2 Intersection(Line const &line) const { + int32 det = direction_.Determinant(line.direction_); + + if (det == 0) { + // lines are parallel or overlap, no intersection + return Vector2::Inf(); + } + + Vector2 c = line.position_ - position_; + double t = c.Determinant(line.direction_) / static_cast(det); + + return position_ + direction_ * t; + } + + private: + Vector2 position_; + Vector2 direction_; +}; + +#endif //ZONEMINDER_SRC_ZM_LINE_H_ diff --git a/src/zm_local_camera.cpp b/src/zm_local_camera.cpp index 7e97ecd08..8f38b4d99 100644 --- a/src/zm_local_camera.cpp +++ b/src/zm_local_camera.cpp @@ -24,8 +24,9 @@ #include #include #include +#include -#if ZM_HAS_V4L +#if ZM_HAS_V4L2 /* Workaround for GNU/kFreeBSD and FreeBSD */ #if defined(__FreeBSD_kernel__) || defined(__FreeBSD__) @@ -45,11 +46,9 @@ static int vidioctl(int fd, int request, void *arg) { return result; } -#if HAVE_LIBSWSCALE static _AVPIXELFORMAT getFfPixFormatFromV4lPalette(int v4l_version, int palette) { _AVPIXELFORMAT pixFormat = AV_PIX_FMT_NONE; -#if ZM_HAS_V4L2 if ( v4l_version == 2 ) { switch ( palette ) { #if defined(V4L2_PIX_FMT_RGB444) && defined(AV_PIX_FMT_RGB444) @@ -174,82 +173,10 @@ static _AVPIXELFORMAT getFfPixFormatFromV4lPalette(int v4l_version, int palette) } } // end switch palette } // end if v4l2 -#endif // ZM_HAS_V4L2 -#if ZM_HAS_V4L1 - if ( v4l_version == 1 ) { - switch( palette ) { - case VIDEO_PALETTE_RGB32 : - if ( BigEndian ) - pixFormat = AV_PIX_FMT_ARGB; - else - pixFormat = AV_PIX_FMT_BGRA; - break; - case VIDEO_PALETTE_RGB24 : - if ( BigEndian ) - pixFormat = AV_PIX_FMT_RGB24; - else - pixFormat = AV_PIX_FMT_BGR24; - break; - case VIDEO_PALETTE_GREY : - pixFormat = AV_PIX_FMT_GRAY8; - break; - case VIDEO_PALETTE_RGB555 : - pixFormat = AV_PIX_FMT_RGB555; - break; - case VIDEO_PALETTE_RGB565 : - pixFormat = AV_PIX_FMT_RGB565; - break; - case VIDEO_PALETTE_YUYV : - case VIDEO_PALETTE_YUV422 : - pixFormat = AV_PIX_FMT_YUYV422; - break; - case VIDEO_PALETTE_YUV422P : - pixFormat = AV_PIX_FMT_YUV422P; - break; - case VIDEO_PALETTE_YUV420P : - pixFormat = AV_PIX_FMT_YUV420P; - break; - default : - { - Fatal("Can't find swscale format for palette %d", palette); - break; -#if 0 - // These are all spare and may match some of the above - pixFormat = AV_PIX_FMT_YUVJ420P; - pixFormat = AV_PIX_FMT_YUVJ422P; - pixFormat = AV_PIX_FMT_YUVJ444P; - pixFormat = AV_PIX_FMT_UYVY422; - pixFormat = AV_PIX_FMT_UYYVYY411; - pixFormat = AV_PIX_FMT_BGR565; - pixFormat = AV_PIX_FMT_BGR555; - pixFormat = AV_PIX_FMT_BGR8; - pixFormat = AV_PIX_FMT_BGR4; - pixFormat = AV_PIX_FMT_BGR4_BYTE; - pixFormat = AV_PIX_FMT_RGB8; - pixFormat = AV_PIX_FMT_RGB4; - pixFormat = AV_PIX_FMT_RGB4_BYTE; - pixFormat = AV_PIX_FMT_NV12; - pixFormat = AV_PIX_FMT_NV21; - pixFormat = AV_PIX_FMT_RGB32_1; - pixFormat = AV_PIX_FMT_BGR32_1; - pixFormat = AV_PIX_FMT_GRAY16BE; - pixFormat = AV_PIX_FMT_GRAY16LE; - pixFormat = AV_PIX_FMT_YUV440P; - pixFormat = AV_PIX_FMT_YUVJ440P; - pixFormat = AV_PIX_FMT_YUVA420P; - //pixFormat = AV_PIX_FMT_VDPAU_H264; - //pixFormat = AV_PIX_FMT_VDPAU_MPEG1; - //pixFormat = AV_PIX_FMT_VDPAU_MPEG2; -#endif - } - } // end switch palette - } // end if v4l1 -#endif // ZM_HAS_V4L1 + return pixFormat; } // end getFfPixFormatFromV4lPalette -#endif // HAVE_LIBSWSCALE -#if ZM_HAS_V4L2 static char palette_desc[32]; /* Automatic format selection preferred formats */ static const uint32_t prefered_rgb32_formats[] = { @@ -283,7 +210,6 @@ static const uint32_t prefered_gray8_formats[] = { V4L2_PIX_FMT_YUV422P, V4L2_PIX_FMT_YUV420 }; -#endif int LocalCamera::camera_count = 0; int LocalCamera::channel_count = 0; @@ -293,16 +219,9 @@ int LocalCamera::standards[VIDEO_MAX_FRAME]; int LocalCamera::vid_fd = -1; int LocalCamera::v4l_version = 0; -#if ZM_HAS_V4L2 LocalCamera::V4L2Data LocalCamera::v4l2_data; -#endif // ZM_HAS_V4L2 -#if ZM_HAS_V4L1 -LocalCamera::V4L1Data LocalCamera::v4l1_data; -#endif // ZM_HAS_V4L1 -#if HAVE_LIBSWSCALE AVFrame **LocalCamera::capturePictures = nullptr; -#endif // HAVE_LIBSWSCALE LocalCamera *LocalCamera::last_camera = nullptr; @@ -370,7 +289,6 @@ LocalCamera::LocalCamera( BigEndian = 0; } -#if ZM_HAS_V4L2 if ( v4l_version == 2 && palette == 0 ) { /* Use automatic format selection */ Debug(2,"Using automatic format selection"); @@ -381,11 +299,14 @@ LocalCamera::LocalCamera( } else { if ( capture ) { Info("Selected capture palette: %s (0x%02hhx%02hhx%02hhx%02hhx)", - palette_desc, (palette>>24)&0xff, (palette>>16)&0xff, (palette>>8)&0xff, (palette)&0xff); + palette_desc, + static_cast((palette >> 24) & 0xff), + static_cast((palette >> 16) & 0xff), + static_cast((palette >> 8) & 0xff), + static_cast((palette) & 0xff)); } } } -#endif if (capture) { if (last_camera) { @@ -402,15 +323,12 @@ LocalCamera::LocalCamera( Warning("Different capture sizes defined for monitors sharing same device, results may be unpredictable or completely wrong"); } -#if HAVE_LIBSWSCALE /* Get ffmpeg pixel format based on capture palette and endianness */ capturePixFormat = getFfPixFormatFromV4lPalette( v4l_version, palette ); imagePixFormat = AV_PIX_FMT_NONE; -#endif // HAVE_LIBSWSCALE } /* V4L2 format matching */ -#if ZM_HAS_V4L2 if ( v4l_version == 2 ) { /* Try to find a match for the selected palette and target colourspace */ @@ -437,14 +355,11 @@ LocalCamera::LocalCamera( /* Unable to find a solution for the selected palette and target colourspace. Conversion required. Notify the user of performance penalty */ } else { if ( capture ) { -#if HAVE_LIBSWSCALE - Info("No direct match for the selected palette (0x%02hhx%02hhx%02hhx%02hhx) and target colorspace (%02u). Format conversion is required, performance penalty expected", - (capturePixFormat>>24)&0xff,((capturePixFormat>>16)&0xff),((capturePixFormat>>8)&0xff),((capturePixFormat)&0xff), colours); -#else - Info("No direct match for the selected palette and target colorspace. Format conversion is required, performance penalty expected"); -#endif + Info( + "No direct match for the selected palette (%d) and target colorspace (%02u). Format conversion is required, performance penalty expected", + capturePixFormat, + colours); } -#if HAVE_LIBSWSCALE /* Try using swscale for the conversion */ conversion_type = 1; Debug(2, "Using swscale for image conversion"); @@ -461,23 +376,15 @@ LocalCamera::LocalCamera( Panic("Unexpected colours: %u",colours); } if ( capture ) { -#if LIBSWSCALE_VERSION_CHECK(0, 8, 0, 8, 0) if ( !sws_isSupportedInput(capturePixFormat) ) { - Error("swscale does not support the used capture format: 0x%02hhx%02hhx%02hhx%02hhx", - (capturePixFormat>>24)&0xff,((capturePixFormat>>16)&0xff),((capturePixFormat>>8)&0xff),((capturePixFormat)&0xff)); + Error("swscale does not support the used capture format: %d", capturePixFormat); conversion_type = 2; /* Try ZM format conversions */ } if ( !sws_isSupportedOutput(imagePixFormat) ) { - Error("swscale does not support the target format: 0x%02hhx%02hhx%02hhx%02hhx", - (imagePixFormat>>24)&0xff,((imagePixFormat>>16)&0xff),((imagePixFormat>>8)&0xff),((imagePixFormat)&0xff)); + Error("swscale does not support the target format: 0x%d", imagePixFormat); conversion_type = 2; /* Try ZM format conversions */ } -#endif } -#else - /* Don't have swscale, see what we can do */ - conversion_type = 2; -#endif /* Our YUYV->Grayscale conversion is a lot faster than swscale's */ if ( colours == ZM_COLOUR_GRAY8 && palette == V4L2_PIX_FMT_YUYV ) { conversion_type = 2; @@ -531,139 +438,19 @@ LocalCamera::LocalCamera( } // end if conversion_type == 2 } // end if needs conversion } // end if v4l2 -#endif // ZM_HAS_V4L2 - - /* V4L1 format matching */ -#if ZM_HAS_V4L1 - if ( v4l_version == 1 ) { - /* Try to find a match for the selected palette and target colourspace */ - - /* RGB32 palette and 32bit target colourspace */ - if ( palette == VIDEO_PALETTE_RGB32 && colours == ZM_COLOUR_RGB32 ) { - conversion_type = 0; - if ( BigEndian ) { - subpixelorder = ZM_SUBPIX_ORDER_ARGB; - } else { - subpixelorder = ZM_SUBPIX_ORDER_BGRA; - } - - /* RGB24 palette and 24bit target colourspace */ - } else if ( palette == VIDEO_PALETTE_RGB24 && colours == ZM_COLOUR_RGB24 ) { - conversion_type = 0; - if ( BigEndian ) { - subpixelorder = ZM_SUBPIX_ORDER_RGB; - } else { - subpixelorder = ZM_SUBPIX_ORDER_BGR; - } - - /* Grayscale palette and grayscale target colourspace */ - } else if ( palette == VIDEO_PALETTE_GREY && colours == ZM_COLOUR_GRAY8 ) { - conversion_type = 0; - subpixelorder = ZM_SUBPIX_ORDER_NONE; - /* Unable to find a solution for the selected palette and target colourspace. Conversion required. Notify the user of performance penalty */ - } else { - if ( capture ) - Info("No direct match for the selected palette and target colorspace. Format conversion is required, performance penalty expected"); -#if HAVE_LIBSWSCALE - /* Try using swscale for the conversion */ - conversion_type = 1; - Debug(2,"Using swscale for image conversion"); - if ( colours == ZM_COLOUR_RGB32 ) { - subpixelorder = ZM_SUBPIX_ORDER_RGBA; - imagePixFormat = AV_PIX_FMT_RGBA; - } else if ( colours == ZM_COLOUR_RGB24 ) { - subpixelorder = ZM_SUBPIX_ORDER_RGB; - imagePixFormat = AV_PIX_FMT_RGB24; - } else if ( colours == ZM_COLOUR_GRAY8 ) { - subpixelorder = ZM_SUBPIX_ORDER_NONE; - imagePixFormat = AV_PIX_FMT_GRAY8; - } else { - Panic("Unexpected colours: %u", colours); - } - if ( capture ) { - if ( !sws_isSupportedInput(capturePixFormat) ) { - Error("swscale does not support the used capture format"); - conversion_type = 2; /* Try ZM format conversions */ - } - if ( !sws_isSupportedOutput(imagePixFormat) ) { - Error("swscale does not support the target format"); - conversion_type = 2; /* Try ZM format conversions */ - } - } - - /* Our YUYV->Grayscale conversion is a lot faster than swscale's */ - if ( colours == ZM_COLOUR_GRAY8 && (palette == VIDEO_PALETTE_YUYV || palette == VIDEO_PALETTE_YUV422) ) { - conversion_type = 2; - } -#else - /* Don't have swscale, see what we can do */ - conversion_type = 2; -#endif - if ( conversion_type == 2 ) { - Debug(2,"Using ZM for image conversion"); - if ( palette == VIDEO_PALETTE_RGB32 && colours == ZM_COLOUR_GRAY8 ) { - if ( BigEndian ) { - conversion_fptr = &std_convert_argb_gray8; - subpixelorder = ZM_SUBPIX_ORDER_NONE; - } else { - conversion_fptr = &std_convert_bgra_gray8; - subpixelorder = ZM_SUBPIX_ORDER_NONE; - } - } else if ( (palette == VIDEO_PALETTE_YUYV || palette == VIDEO_PALETTE_YUV422) && colours == ZM_COLOUR_GRAY8 ) { - /* Fast YUYV->Grayscale conversion by extracting the Y channel */ - if ( config.cpu_extensions && sse_version >= 35 ) { - conversion_fptr = &ssse3_convert_yuyv_gray8; - Debug(2,"Using SSSE3 YUYV->grayscale fast conversion"); - } else { - conversion_fptr = &std_convert_yuyv_gray8; - Debug(2,"Using standard YUYV->grayscale fast conversion"); - } - subpixelorder = ZM_SUBPIX_ORDER_NONE; - } else if ( (palette == VIDEO_PALETTE_YUYV || palette == VIDEO_PALETTE_YUV422) && colours == ZM_COLOUR_RGB24 ) { - conversion_fptr = &zm_convert_yuyv_rgb; - subpixelorder = ZM_SUBPIX_ORDER_RGB; - } else if ( (palette == VIDEO_PALETTE_YUYV || palette == VIDEO_PALETTE_YUV422) && colours == ZM_COLOUR_RGB32 ) { - conversion_fptr = &zm_convert_yuyv_rgba; - subpixelorder = ZM_SUBPIX_ORDER_RGBA; - } else if ( palette == VIDEO_PALETTE_RGB555 && colours == ZM_COLOUR_RGB24 ) { - conversion_fptr = &zm_convert_rgb555_rgb; - subpixelorder = ZM_SUBPIX_ORDER_RGB; - } else if ( palette == VIDEO_PALETTE_RGB555 && colours == ZM_COLOUR_RGB32 ) { - conversion_fptr = &zm_convert_rgb555_rgba; - subpixelorder = ZM_SUBPIX_ORDER_RGBA; - } else if ( palette == VIDEO_PALETTE_RGB565 && colours == ZM_COLOUR_RGB24 ) { - conversion_fptr = &zm_convert_rgb565_rgb; - subpixelorder = ZM_SUBPIX_ORDER_RGB; - } else if ( palette == VIDEO_PALETTE_RGB565 && colours == ZM_COLOUR_RGB32 ) { - conversion_fptr = &zm_convert_rgb565_rgba; - subpixelorder = ZM_SUBPIX_ORDER_RGBA; - } else { - Fatal("Unable to find a suitable format conversion for the selected palette and target colorspace."); - } - } // end if conversion_type == 2 - } - } -#endif // ZM_HAS_V4L1 last_camera = this; Debug(3, "Selected subpixelorder: %u", subpixelorder); -#if HAVE_LIBSWSCALE /* Initialize swscale stuff */ if ( capture and (conversion_type == 1) ) { -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) tmpPicture = av_frame_alloc(); -#else - tmpPicture = avcodec_alloc_frame(); -#endif + if ( !tmpPicture ) Fatal("Could not allocate temporary picture"); -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) unsigned int pSize = av_image_get_buffer_size(imagePixFormat, width, height, 1); -#else - unsigned int pSize = avpicture_get_size(imagePixFormat, width, height); -#endif + if ( pSize != imagesize ) { Fatal("Image size mismatch. Required: %d Available: %llu", pSize, imagesize); } @@ -680,7 +467,6 @@ LocalCamera::LocalCamera( tmpPicture = nullptr; imgConversionContext = nullptr; } // end if capture and conversion_tye == swscale -#endif if ( capture and device_prime ) Initialise(); } // end LocalCamera::LocalCamera @@ -689,7 +475,6 @@ LocalCamera::~LocalCamera() { if ( device_prime && capture ) Terminate(); -#if HAVE_LIBSWSCALE /* Clean up swscale stuff */ if ( capture && (conversion_type == 1) ) { sws_freeContext(imgConversionContext); @@ -697,10 +482,14 @@ LocalCamera::~LocalCamera() { av_frame_free(&tmpPicture); } -#endif - } // end LocalCamera::~LocalCamera +int LocalCamera::Close() { + if (device_prime && capture) + Terminate(); + return 0; +}; + void LocalCamera::Initialise() { Debug(3, "Opening video device %s", device.c_str()); //if ( (vid_fd = open( device.c_str(), O_RDWR|O_NONBLOCK, 0 )) < 0 ) @@ -714,7 +503,6 @@ void LocalCamera::Initialise() { if ( !S_ISCHR(st.st_mode) ) Fatal("File %s is not device file: %s", device.c_str(), strerror(errno)); -#if ZM_HAS_V4L2 Debug(2, "V4L2 support enabled, using V4L%d api", v4l_version); if ( v4l_version == 2 ) { struct v4l2_capability vid_cap; @@ -881,9 +669,8 @@ void LocalCamera::Initialise() { channel_count, v4l_multi_buffer, v4l2_data.reqbufs.count); v4l2_data.buffers = new V4L2MappedBuffer[v4l2_data.reqbufs.count]; -#if HAVE_LIBSWSCALE capturePictures = new AVFrame *[v4l2_data.reqbufs.count]; -#endif // HAVE_LIBSWSCALE + for ( unsigned int i = 0; i < v4l2_data.reqbufs.count; i++ ) { struct v4l2_buffer vid_buf; @@ -905,15 +692,11 @@ void LocalCamera::Initialise() { Fatal("Can't map video buffer %u (%u bytes) to memory: %s(%d)", i, vid_buf.length, strerror(errno), errno); -#if HAVE_LIBSWSCALE -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) capturePictures[i] = av_frame_alloc(); -#else - capturePictures[i] = avcodec_alloc_frame(); -#endif + if ( !capturePictures[i] ) Fatal("Could not allocate picture"); -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) + av_image_fill_arrays( capturePictures[i]->data, capturePictures[i]->linesize, @@ -922,16 +705,6 @@ void LocalCamera::Initialise() { v4l2_data.fmt.fmt.pix.width, v4l2_data.fmt.fmt.pix.height, 1); -#else - avpicture_fill( - (AVPicture *)capturePictures[i], - (uint8_t*)v4l2_data.buffers[i].start, - capturePixFormat, - v4l2_data.fmt.fmt.pix.width, - v4l2_data.fmt.fmt.pix.height - ); -#endif -#endif // HAVE_LIBSWSCALE } // end foreach request buf Debug(3, "Configuring video source"); @@ -964,174 +737,9 @@ void LocalCamera::Initialise() { Hue(hue); Colour(colour); } -#endif // ZM_HAS_V4L2 -#if ZM_HAS_V4L1 - if ( v4l_version == 1 ) { - Debug(3, "Configuring picture attributes"); - - struct video_picture vid_pic; - memset(&vid_pic, 0, sizeof(vid_pic)); - if ( ioctl(vid_fd, VIDIOCGPICT, &vid_pic) < 0 ) - Fatal("Failed to get picture attributes: %s", strerror(errno)); - - Debug(4, - "Old Palette:%d, depth:%d, brightness:%d, hue:%d, colour:%d, contrast:%d", - vid_pic.palette, - vid_pic.depth, - vid_pic.brightness, - vid_pic.hue, - vid_pic.colour, - vid_pic.contrast - ); - - switch (vid_pic.palette = palette) { - case VIDEO_PALETTE_RGB32 : - vid_pic.depth = 32; - break; - case VIDEO_PALETTE_RGB24 : - vid_pic.depth = 24; - break; - case VIDEO_PALETTE_GREY : - vid_pic.depth = 8; - break; - case VIDEO_PALETTE_RGB565 : - case VIDEO_PALETTE_YUYV : - case VIDEO_PALETTE_YUV422 : - case VIDEO_PALETTE_YUV420P : - case VIDEO_PALETTE_YUV422P : - default: - vid_pic.depth = 16; - break; - } - - if ( brightness >= 0 ) vid_pic.brightness = brightness; - if ( hue >= 0 ) vid_pic.hue = hue; - if ( colour >= 0 ) vid_pic.colour = colour; - if ( contrast >= 0 ) vid_pic.contrast = contrast; - - if ( ioctl(vid_fd, VIDIOCSPICT, &vid_pic) < 0 ) { - Error("Failed to set picture attributes: %s", strerror(errno)); - if ( config.strict_video_config ) - exit(-1); - } - - Debug(3, "Configuring window attributes"); - - struct video_window vid_win; - memset(&vid_win, 0, sizeof(vid_win)); - if ( ioctl(vid_fd, VIDIOCGWIN, &vid_win) < 0 ) { - Fatal("Failed to get window attributes: %s", strerror(errno)); - } - Debug(4, "Old X:%d Y:%d W:%d H:%d", - vid_win.x, vid_win.y, vid_win.width, vid_win.height); - - vid_win.x = 0; - vid_win.y = 0; - vid_win.width = width; - vid_win.height = height; - vid_win.flags &= ~VIDEO_WINDOW_INTERLACE; - - if ( ioctl(vid_fd, VIDIOCSWIN, &vid_win) < 0 ) { - Error("Failed to set window attributes: %s", strerror(errno)); - if ( config.strict_video_config ) - exit(-1); - } - - Info("vid_win.width = %08x, vid_win.height = %08x, vid_win.flags = %08x", - vid_win.width, vid_win.height, vid_win.flags); - - Debug(3, "Setting up request buffers"); - if ( ioctl(vid_fd, VIDIOCGMBUF, &v4l1_data.frames) < 0 ) - Fatal("Failed to setup memory: %s", strerror(errno)); - if ( channel_count > 1 && !v4l_multi_buffer ) - v4l1_data.frames.frames = 1; - v4l1_data.buffers = new video_mmap[v4l1_data.frames.frames]; - Debug(4, "vmb.frames = %d, vmb.size = %d", - v4l1_data.frames.frames, v4l1_data.frames.size); - - Debug(3, "Setting up %d frame buffers", v4l1_data.frames.frames); - - v4l1_data.bufptr = (unsigned char *)mmap(0, v4l1_data.frames.size, PROT_READ|PROT_WRITE, MAP_SHARED, vid_fd, 0); - if ( v4l1_data.bufptr == MAP_FAILED ) - Fatal("Could not mmap video: %s", strerror(errno)); - -#if HAVE_LIBSWSCALE - capturePictures = new AVFrame *[v4l1_data.frames.frames]; - for ( int i = 0; i < v4l1_data.frames.frames; i++ ) { - v4l1_data.buffers[i].frame = i; - v4l1_data.buffers[i].width = width; - v4l1_data.buffers[i].height = height; - v4l1_data.buffers[i].format = palette; - -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - capturePictures[i] = av_frame_alloc(); -#else - capturePictures[i] = avcodec_alloc_frame(); -#endif - if ( !capturePictures[i] ) - Fatal("Could not allocate picture"); -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) - av_image_fill_arrays( - capturePictures[i]->data, - capturePictures[i]->linesize, - (unsigned char *)v4l1_data.bufptr+v4l1_data.frames.offsets[i], - capturePixFormat, width, height, 1); -#else - avpicture_fill( - (AVPicture *)capturePictures[i], - (unsigned char *)v4l1_data.bufptr+v4l1_data.frames.offsets[i], - capturePixFormat, width, height ); -#endif - } -#endif // HAVE_LIBSWSCALE - - Debug(3, "Configuring video source"); - - struct video_channel vid_src; - memset(&vid_src, 0, sizeof(vid_src)); - vid_src.channel = channel; - if ( ioctl(vid_fd, VIDIOCGCHAN, &vid_src) < 0 ) - Fatal("Failed to get camera source: %s", strerror(errno)); - - Debug(4, "Old C:%d, F:%d, Fl:%x, T:%d", - vid_src.channel, vid_src.norm, vid_src.flags, vid_src.type); - - vid_src.norm = standard; - vid_src.flags = 0; - vid_src.type = VIDEO_TYPE_CAMERA; - if ( ioctl(vid_fd, VIDIOCSCHAN, &vid_src) < 0 ) { - Error("Failed to set camera source %d: %s", channel, strerror(errno)); - if ( config.strict_video_config ) - exit(-1); - } - - if ( ioctl(vid_fd, VIDIOCGWIN, &vid_win) < 0 ) - Fatal("Failed to get window data: %s", strerror(errno)); - - Info("vid_win.width = %08x, vid_win.height = %08x, vid_win.flags = %08x", - vid_win.width, vid_win.height, vid_win.flags); - - Debug(4, "New X:%d Y:%d W:%d H:%d", - vid_win.x, vid_win.y, vid_win.width, vid_win.height); - - if ( ioctl(vid_fd, VIDIOCGPICT, &vid_pic) < 0 ) - Fatal("Failed to get window data: %s", strerror(errno)); - - Debug(4, - "New Palette:%d, depth:%d, brightness:%d, hue:%d, colour:%d, contrast:%d", - vid_pic.palette, - vid_pic.depth, - vid_pic.brightness, - vid_pic.hue, - vid_pic.colour, - vid_pic.contrast - ); - } // end if v4l -#endif // ZM_HAS_V4L1 } // end LocalCamera::Initialize void LocalCamera::Terminate() { -#if ZM_HAS_V4L2 if ( v4l_version == 2 ) { Debug(3, "Terminating video stream"); //enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; @@ -1142,40 +750,12 @@ void LocalCamera::Terminate() { Debug(3, "Unmapping video buffers"); for ( unsigned int i = 0; i < v4l2_data.reqbufs.count; i++ ) { -#if HAVE_LIBSWSCALE - /* Free capture pictures */ -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) av_frame_free(&capturePictures[i]); -#else - av_freep(&capturePictures[i]); -#endif -#endif + if ( munmap(v4l2_data.buffers[i].start, v4l2_data.buffers[i].length) < 0 ) Error("Failed to munmap buffer %d: %s", i, strerror(errno)); } } -#endif // ZM_HAS_V4L2 - -#if ZM_HAS_V4L1 - if ( v4l_version == 1 ) { -#if HAVE_LIBSWSCALE - for ( int i=0; i < v4l1_data.frames.frames; i++ ) { - /* Free capture pictures */ -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - av_frame_free(&capturePictures[i]); -#else - av_freep(&capturePictures[i]); -#endif - } -#endif - - Debug(3, "Unmapping video buffers"); - if ( munmap((char*)v4l1_data.bufptr, v4l1_data.frames.size) < 0 ) - Error("Failed to munmap buffers: %s", strerror(errno)); - - delete[] v4l1_data.buffers; - } // end if using v4l1 -#endif // ZM_HAS_V4L1 close(vid_fd); primed = false; @@ -1184,7 +764,6 @@ void LocalCamera::Terminate() { uint32_t LocalCamera::AutoSelectFormat(int p_colours) { /* Automatic format selection */ uint32_t selected_palette = 0; -#if ZM_HAS_V4L2 char fmt_desc[64][32]; uint32_t fmt_fcc[64]; v4l2_fmtdesc fmtinfo; @@ -1212,14 +791,14 @@ uint32_t LocalCamera::AutoSelectFormat(int p_colours) { /* Got a format. Copy it to the array */ strcpy(fmt_desc[nIndex], (const char*)(fmtinfo.description)); fmt_fcc[nIndex] = fmtinfo.pixelformat; - + Debug(3, "Got format: %s (0x%02hhx%02hhx%02hhx%02hhx) at index %d", - fmt_desc[nIndex], - (fmt_fcc[nIndex]>>24)&0xff, - (fmt_fcc[nIndex]>>16)&0xff, - (fmt_fcc[nIndex]>>8)&0xff, - (fmt_fcc[nIndex])&0xff, - nIndex); + fmt_desc[nIndex], + static_cast((fmt_fcc[nIndex] >> 24) & 0xff), + static_cast((fmt_fcc[nIndex] >> 16) & 0xff), + static_cast((fmt_fcc[nIndex] >> 8) & 0xff), + static_cast((fmt_fcc[nIndex]) & 0xff), + nIndex); /* Proceed to the next index */ memset(&fmtinfo, 0, sizeof(fmtinfo)); @@ -1248,13 +827,23 @@ uint32_t LocalCamera::AutoSelectFormat(int p_colours) { for ( unsigned int j=0; j < nIndex; j++ ) { if ( preferedformats[i] == fmt_fcc[j] ) { Debug(6, "Choosing format: %s (0x%02hhx%02hhx%02hhx%02hhx) at index %u", - fmt_desc[j],fmt_fcc[j]&0xff, (fmt_fcc[j]>>8)&0xff, (fmt_fcc[j]>>16)&0xff, (fmt_fcc[j]>>24)&0xff ,j); + fmt_desc[j], + static_cast(fmt_fcc[j] & 0xff), + static_cast((fmt_fcc[j] >> 8) & 0xff), + static_cast((fmt_fcc[j] >> 16) & 0xff), + static_cast((fmt_fcc[j] >> 24) & 0xff), + j); /* Found a format! */ nIndexUsed = j; break; } else { Debug(6, "No match for format: %s (0x%02hhx%02hhx%02hhx%02hhx) at index %u", - fmt_desc[j],fmt_fcc[j]&0xff, (fmt_fcc[j]>>8)&0xff, (fmt_fcc[j]>>16)&0xff, (fmt_fcc[j]>>24)&0xff ,j); + fmt_desc[j], + static_cast(fmt_fcc[j] & 0xff), + static_cast((fmt_fcc[j] >> 8) & 0xff), + static_cast((fmt_fcc[j] >> 16) & 0xff), + static_cast((fmt_fcc[j] >> 24) & 0xff), + j); } } } @@ -1269,7 +858,6 @@ uint32_t LocalCamera::AutoSelectFormat(int p_colours) { /* Close the device */ close(enum_fd); -#endif /* ZM_HAS_V4L2 */ return selected_palette; } //uint32_t LocalCamera::AutoSelectFormat(int p_colours) @@ -1277,54 +865,56 @@ uint32_t LocalCamera::AutoSelectFormat(int p_colours) { (test) ? (prefix yesString " " capability "\n") : (prefix noString " " capability "\n") bool LocalCamera::GetCurrentSettings( - const char *device, + const std::string& device, char *output, int version, bool verbose) { output[0] = 0; char *output_ptr = output; - char queryDevice[PATH_MAX] = ""; + std::string queryDevice; int devIndex = 0; do { - if ( device ) { - strncpy(queryDevice, device, sizeof(queryDevice)-1); + if (!device.empty()) { + queryDevice = device; } else { - sprintf(queryDevice, "/dev/video%d", devIndex); + queryDevice = stringtf("/dev/video%d", devIndex); } - if ( (vid_fd = open(queryDevice, O_RDWR)) <= 0 ) { - if ( device ) { - Error("Failed to open video device %s: %s", queryDevice, strerror(errno)); - if ( verbose ) + if ((vid_fd = open(queryDevice.c_str(), O_RDWR)) <= 0) { + if (!device.empty()) { + Error("Failed to open video device %s: %s", queryDevice.c_str(), strerror(errno)); + if (verbose) { output_ptr += sprintf(output_ptr, "Error, failed to open video device %s: %s\n", - queryDevice, strerror(errno)); - else + queryDevice.c_str(), strerror(errno)); + } else { output_ptr += sprintf(output_ptr, "error%d\n", errno); + } return false; } else { return true; } } - if ( verbose ) { - output_ptr += sprintf(output_ptr, "Video Device: %s\n", queryDevice); + + if (verbose) { + output_ptr += sprintf(output_ptr, "Video Device: %s\n", queryDevice.c_str()); } else { - output_ptr += sprintf(output_ptr, "d:%s|", queryDevice); + output_ptr += sprintf(output_ptr, "d:%s|", queryDevice.c_str()); } -#if ZM_HAS_V4L2 - if ( version == 2 ) { - struct v4l2_capability vid_cap; - if ( vidioctl(vid_fd, VIDIOC_QUERYCAP, &vid_cap) < 0 ) { + if (version == 2) { + v4l2_capability vid_cap = {}; + if (vidioctl(vid_fd, VIDIOC_QUERYCAP, &vid_cap) < 0) { Error("Failed to query video device: %s", strerror(errno)); - if ( verbose ) { + if (verbose) { output_ptr += sprintf(output_ptr, "Error, failed to query video capabilities %s: %s\n", - queryDevice, strerror(errno)); + queryDevice.c_str(), strerror(errno)); } else { output_ptr += sprintf(output_ptr, "error%d\n", errno); } - if ( device ) + if (!device.empty()) { return false; + } } if ( verbose ) { @@ -1367,7 +957,7 @@ bool LocalCamera::GetCurrentSettings( output_ptr += sprintf(output_ptr, verbose ? " Standards:\n" : "S:"); - struct v4l2_standard standard; + v4l2_standard standard = {}; int standardIndex = 0; do { memset(&standard, 0, sizeof(standard)); @@ -1395,10 +985,10 @@ bool LocalCamera::GetCurrentSettings( *(output_ptr-1) = '|'; output_ptr += sprintf(output_ptr, verbose ? " Formats:\n" : "F:"); - struct v4l2_fmtdesc format; + int formatIndex = 0; do { - memset(&format, 0, sizeof(format)); + v4l2_fmtdesc format = {}; format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; format.index = formatIndex; @@ -1439,9 +1029,9 @@ bool LocalCamera::GetCurrentSettings( else output_ptr += sprintf(output_ptr, "Crop Capabilities\n"); - struct v4l2_cropcap cropcap; - memset(&cropcap, 0, sizeof(cropcap)); + v4l2_cropcap cropcap = {}; cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if ( vidioctl(vid_fd, VIDIOC_CROPCAP, &cropcap) < 0 ) { if ( errno != EINVAL ) { /* Failed querying crop capability, write error to the log and continue as if crop is not supported */ @@ -1455,8 +1045,8 @@ bool LocalCamera::GetCurrentSettings( output_ptr += sprintf(output_ptr, "B:%dx%d|", 0, 0); } } else { - struct v4l2_crop crop; - memset(&crop, 0, sizeof(crop)); + v4l2_crop crop = {}; + crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if ( vidioctl(vid_fd, VIDIOC_G_CROP, &crop) < 0 ) { @@ -1541,17 +1131,17 @@ bool LocalCamera::GetCurrentSettings( " Name: %s\n" " Type: %s\n" " Audioset: %08x\n" - " Standards: 0x%llx\n" + " Standards: 0x%" PRIx64"\n" , input.index , input.name , input.type==V4L2_INPUT_TYPE_TUNER?"Tuner":(input.type==V4L2_INPUT_TYPE_CAMERA?"Camera":"Unknown") , input.audioset - , input.std ); + , static_cast(input.std)); } else { - output_ptr += sprintf( output_ptr, "i%d:%s|i%dT:%s|i%dS:%llx|" + output_ptr += sprintf( output_ptr, "i%d:%s|i%dT:%s|i%dS:%" PRIx64 "|" , input.index, input.name , input.index, input.type==V4L2_INPUT_TYPE_TUNER?"Tuner":(input.type==V4L2_INPUT_TYPE_CAMERA?"Camera":"Unknown") - , input.index, input.std); + , input.index, static_cast(input.std)); } if ( verbose ) { @@ -1571,209 +1161,16 @@ bool LocalCamera::GetCurrentSettings( if ( !verbose ) *(output_ptr-1) = '\n'; } -#endif // ZM_HAS_V4L2 -#if ZM_HAS_V4L1 - if ( version == 1 ) { - struct video_capability vid_cap; - memset(&vid_cap, 0, sizeof(video_capability)); - if ( ioctl(vid_fd, VIDIOCGCAP, &vid_cap) < 0 ) { - Error("Failed to get video capabilities: %s", strerror(errno)); - if ( verbose ) - output_ptr += sprintf(output_ptr, - "Error, failed to get video capabilities %s: %s\n", - queryDevice, strerror(errno)); - else - output_ptr += sprintf(output_ptr, "error%d\n", errno); - return false; - } - if ( verbose ) { - output_ptr += sprintf( output_ptr, "Video Capabilities\n" - " Name: %s\n" - " Type: %d\n%s%s%s%s%s%s%s%s%s%s%s%s%s%s" - " Video Channels: %d\n" - " Audio Channels: %d\n" - " Maximum Width: %d\n" - " Maximum Height: %d\n" - " Minimum Width: %d\n" - " Minimum Height: %d\n", - vid_cap.name, - vid_cap.type, - (vid_cap.type&VID_TYPE_CAPTURE)?" Can capture\n":"", - (vid_cap.type&VID_TYPE_TUNER)?" Can tune\n":"", - (vid_cap.type&VID_TYPE_TELETEXT)?" Does teletext\n":"", - (vid_cap.type&VID_TYPE_OVERLAY)?" Overlay onto frame buffer\n":"", - (vid_cap.type&VID_TYPE_CHROMAKEY)?" Overlay by chromakey\n":"", - (vid_cap.type&VID_TYPE_CLIPPING)?" Can clip\n":"", - (vid_cap.type&VID_TYPE_FRAMERAM)?" Uses the frame buffer memory\n":"", - (vid_cap.type&VID_TYPE_SCALES)?" Scalable\n":"", - (vid_cap.type&VID_TYPE_MONOCHROME)?" Monochrome only\n":"", - (vid_cap.type&VID_TYPE_SUBCAPTURE)?" Can capture subareas of the image\n":"", - (vid_cap.type&VID_TYPE_MPEG_DECODER)?" Can decode MPEG streams\n":"", - (vid_cap.type&VID_TYPE_MPEG_ENCODER)?" Can encode MPEG streams\n":"", - (vid_cap.type&VID_TYPE_MJPEG_DECODER)?" Can decode MJPEG streams\n":"", - (vid_cap.type&VID_TYPE_MJPEG_ENCODER)?" Can encode MJPEG streams\n":"", - vid_cap.channels, - vid_cap.audios, - vid_cap.maxwidth, - vid_cap.maxheight, - vid_cap.minwidth, - vid_cap.minheight ); - } else { - output_ptr += sprintf(output_ptr, "N:%s|T:%d|nC:%d|nA:%d|mxW:%d|mxH:%d|mnW:%d|mnH:%d|" - , vid_cap.name - , vid_cap.type - , vid_cap.channels - , vid_cap.audios - , vid_cap.maxwidth - , vid_cap.maxheight - , vid_cap.minwidth - , vid_cap.minheight); - } - struct video_window vid_win; - memset(&vid_win, 0, sizeof(video_window)); - if ( ioctl(vid_fd, VIDIOCGWIN, &vid_win) < 0 ) { - Error("Failed to get window attributes: %s", strerror(errno)); - if ( verbose ) - output_ptr += sprintf(output_ptr, "Error, failed to get window attributes: %s\n", strerror(errno)); - else - output_ptr += sprintf(output_ptr, "error%d\n", errno); - return false; - } - if ( verbose ) { - output_ptr += sprintf(output_ptr, - "Window Attributes\n" - " X Offset: %d\n" - " Y Offset: %d\n" - " Width: %d\n" - " Height: %d\n" - , vid_win.x - , vid_win.y - , vid_win.width - , vid_win.height ); - } else { - output_ptr += sprintf(output_ptr, "X:%d|Y:%d|W:%d|H:%d|", - vid_win.height, vid_win.x, vid_win.y, vid_win.width); - } - - struct video_picture vid_pic; - memset(&vid_pic, 0, sizeof(video_picture)); - if ( ioctl(vid_fd, VIDIOCGPICT, &vid_pic) < 0 ) { - Error("Failed to get picture attributes: %s", strerror(errno)); - if ( verbose ) - output_ptr += sprintf(output_ptr, "Error, failed to get picture attributes: %s\n", strerror(errno)); - else - output_ptr += sprintf(output_ptr, "error%d\n", errno); - return false; - } - if ( verbose ) { - output_ptr += sprintf(output_ptr, - "Picture Attributes\n" - " Palette: %d - %s\n" - " Colour Depth: %d\n" - " Brightness: %d\n" - " Hue: %d\n" - " Colour :%d\n" - " Contrast: %d\n" - " Whiteness: %d\n" - , vid_pic.palette, - vid_pic.palette==VIDEO_PALETTE_GREY?"Linear greyscale":( - vid_pic.palette==VIDEO_PALETTE_HI240?"High 240 cube (BT848)":( - vid_pic.palette==VIDEO_PALETTE_RGB565?"565 16 bit RGB":( - vid_pic.palette==VIDEO_PALETTE_RGB24?"24bit RGB":( - vid_pic.palette==VIDEO_PALETTE_RGB32?"32bit RGB":( - vid_pic.palette==VIDEO_PALETTE_RGB555?"555 15bit RGB":( - vid_pic.palette==VIDEO_PALETTE_YUV422?"YUV422 capture":( - vid_pic.palette==VIDEO_PALETTE_YUYV?"YUYV":( - vid_pic.palette==VIDEO_PALETTE_UYVY?"UVYV":( - vid_pic.palette==VIDEO_PALETTE_YUV420?"YUV420":( - vid_pic.palette==VIDEO_PALETTE_YUV411?"YUV411 capture":( - vid_pic.palette==VIDEO_PALETTE_RAW?"RAW capture (BT848)":( - vid_pic.palette==VIDEO_PALETTE_YUYV?"YUYV":( - vid_pic.palette==VIDEO_PALETTE_YUV422?"YUV422":( - vid_pic.palette==VIDEO_PALETTE_YUV422P?"YUV 4:2:2 Planar":( - vid_pic.palette==VIDEO_PALETTE_YUV411P?"YUV 4:1:1 Planar":( - vid_pic.palette==VIDEO_PALETTE_YUV420P?"YUV 4:2:0 Planar":( - vid_pic.palette==VIDEO_PALETTE_YUV410P?"YUV 4:1:0 Planar":"Unknown" - ))))))))))))))))), - vid_pic.depth, - vid_pic.brightness, - vid_pic.hue, - vid_pic.colour, - vid_pic.contrast, - vid_pic.whiteness - ); - } else { - output_ptr += sprintf(output_ptr, "P:%d|D:%d|B:%d|h:%d|Cl:%d|Cn:%d|w:%d|", - vid_pic.palette, - vid_pic.depth, - vid_pic.brightness, - vid_pic.hue, - vid_pic.colour, - vid_pic.contrast, - vid_pic.whiteness - ); - } - - for ( int chan = 0; chan < vid_cap.channels; chan++ ) { - struct video_channel vid_src; - memset(&vid_src, 0, sizeof(video_channel)); - vid_src.channel = chan; - if ( ioctl(vid_fd, VIDIOCGCHAN, &vid_src) < 0 ) { - Error("Failed to get channel %d attributes: %s", chan, strerror(errno)); - if ( verbose ) - output_ptr += sprintf(output_ptr, "Error, failed to get channel %d attributes: %s\n", chan, strerror(errno)); - else - output_ptr += sprintf(output_ptr, "error%d\n", errno); - return false; - } - if ( verbose ) { - output_ptr += sprintf(output_ptr, - "Channel %d Attributes\n" - " Name: %s\n" - " Channel: %d\n" - " Flags: %d\n%s%s" - " Type: %d - %s\n" - " Format: %d - %s\n" - , chan - , vid_src.name - , vid_src.channel - , vid_src.flags - , (vid_src.flags&VIDEO_VC_TUNER)?" Channel has a tuner\n":"" - , (vid_src.flags&VIDEO_VC_AUDIO)?" Channel has audio\n":"" - , vid_src.type, - vid_src.type==VIDEO_TYPE_TV?"TV":( - vid_src.type==VIDEO_TYPE_CAMERA?"Camera":"Unknown" - ) - , vid_src.norm, - vid_src.norm==VIDEO_MODE_PAL?"PAL":( - vid_src.norm==VIDEO_MODE_NTSC?"NTSC":( - vid_src.norm==VIDEO_MODE_SECAM?"SECAM":( - vid_src.norm==VIDEO_MODE_AUTO?"AUTO":"Unknown" - )))); - } else { - output_ptr += sprintf(output_ptr, "n%d:%s|C%d:%d|Fl%d:%x|T%d:%d|F%d:%d%s|" - , chan, vid_src.name - , chan, vid_src.channel - , chan, vid_src.flags - , chan, vid_src.type - , chan, vid_src.norm, chan==(vid_cap.channels-1)?"":"," - ); - } - } - if ( !verbose ) - *output_ptr = '\n'; - } -#endif // ZM_HAS_V4L1 close(vid_fd); - if ( device ) + if (!device.empty()) { break; + } } while ( ++devIndex < 32 ); return true; } int LocalCamera::Brightness(int p_brightness) { -#if ZM_HAS_V4L2 if ( v4l_version == 2 ) { struct v4l2_control vid_control; @@ -1803,31 +1200,10 @@ int LocalCamera::Brightness(int p_brightness) { } return vid_control.value; } -#endif // ZM_HAS_V4L2 -#if ZM_HAS_V4L1 - if ( v4l_version == 1 ) { - struct video_picture vid_pic; - memset(&vid_pic, 0, sizeof(video_picture)); - if ( ioctl(vid_fd, VIDIOCGPICT, &vid_pic) < 0 ) { - Error("Failed to get picture attributes: %s", strerror(errno)); - return -1; - } - - if ( p_brightness >= 0 ) { - vid_pic.brightness = p_brightness; - if ( ioctl(vid_fd, VIDIOCSPICT, &vid_pic) < 0 ) { - Error("Failed to set picture attributes: %s", strerror(errno)); - return -1; - } - } - return vid_pic.brightness; - } -#endif // ZM_HAS_V4L1 return -1; } int LocalCamera::Hue(int p_hue) { -#if ZM_HAS_V4L2 if ( v4l_version == 2 ) { struct v4l2_control vid_control; @@ -1853,31 +1229,10 @@ int LocalCamera::Hue(int p_hue) { } return vid_control.value; } -#endif // ZM_HAS_V4L2 -#if ZM_HAS_V4L1 - if ( v4l_version == 1 ) { - struct video_picture vid_pic; - memset(&vid_pic, 0, sizeof(video_picture)); - if ( ioctl(vid_fd, VIDIOCGPICT, &vid_pic) < 0 ) { - Error("Failed to get picture attributes: %s", strerror(errno)); - return -1; - } - - if ( p_hue >= 0 ) { - vid_pic.hue = p_hue; - if ( ioctl(vid_fd, VIDIOCSPICT, &vid_pic) < 0 ) { - Error("Failed to set picture attributes: %s", strerror(errno)); - return -1; - } - } - return vid_pic.hue; - } -#endif // ZM_HAS_V4L1 return -1; } int LocalCamera::Colour( int p_colour ) { -#if ZM_HAS_V4L2 if ( v4l_version == 2 ) { struct v4l2_control vid_control; @@ -1904,31 +1259,10 @@ int LocalCamera::Colour( int p_colour ) { } return vid_control.value; } -#endif // ZM_HAS_V4L2 -#if ZM_HAS_V4L1 - if ( v4l_version == 1 ) { - struct video_picture vid_pic; - memset(&vid_pic, 0, sizeof(video_picture)); - if ( ioctl(vid_fd, VIDIOCGPICT, &vid_pic) < 0) { - Error("Failed to get picture attributes: %s", strerror(errno)); - return -1; - } - - if ( p_colour >= 0 ) { - vid_pic.colour = p_colour; - if ( ioctl(vid_fd, VIDIOCSPICT, &vid_pic) < 0 ) { - Error("Failed to set picture attributes: %s", strerror(errno)); - return -1; - } - } - return vid_pic.colour; - } -#endif // ZM_HAS_V4L1 return -1; } int LocalCamera::Contrast( int p_contrast ) { -#if ZM_HAS_V4L2 if ( v4l_version == 2 ) { struct v4l2_control vid_control; @@ -1955,26 +1289,6 @@ int LocalCamera::Contrast( int p_contrast ) { } return vid_control.value; } -#endif // ZM_HAS_V4L2 -#if ZM_HAS_V4L1 - if ( v4l_version == 1 ) { - struct video_picture vid_pic; - memset(&vid_pic, 0, sizeof(video_picture)); - if ( ioctl(vid_fd, VIDIOCGPICT, &vid_pic) < 0 ) { - Error("Failed to get picture attributes: %s", strerror(errno)); - return -1; - } - - if ( p_contrast >= 0 ) { - vid_pic.contrast = p_contrast; - if ( ioctl(vid_fd, VIDIOCSPICT, &vid_pic) < 0 ) { - Error("Failed to set picture attributes: %s", strerror(errno)); - return -1; - } - } - return vid_pic.contrast; - } -#endif // ZM_HAS_V4L1 return -1; } @@ -1984,7 +1298,6 @@ int LocalCamera::PrimeCapture() { return 1; Debug(2, "Priming capture"); -#if ZM_HAS_V4L2 if ( v4l_version == 2 ) { Debug(3, "Queueing (%d) buffers", v4l2_data.reqbufs.count); for ( unsigned int frame = 0; frame < v4l2_data.reqbufs.count; frame++ ) { @@ -2015,18 +1328,6 @@ int LocalCamera::PrimeCapture() { return -1; } } // end if v4l_version == 2 -#endif // ZM_HAS_V4L2 -#if ZM_HAS_V4L1 - if ( v4l_version == 1 ) { - for ( int frame = 0; frame < v4l1_data.frames.frames; frame++ ) { - Debug(3, "Queueing frame %d", frame); - if ( ioctl(vid_fd, VIDIOCMCAPTURE, &v4l1_data.buffers[frame]) < 0 ) { - Error("Capture failure for frame %d: %s", frame, strerror(errno)); - return -1; - } - } - } -#endif // ZM_HAS_V4L1 return 1; } // end LocalCamera::PrimeCapture @@ -2036,23 +1337,22 @@ int LocalCamera::PreCapture() { return 1; } -int LocalCamera::Capture(ZMPacket &zm_packet) { +int LocalCamera::Capture(std::shared_ptr &zm_packet) { // We assume that the avpacket is allocated, and just needs to be filled static uint8_t* buffer = nullptr; int buffer_bytesused = 0; int capture_frame = -1; int captures_per_frame = 1; - if ( channel_count > 1 ) + if (channel_count > 1) captures_per_frame = v4l_captures_per_frame; - if ( captures_per_frame <= 0 ) { + if (captures_per_frame <= 0) { captures_per_frame = 1; Warning("Invalid Captures Per Frame setting: %d", captures_per_frame); } // Do the capture, unless we are the second or subsequent camera on a channel, in which case just reuse the buffer if ( channel_prime ) { -#if ZM_HAS_V4L2 if ( v4l_version == 2 ) { static struct v4l2_buffer vid_buf; @@ -2099,37 +1399,7 @@ int LocalCamera::Capture(ZMPacket &zm_packet) { v4l2_data.fmt.fmt.pix.width, v4l2_data.fmt.fmt.pix.height, width, height); } } // end if v4l2 -#if ZM_HAS_V4L1 - else -#endif // ZM_HAS_V4L1 -#endif // ZM_HAS_V4L2 -#if ZM_HAS_V4L1 - if ( v4l_version == 1 ) { - Debug(3, "Capturing %d frames", captures_per_frame); - while ( captures_per_frame ) { - Debug(3, "Syncing frame %d", v4l1_data.active_frame); - if ( ioctl(vid_fd, VIDIOCSYNC, &v4l1_data.active_frame) < 0 ) { - Error("Sync failure for frame %d buffer %d: %s", - v4l1_data.active_frame, captures_per_frame, strerror(errno) ); - return -1; - } - captures_per_frame--; - if ( captures_per_frame ) { - Debug(3, "Capturing frame %d", v4l1_data.active_frame); - if ( ioctl(vid_fd, VIDIOCMCAPTURE, &v4l1_data.buffers[v4l1_data.active_frame]) < 0 ) { - Error("Capture failure for buffer %d (%d): %s", - v4l1_data.active_frame, captures_per_frame, strerror(errno)); - return -1; - } - } - } - capture_frame = v4l1_data.active_frame; - Debug(3, "Captured %d for channel %d", capture_frame, channel); - buffer = v4l1_data.bufptr+v4l1_data.frames.offsets[capture_frame]; - } -#endif // ZM_HAS_V4L1 -#if ZM_HAS_V4L2 if ( v4l_version == 2 ) { if ( channel_count > 1 ) { int next_channel = (channel_index+1)%channel_count; @@ -2154,69 +1424,29 @@ int LocalCamera::Capture(ZMPacket &zm_packet) { Error("Unable to requeue buffer due to not v4l2_data"); } } -#if ZM_HAS_V4L1 - else -#endif // ZM_HAS_V4L1 -#endif // ZM_HAS_V4L2 -#if ZM_HAS_V4L1 - if ( v4l_version == 1 ) { - if ( channel_count > 1 ) { - Debug(3, "Switching video source"); - int next_channel = (channel_index+1)%channel_count; - struct video_channel vid_src; - memset(&vid_src, 0, sizeof(vid_src)); - vid_src.channel = channel; - if ( ioctl(vid_fd, VIDIOCGCHAN, &vid_src) < 0 ) { - Error("Failed to get camera source %d: %s", channel, strerror(errno)); - return -1; - } - - vid_src.channel = channels[next_channel]; - vid_src.norm = standards[next_channel]; - vid_src.flags = 0; - vid_src.type = VIDEO_TYPE_CAMERA; - if ( ioctl(vid_fd, VIDIOCSCHAN, &vid_src) < 0 ) { - Error("Failed to set camera source %d: %s", channel, strerror(errno)); - return -1; - } - } - Debug(3, "Requeueing frame %d", v4l1_data.active_frame); - if ( ioctl(vid_fd, VIDIOCMCAPTURE, &v4l1_data.buffers[v4l1_data.active_frame]) < 0 ) { - Error("Capture failure for frame %d: %s", v4l1_data.active_frame, strerror(errno)); - return -1; - } - v4l1_data.active_frame = (v4l1_data.active_frame+1)%v4l1_data.frames.frames; - } -#endif // ZM_HAS_V4L1 - } /* prime capture */ - if (!zm_packet.image) { + if (!zm_packet->image) { Debug(4, "Allocating image"); - zm_packet.image = new Image(width, height, colours, subpixelorder); + zm_packet->image = new Image(width, height, colours, subpixelorder); } - if ( conversion_type != 0 ) { + if (conversion_type != 0) { Debug(3, "Performing format conversion %d", conversion_type); /* Request a writeable buffer of the target image */ - uint8_t *directbuffer = zm_packet.image->WriteBuffer(width, height, colours, subpixelorder); - if ( directbuffer == nullptr ) { + uint8_t *directbuffer = zm_packet->image->WriteBuffer(width, height, colours, subpixelorder); + if (directbuffer == nullptr) { Error("Failed requesting writeable buffer for the captured image."); return -1; } -#if HAVE_LIBSWSCALE - if ( conversion_type == 1 ) { + if (conversion_type == 1) { Debug(9, "Calling sws_scale to perform the conversion"); /* Use swscale to convert the image directly into the shared memory */ -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) av_image_fill_arrays(tmpPicture->data, tmpPicture->linesize, directbuffer, imagePixFormat, width, height, 1); -#else - avpicture_fill( (AVPicture *)tmpPicture, directbuffer, - imagePixFormat, width, height ); -#endif + sws_scale( imgConversionContext, capturePictures[capture_frame]->data, @@ -2226,9 +1456,7 @@ int LocalCamera::Capture(ZMPacket &zm_packet) { tmpPicture->data, tmpPicture->linesize ); - } else -#endif - if ( conversion_type == 2 ) { + } else if (conversion_type == 2) { Debug(9, "Calling the conversion function"); /* Call the image conversion function and convert directly into the shared memory */ (*conversion_fptr)(buffer, directbuffer, pixels); @@ -2236,24 +1464,23 @@ int LocalCamera::Capture(ZMPacket &zm_packet) { // Need to store the jpeg data too Debug(9, "Decoding the JPEG image"); /* JPEG decoding */ - zm_packet.image->DecodeJpeg(buffer, buffer_bytesused, colours, subpixelorder); + zm_packet->image->DecodeJpeg(buffer, buffer_bytesused, colours, subpixelorder); } - } else { Debug(3, "No format conversion performed. Assigning the image"); /* No conversion was performed, the image is in the V4L buffers and needs to be copied into the shared memory */ - zm_packet.image->Assign(width, height, colours, subpixelorder, buffer, imagesize); + zm_packet->image->Assign(width, height, colours, subpixelorder, buffer, imagesize); } // end if doing conversion or not - zm_packet.packet.stream_index = mVideoStreamId; - zm_packet.stream = mVideoStream; - zm_packet.codec_type = AVMEDIA_TYPE_VIDEO; - zm_packet.keyframe = 1; + zm_packet->packet.stream_index = mVideoStreamId; + zm_packet->stream = mVideoStream; + zm_packet->codec_type = AVMEDIA_TYPE_VIDEO; + zm_packet->keyframe = 1; return 1; } // end int LocalCamera::Capture() int LocalCamera::PostCapture() { return 1; } -#endif // ZM_HAS_V4L +#endif // ZM_HAS_V4L2 diff --git a/src/zm_local_camera.h b/src/zm_local_camera.h index 6d3ff2672..f6991c5de 100644 --- a/src/zm_local_camera.h +++ b/src/zm_local_camera.h @@ -22,22 +22,9 @@ #include "zm_camera.h" -#if ZM_HAS_V4L +#if ZM_HAS_V4L2 -#ifdef HAVE_LINUX_VIDEODEV_H -#include -#endif // HAVE_LINUX_VIDEODEV_H -#ifdef HAVE_LIBV4L1_VIDEODEV_H -#include -#endif // HAVE_LIB4VL1_VIDEODEV_H -#ifdef HAVE_LINUX_VIDEODEV2_H #include -#endif // HAVE_LINUX_VIDEODEV2_H - -// Required on systems with v4l1 but without v4l2 headers -#ifndef VIDEO_MAX_FRAME -#define VIDEO_MAX_FRAME 32 -#endif // // Class representing 'local' cameras, i.e. those which are @@ -46,7 +33,6 @@ // class LocalCamera : public Camera { protected: -#if ZM_HAS_V4L2 struct V4L2MappedBuffer { void *start; size_t length; @@ -60,16 +46,6 @@ protected: V4L2MappedBuffer *buffers; v4l2_buffer *bufptr; }; -#endif // ZM_HAS_V4L2 - -#if ZM_HAS_V4L1 - struct V4L1Data { - int active_frame; - video_mbuf frames; - video_mmap *buffers; - unsigned char *bufptr; - }; -#endif // ZM_HAS_V4L1 protected: std::string device; @@ -95,20 +71,13 @@ protected: bool v4l_multi_buffer; unsigned int v4l_captures_per_frame; -#if ZM_HAS_V4L2 static V4L2Data v4l2_data; -#endif // ZM_HAS_V4L2 -#if ZM_HAS_V4L1 - static V4L1Data v4l1_data; -#endif // ZM_HAS_V4L1 -#if HAVE_LIBSWSCALE static AVFrame **capturePictures; _AVPIXELFORMAT imagePixFormat; _AVPIXELFORMAT capturePixFormat; struct SwsContext *imgConversionContext; AVFrame *tmpPicture; -#endif // HAVE_LIBSWSCALE static LocalCamera *last_camera; @@ -151,13 +120,12 @@ public: int PrimeCapture() override; int PreCapture() override; - int Capture(ZMPacket &p) override; + int Capture(std::shared_ptr &p) override; int PostCapture() override; - int Close() override { return 0; }; - - static bool GetCurrentSettings(const char *device, char *output, int version, bool verbose); + int Close() override; + static bool GetCurrentSettings(const std::string &device, char *output, int version, bool verbose); }; -#endif // ZM_HAS_V4L +#endif // ZM_HAS_V4L2 #endif // ZM_LOCAL_CAMERA_H diff --git a/src/zm_logger.cpp b/src/zm_logger.cpp index dd03ec9f2..5c7c5e9a7 100644 --- a/src/zm_logger.cpp +++ b/src/zm_logger.cpp @@ -20,10 +20,10 @@ #include "zm_logger.h" #include "zm_db.h" +#include "zm_time.h" #include "zm_utils.h" #include #include -#include #include #ifdef __FreeBSD__ @@ -425,30 +425,20 @@ void Logger::logPrint(bool hex, const char *filepath, int line, int level, const char timeString[64]; char logString[4096]; // SQL TEXT can hold 64k so we could go up to 32k here but why? va_list argPtr; - struct timeval timeVal; const char *base = strrchr(filepath, '/'); const char *file = base ? base+1 : filepath; const char *classString = smCodes[level].c_str(); - gettimeofday(&timeVal, nullptr); + SystemTimePoint now = std::chrono::system_clock::now(); + time_t now_sec = std::chrono::system_clock::to_time_t(now); + Microseconds now_frac = std::chrono::duration_cast( + now.time_since_epoch() - std::chrono::duration_cast(now.time_since_epoch())); -#if 0 - if ( logRuntime ) { - static struct timeval logStart; - - subtractTime( &timeVal, &logStart ); - - snprintf( timeString, sizeof(timeString), "%ld.%03ld", timeVal.tv_sec, timeVal.tv_usec/1000 ); - } else { -#endif - char *timePtr = timeString; - tm now_tm = {}; - timePtr += strftime(timePtr, sizeof(timeString), "%x %H:%M:%S", localtime_r(&timeVal.tv_sec, &now_tm)); - snprintf(timePtr, sizeof(timeString)-(timePtr-timeString), ".%06ld", timeVal.tv_usec); -#if 0 - } -#endif + char *timePtr = timeString; + tm now_tm = {}; + timePtr += strftime(timePtr, sizeof(timeString), "%x %H:%M:%S", localtime_r(&now_sec, &now_tm)); + snprintf(timePtr, sizeof(timeString) - (timePtr - timeString), ".%06" PRIi64, static_cast(now_frac.count())); pid_t tid; #ifdef __FreeBSD__ @@ -528,17 +518,15 @@ void Logger::logPrint(bool hex, const char *filepath, int line, int level, const if (level <= mDatabaseLevel) { if (zmDbConnected) { - int syslogSize = syslogEnd-syslogStart; - char escapedString[(syslogSize*2)+1]; - mysql_real_escape_string(&dbconn, escapedString, syslogStart, syslogSize); + std::string escapedString = zmDbEscapeString({syslogStart, syslogEnd}); std::string sql_string = stringtf( "INSERT INTO `Logs` " "( `TimeKey`, `Component`, `ServerId`, `Pid`, `Level`, `Code`, `Message`, `File`, `Line` )" " VALUES " - "( %ld.%06ld, '%s', %d, %d, %d, '%s', '%s', '%s', %d )", - timeVal.tv_sec, timeVal.tv_usec, mId.c_str(), staticConfig.SERVER_ID, tid, level, classString, escapedString, file, line - ); + "( %ld.%06" PRIi64 ", '%s', %d, %d, %d, '%s', '%s', '%s', %d )", + now_sec, static_cast(now_frac.count()), mId.c_str(), staticConfig.SERVER_ID, tid, level, classString, + escapedString.c_str(), file, line); dbQueue.push(std::move(sql_string)); } else { puts("Db is closed"); diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 9c58a8ffb..32b9161ac 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -21,27 +21,21 @@ #include "zm_group.h" #include "zm_eventstream.h" +#include "zm_ffmpeg_camera.h" #include "zm_fifo.h" #include "zm_file_camera.h" #include "zm_remote_camera.h" #include "zm_remote_camera_http.h" #include "zm_remote_camera_nvsocket.h" +#include "zm_remote_camera_rtsp.h" #include "zm_signal.h" #include "zm_time.h" #include "zm_utils.h" #include "zm_zone.h" -#if ZM_HAS_V4L +#if ZM_HAS_V4L2 #include "zm_local_camera.h" -#endif // ZM_HAS_V4L - -#if HAVE_LIBAVFORMAT -#include "zm_remote_camera_rtsp.h" -#endif // HAVE_LIBAVFORMAT - -#if HAVE_LIBAVFORMAT -#include "zm_ffmpeg_camera.h" -#endif // HAVE_LIBAVFORMAT +#endif // ZM_HAS_V4L2 #if HAVE_LIBVLC #include "zm_libvlc_camera.h" @@ -124,7 +118,7 @@ Monitor::MonitorLink::MonitorLink(unsigned int p_id, const char *p_name) : #if ZM_MEM_MAPPED map_fd = -1; - snprintf(mem_file, sizeof(mem_file), "%s/zm.mmap.%u", staticConfig.PATH_MAP.c_str(), id); + mem_file = stringtf("%s/zm.mmap.%u", staticConfig.PATH_MAP.c_str(), id); #else // ZM_MEM_MAPPED shm_id = 0; #endif // ZM_MEM_MAPPED @@ -143,20 +137,21 @@ Monitor::MonitorLink::~MonitorLink() { } bool Monitor::MonitorLink::connect() { - if ( !last_connect_time || (time(nullptr) - last_connect_time) > 60 ) { - last_connect_time = time(nullptr); + 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", mem_size); #if ZM_MEM_MAPPED - map_fd = open(mem_file, O_RDWR, (mode_t)0600); - if ( map_fd < 0 ) { - Debug(3, "Can't open linked memory map file %s: %s", mem_file, strerror(errno)); + 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 ) { + 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); @@ -164,31 +159,31 @@ bool Monitor::MonitorLink::connect() { } struct stat map_stat; - if ( fstat(map_fd, &map_stat) < 0 ) { - Error("Can't stat linked memory map file %s: %s", mem_file, strerror(errno)); + 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, strerror(errno)); + 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 ) { + } else if (map_stat.st_size < mem_size) { Error("Got unexpected memory map file size %ld, expected %jd", map_stat.st_size, 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, mem_size, strerror(errno)); + if (mem_ptr == MAP_FAILED) { + Error("Can't map file %s (%jd bytes) to memory: %s", mem_file.c_str(), mem_size, strerror(errno)); disconnect(); return false; } #else // ZM_MEM_MAPPED shm_id = shmget((config.shm_key&0xffff0000)|id, mem_size, 0700); - if ( shm_id < 0 ) { + if (shm_id < 0) { Debug(3, "Can't shmget link memory: %s", strerror(errno)); connected = false; return false; @@ -204,7 +199,7 @@ bool Monitor::MonitorLink::connect() { shared_data = (SharedData *)mem_ptr; trigger_data = (TriggerData *)((char *)shared_data + sizeof(SharedData)); - if ( !shared_data->valid ) { + if (!shared_data->valid) { Debug(3, "Linked memory not initialised by capture daemon"); disconnect(); return false; @@ -220,23 +215,23 @@ bool Monitor::MonitorLink::connect() { } // end bool Monitor::MonitorLink::connect() bool Monitor::MonitorLink::disconnect() { - if ( connected ) { + 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 (mem_ptr > (void *)0) { + msync(mem_ptr, mem_size, MS_ASYNC); + munmap(mem_ptr, mem_size); } - if ( map_fd >= 0 ) - close( map_fd ); + 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 ); + if (shmctl(shm_id, IPC_STAT, &shm_data) < 0) { + Debug(3, "Can't shmctl: %s", strerror(errno)); + return false; } shm_id = 0; @@ -252,7 +247,6 @@ bool Monitor::MonitorLink::disconnect() { Debug(3, "Can't shmdt: %s", strerror(errno)); return false; } - #endif // ZM_MEM_MAPPED mem_size = 0; mem_ptr = nullptr; @@ -289,8 +283,8 @@ Monitor::Monitor() storage_id(0), type(LOCAL), function(NONE), - enabled(0), - decoding_enabled(0), + enabled(false), + decoding_enabled(false), //protocol //method //options @@ -313,8 +307,8 @@ Monitor::Monitor() deinterlacing_value(0), decoder_hwaccel_name(""), decoder_hwaccel_device(""), - videoRecording(0), - rtsp_describe(0), + videoRecording(false), + rtsp_describe(false), savejpegs(0), colours(0), @@ -324,10 +318,10 @@ Monitor::Monitor() encoder(""), output_container(""), imagePixFormat(AV_PIX_FMT_NONE), - record_audio(0), + record_audio(false), //event_prefix //label_format - label_coord(Coord(0,0)), + label_coord(Vector2(0,0)), label_size(0), image_buffer_count(0), max_image_buffer_count(0), @@ -349,11 +343,11 @@ Monitor::Monitor() fps_report_interval(0), ref_blend_perc(0), alarm_ref_blend_perc(0), - track_motion(0), + track_motion(false), signal_check_points(0), signal_check_colour(0), - embed_exif(0), - rtsp_server(0), + embed_exif(false), + rtsp_server(false), rtsp_streamname(""), importance(0), capture_max_fps(0), @@ -372,10 +366,6 @@ Monitor::Monitor() last_section_mod(0), buffer_count(0), state(IDLE), - start_time(0), - last_fps_time(0), - last_analysis_fps_time(0), - auto_resume_time(0), last_motion_score(0), event_close_mode(CLOSE_IDLE), #if ZM_MEM_MAPPED @@ -423,7 +413,7 @@ Monitor::Monitor() else event_close_mode = CLOSE_IDLE; - event = 0; + event = nullptr; last_section_mod = 0; adaptive_skip = true; @@ -457,7 +447,7 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { server_id = dbrow[col] ? atoi(dbrow[col]) : 0; col++; storage_id = atoi(dbrow[col]); col++; - if (storage) delete storage; + delete storage; storage = new Storage(storage_id); if ( ! strcmp(dbrow[col], "Local") ) { @@ -482,16 +472,22 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { Debug(1, "Have camera type %s", CameraType_Strings[type].c_str()); col++; function = (Function)atoi(dbrow[col]); col++; - enabled = dbrow[col] ? atoi(dbrow[col]) : 0; col++; - decoding_enabled = dbrow[col] ? atoi(dbrow[col]) : 0; col++; + enabled = dbrow[col] ? atoi(dbrow[col]) : false; col++; + decoding_enabled = dbrow[col] ? atoi(dbrow[col]) : false; col++; ReloadLinkedMonitors(dbrow[col]); col++; /* "AnalysisFPSLimit, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS," */ analysis_fps_limit = dbrow[col] ? strtod(dbrow[col], nullptr) : 0.0; col++; - analysis_update_delay = strtoul(dbrow[col++], nullptr, 0); - capture_delay = (dbrow[col] && atof(dbrow[col])>0.0)?int(DT_PREC_6/atof(dbrow[col])):0; col++; - alarm_capture_delay = (dbrow[col] && atof(dbrow[col])>0.0)?int(DT_PREC_6/atof(dbrow[col])):0; col++; + analysis_update_delay = Seconds(strtoul(dbrow[col++], nullptr, 0)); + capture_delay = + (dbrow[col] && atof(dbrow[col]) > 0.0) ? std::chrono::duration_cast(FPSeconds(1 / atof(dbrow[col]))) + : Microseconds(0); + col++; + alarm_capture_delay = + (dbrow[col] && atof(dbrow[col]) > 0.0) ? std::chrono::duration_cast(FPSeconds(1 / atof(dbrow[col]))) + : Microseconds(0); + col++; /* "Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, " // V4L Settings */ device = dbrow[col] ? dbrow[col] : ""; col++; @@ -561,7 +557,7 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { /* "EventPrefix, LabelFormat, LabelX, LabelY, LabelSize," */ event_prefix = dbrow[col] ? dbrow[col] : ""; col++; label_format = dbrow[col] ? ReplaceAll(dbrow[col], "\\n", "\n") : ""; col++; - label_coord = Coord(atoi(dbrow[col]), atoi(dbrow[col+1])); col += 2; + label_coord = Vector2(atoi(dbrow[col]), atoi(dbrow[col + 1])); col += 2; label_size = atoi(dbrow[col]); col++; /* "ImageBufferCount, `MaxImageBufferCount`, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, " */ @@ -579,8 +575,8 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { else if (alarm_frame_count > MAX_PRE_ALARM_FRAMES) alarm_frame_count = MAX_PRE_ALARM_FRAMES; /* "SectionLength, MinSectionLength, FrameSkip, MotionFrameSkip, " */ - section_length = atoi(dbrow[col]); col++; - min_section_length = atoi(dbrow[col]); col++; + section_length = Seconds(atoi(dbrow[col])); col++; + min_section_length = Seconds(atoi(dbrow[col])); col++; frame_skip = atoi(dbrow[col]); col++; motion_frame_skip = atoi(dbrow[col]); col++; @@ -688,10 +684,10 @@ void Monitor::LoadCamera() { switch (type) { case LOCAL: { -#if ZM_HAS_V4L +#if ZM_HAS_V4L2 int extras = (deinterlacing >> 24) & 0xff; - camera = ZM::make_unique(this, + camera = zm::make_unique(this, device, channel, format, @@ -717,7 +713,7 @@ void Monitor::LoadCamera() { } case REMOTE: { if (protocol == "http") { - camera = ZM::make_unique(this, + camera = zm::make_unique(this, method, host, port, @@ -733,9 +729,8 @@ void Monitor::LoadCamera() { record_audio ); } -#if HAVE_LIBAVFORMAT else if (protocol == "rtsp") { - camera = ZM::make_unique(this, + camera = zm::make_unique(this, method, host, // Host port, // Port @@ -752,14 +747,13 @@ void Monitor::LoadCamera() { record_audio ); } -#endif // HAVE_LIBAVFORMAT else { Error("Unexpected remote camera protocol '%s'", protocol.c_str()); } break; } case FILE: { - camera = ZM::make_unique(this, + camera = zm::make_unique(this, path.c_str(), camera_width, camera_height, @@ -773,9 +767,8 @@ void Monitor::LoadCamera() { ); break; } -#if HAVE_LIBAVFORMAT case FFMPEG: { - camera = ZM::make_unique(this, + camera = zm::make_unique(this, path, second_path, method, @@ -794,9 +787,8 @@ void Monitor::LoadCamera() { ); break; } -#endif // HAVE_LIBAVFORMAT case NVSOCKET: { - camera = ZM::make_unique(this, + camera = zm::make_unique(this, host.c_str(), port.c_str(), path.c_str(), @@ -814,7 +806,7 @@ void Monitor::LoadCamera() { } case LIBVLC: { #if HAVE_LIBVLC - camera = ZM::make_unique(this, + camera = zm::make_unique(this, path.c_str(), method, options, @@ -835,7 +827,7 @@ void Monitor::LoadCamera() { } case CURL: { #if HAVE_LIBCURL - camera = ZM::make_unique(this, + camera = zm::make_unique(this, path.c_str(), user.c_str(), pass.c_str(), @@ -856,7 +848,7 @@ void Monitor::LoadCamera() { } case VNC: { #if HAVE_LIBVNC - camera = ZM::make_unique(this, + camera = zm::make_unique(this, host.c_str(), port.c_str(), user.c_str(), @@ -887,7 +879,7 @@ std::shared_ptr Monitor::Load(unsigned int p_id, bool load_zones, Purpo std::string sql = load_monitor_sql + stringtf(" WHERE Id=%d", p_id); zmDbRow dbrow; - if ( !dbrow.fetch(sql.c_str()) ) { + if (!dbrow.fetch(sql)) { Error("Can't use query result: %s", mysql_error(&dbconn)); return nullptr; } @@ -899,29 +891,29 @@ std::shared_ptr Monitor::Load(unsigned int p_id, bool load_zones, Purpo } bool Monitor::connect() { - if (mem_ptr != nullptr) { Warning("Already connected. Please call disconnect first."); } Debug(3, "Connecting to monitor. Purpose is %d", purpose); #if ZM_MEM_MAPPED - snprintf(mem_file, sizeof(mem_file), "%s/zm.mmap.%u", staticConfig.PATH_MAP.c_str(), id); + mem_file = stringtf("%s/zm.mmap.%u", staticConfig.PATH_MAP.c_str(), id); if (purpose != CAPTURE) { - map_fd = open(mem_file, O_RDWR); + map_fd = open(mem_file.c_str(), O_RDWR); } else { - map_fd = open(mem_file, O_RDWR|O_CREAT, (mode_t)0660); + umask(0); + map_fd = open(mem_file.c_str(), O_RDWR|O_CREAT, (mode_t)0666); } if (map_fd < 0) { - Error("Can't open memory map file %s: %s", mem_file, strerror(errno)); + Error("Can't open memory map file %s: %s", mem_file.c_str(), strerror(errno)); return false; } else { - Debug(3, "Success opening mmap file at (%s)", mem_file); + Debug(3, "Success opening mmap file at (%s)", mem_file.c_str()); } struct stat map_stat; if (fstat(map_fd, &map_stat) < 0) { - Error("Can't stat memory map file %s: %s, is the zmc process for this monitor running?", mem_file, strerror(errno)); + Error("Can't stat memory map file %s: %s, is the zmc process for this monitor running?", mem_file.c_str(), strerror(errno)); close(map_fd); map_fd = -1; return false; @@ -931,7 +923,7 @@ bool Monitor::connect() { if (purpose == CAPTURE) { // Allocate the size if (ftruncate(map_fd, mem_size) < 0) { - Error("Can't extend memory map file %s to %jd bytes: %s", mem_file, mem_size, strerror(errno)); + Error("Can't extend memory map file %s to %jd bytes: %s", mem_file.c_str(), mem_size, strerror(errno)); close(map_fd); map_fd = -1; return false; @@ -954,18 +946,18 @@ bool Monitor::connect() { mem_ptr = (unsigned char *)mmap(nullptr, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_LOCKED, map_fd, 0); if (mem_ptr == MAP_FAILED) { if (errno == EAGAIN) { - Debug(1, "Unable to map file %s (%jd bytes) to locked memory, trying unlocked", mem_file, mem_size); + Debug(1, "Unable to map file %s (%jd bytes) to locked memory, trying unlocked", mem_file.c_str(), mem_size); #endif mem_ptr = (unsigned char *)mmap(nullptr, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, map_fd, 0); - Debug(1, "Mapped file %s (%jd bytes) to unlocked memory", mem_file, mem_size); + Debug(1, "Mapped file %s (%jd bytes) to unlocked memory", mem_file.c_str(), mem_size); #ifdef MAP_LOCKED } else { - Error("Unable to map file %s (%jd bytes) to locked memory (%s)", mem_file, mem_size, strerror(errno)); + Error("Unable to map file %s (%jd bytes) to locked memory (%s)", mem_file.c_str(), mem_size, strerror(errno)); } } #endif if ((mem_ptr == MAP_FAILED) or (mem_ptr == nullptr)) { - Error("Can't map file %s (%jd bytes) to memory: %s(%d)", mem_file, mem_size, strerror(errno), errno); + Error("Can't map file %s (%jd bytes) to memory: %s(%d)", mem_file.c_str(), mem_size, strerror(errno), errno); close(map_fd); map_fd = -1; mem_ptr = nullptr; @@ -995,14 +987,12 @@ bool Monitor::connect() { } if (!camera) LoadCamera(); - Debug(3, "Allocating %d image buffers", image_buffer_count); - image_buffer = new ZMPacket[image_buffer_count]; + image_buffer.resize(image_buffer_count); for (int32_t i = 0; i < image_buffer_count; i++) { - image_buffer[i].image_index = i; - image_buffer[i].timestamp = &(shared_timestamps[i]); - image_buffer[i].image = new Image(width, height, camera->Colours(), camera->SubpixelOrder(), &(shared_images[i*camera->ImageSize()])); - image_buffer[i].image->HoldBuffer(true); /* Don't release the internal buffer or replace it with another */ + image_buffer[i] = new Image(width, height, camera->Colours(), camera->SubpixelOrder(), &(shared_images[i*camera->ImageSize()])); + image_buffer[i]->HoldBuffer(true); /* Don't release the internal buffer or replace it with another */ } + Debug(3, "Allocated %zu %zu image buffers", image_buffer.capacity(), image_buffer.size()); if (purpose == CAPTURE) { memset(mem_ptr, 0, mem_size); @@ -1011,7 +1001,7 @@ bool Monitor::connect() { shared_data->signal = false; shared_data->capture_fps = 0.0; shared_data->analysis_fps = 0.0; - shared_data->state = IDLE; + shared_data->state = state = IDLE; shared_data->last_write_index = image_buffer_count; shared_data->last_read_index = image_buffer_count; shared_data->last_write_time = 0; @@ -1041,18 +1031,16 @@ bool Monitor::connect() { // Uh, why nothing? Why not nullptr? snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "nothing"); video_store_data->size = sizeof(VideoStoreData); + usedsubpixorder = camera->SubpixelOrder(); // Used in CheckSignal shared_data->valid = true; - } else if ( !shared_data->valid ) { + } else if (!shared_data->valid) { Error("Shared data not initialised by capture daemon for monitor %s", name.c_str()); return false; } // We set these here because otherwise the first fps calc is meaningless - struct timeval now; - gettimeofday(&now, nullptr); - double now_double = (double)now.tv_sec + (0.000001f * now.tv_usec); - last_fps_time = now_double; - last_analysis_fps_time = now_double; + last_fps_time = std::chrono::system_clock::now(); + last_analysis_fps_time = std::chrono::system_clock::now(); Debug(3, "Success connecting"); return true; @@ -1064,6 +1052,13 @@ bool Monitor::disconnect() { return true; } + if (purpose == CAPTURE) { + if (unlink(mem_file.c_str()) < 0) { + Warning("Can't unlink '%s': %s", mem_file.c_str(), strerror(errno)); + } + Debug(1, "Setting shared_data->valid = false"); + shared_data->valid = false; + } #if ZM_MEM_MAPPED msync(mem_ptr, mem_size, MS_ASYNC); munmap(mem_ptr, mem_size); @@ -1073,9 +1068,6 @@ bool Monitor::disconnect() { mem_ptr = nullptr; shared_data = nullptr; - if (purpose == CAPTURE and (unlink(mem_file) < 0) ) { - Warning("Can't unlink '%s': %s", mem_file, strerror(errno)); - } #else // ZM_MEM_MAPPED struct shmid_ds shm_data; if (shmctl(shm_id, IPC_STAT, &shm_data) < 0) { @@ -1096,16 +1088,10 @@ bool Monitor::disconnect() { } #endif // ZM_MEM_MAPPED - if (image_buffer) { - for ( int32_t i = 0; i < image_buffer_count; i++ ) { - // We delete the image because it is an object pointing to space that won't be free'd. - delete image_buffer[i].image; - image_buffer[i].image = nullptr; - // We don't delete the timestamp because it is just a pointer to shared mem. - image_buffer[i].timestamp = nullptr; - } - delete[] image_buffer; - image_buffer = nullptr; + for (int32_t i = 0; i < image_buffer_count; i++) { + // We delete the image because it is an object pointing to space that won't be free'd. + delete image_buffer[i]; + image_buffer[i] = nullptr; } return true; @@ -1116,10 +1102,6 @@ Monitor::~Monitor() { if (mem_ptr != nullptr) { if (purpose != QUERY) { - shared_data->state = state = IDLE; - shared_data->last_read_index = image_buffer_count; - shared_data->last_read_time = 0; - shared_data->valid = false; memset(mem_ptr, 0, mem_size); } // end if purpose != query disconnect(); @@ -1170,54 +1152,64 @@ void Monitor::AddPrivacyBitmask() { } int Monitor::GetImage(int32_t index, int scale) { - if ( index < 0 || index > image_buffer_count ) { + if (index < 0 || index > image_buffer_count) { + Warning("Invalid index %d passed. image_buffer_count = %d", index, image_buffer_count); index = shared_data->last_write_index; } - if ( index != image_buffer_count ) { - Image *image; - // If we are going to be modifying the snapshot before writing, then we need to copy it - if ( ( scale != ZM_SCALE_BASE ) || ( !config.timestamp_on_capture ) ) { - ZMPacket *snap = &image_buffer[index]; - alarm_image.Assign(*snap->image); + if (!image_buffer.size() or static_cast(index) >= image_buffer.size()) { + Error("Image Buffer has not been allocated"); + return -1; + } + if ( index == image_buffer_count ) { + Error("Unable to generate image, no images in buffer"); + return 0; + } - if ( scale != ZM_SCALE_BASE ) { - alarm_image.Scale(scale); - } + Image *image; + // If we are going to be modifying the snapshot before writing, then we need to copy it + if ((scale != ZM_SCALE_BASE) || (!config.timestamp_on_capture)) { + alarm_image.Assign(*image_buffer[index]); - if ( !config.timestamp_on_capture ) { - TimestampImage(&alarm_image, snap->timestamp); - } - image = &alarm_image; - } else { - image = image_buffer[index].image; + if (scale != ZM_SCALE_BASE) { + alarm_image.Scale(scale); } - static char filename[PATH_MAX]; - snprintf(filename, sizeof(filename), "Monitor%u.jpg", id); - image->WriteJpeg(filename); + if (!config.timestamp_on_capture) { + TimestampImage(&alarm_image, SystemTimePoint(zm::chrono::duration_cast(shared_timestamps[index]))); + } + image = &alarm_image; } else { - Error("Unable to generate image, no images in buffer"); + image = image_buffer[index]; } - return 0; + + std::string filename = stringtf("Monitor%u.jpg", id); + image->WriteJpeg(filename); + return 1; } ZMPacket *Monitor::getSnapshot(int index) const { - - if ( (index < 0) || (index > image_buffer_count) ) { + if ((index < 0) || (index >= image_buffer_count)) { index = shared_data->last_write_index; } - return &image_buffer[index]; - + if (!image_buffer.size() or static_cast(index) >= image_buffer.size()) { + Error("Image Buffer has not been allocated"); + return nullptr; + } + if (index != image_buffer_count) { + return new ZMPacket(image_buffer[index], + SystemTimePoint(zm::chrono::duration_cast(shared_timestamps[index]))); + } else { + Error("Unable to generate image, no images in buffer"); + } return nullptr; } -struct timeval Monitor::GetTimestamp(int index) const { +SystemTimePoint Monitor::GetTimestamp(int index) const { ZMPacket *packet = getSnapshot(index); - if ( packet ) - return *packet->timestamp; + if (packet) + return packet->timestamp; - static struct timeval null_tv = { 0, 0 }; - return null_tv; + return {}; } unsigned int Monitor::GetLastReadIndex() const { @@ -1235,58 +1227,6 @@ uint64_t Monitor::GetLastEventId() const { // This function is crap. double Monitor::GetFPS() const { return get_capture_fps(); - // last_write_index is the last capture index. It starts as == image_buffer_count so that the first asignment % image_buffer_count = 0; - int32_t index1 = shared_data->last_write_index; - if ( index1 >= image_buffer_count ) { - // last_write_index only has this value on startup before capturing anything. - return 0.0; - } - Debug(2, "index1(%d)", index1); - ZMPacket *snap1 = &image_buffer[index1]; - if ( !snap1->timestamp->tv_sec ) { - // This should be impossible - Warning("Impossible situation. No timestamp on captured image index was %d, image-buffer_count was (%d)", index1, image_buffer_count); - return 0.0; - } - struct timeval time1 = *snap1->timestamp; - - int32_t fps_image_count = image_buffer_count; - - int32_t index2 = (index1+1)%image_buffer_count; - Debug(2, "index2(%d)", index2); - ZMPacket *snap2 = &image_buffer[index2]; - // the timestamp pointers are initialized on connection, so that's redundant - // tv_sec is probably only zero during the first loop of capturing, so this basically just counts the unused images. - // The problem is that there is no locking, and we set the timestamp before we set last_write_index, - // so there is a small window where the next image can have a timestamp in the future - while ( !snap2->timestamp->tv_sec || tvDiffSec(*snap2->timestamp, *snap1->timestamp) < 0 ) { - if ( index1 == index2 ) { - // All images are uncaptured - return 0.0; - } - index2 = (index2+1)%image_buffer_count; - snap2 = &image_buffer[ index2 ]; - fps_image_count--; - } - struct timeval time2 = *snap2->timestamp; - - double time_diff = tvDiffSec( time2, time1 ); - if ( ! time_diff ) { - Error("No diff between time_diff = %lf (%d:%ld.%ld - %d:%ld.%ld), ibc: %d", - time_diff, index2, time2.tv_sec, time2.tv_usec, index1, time1.tv_sec, time1.tv_usec, image_buffer_count); - return 0.0; - } - double curr_fps = fps_image_count/time_diff; - - if ( curr_fps < 0.0 ) { - Error("Negative FPS %f, time_diff = %lf (%d:%ld.%ld - %d:%ld.%ld), ibc: %d", - curr_fps, time_diff, index2, time2.tv_sec, time2.tv_usec, index1, time1.tv_sec, time1.tv_usec, image_buffer_count); - return 0.0; - } else { - Debug(2, "GetFPS %f, time_diff = %lf (%d:%ld.%ld - %d:%ld.%ld), ibc: %d", - curr_fps, time_diff, index2, time2.tv_sec, time2.tv_usec, index1, time1.tv_sec, time1.tv_usec, image_buffer_count); - } - return curr_fps; } /* I think this returns the # of micro seconds that we should sleep in order to maintain the desired analysis rate */ @@ -1344,16 +1284,14 @@ void Monitor::actionReload() { void Monitor::actionEnable() { shared_data->action |= RELOAD; - char sql[ZM_SQL_SML_BUFSIZ]; - snprintf(sql, sizeof(sql), "UPDATE `Monitors` SET `Enabled` = 1 WHERE `Id` = %u", id); + std::string sql = stringtf("UPDATE `Monitors` SET `Enabled` = 1 WHERE `Id` = %u", id); zmDbDo(sql); } void Monitor::actionDisable() { shared_data->action |= RELOAD; - char sql[ZM_SQL_SML_BUFSIZ]; - snprintf(sql, sizeof(sql), "UPDATE `Monitors` SET `Enabled` = 0 WHERE `Id` = %u", id); + std::string sql = stringtf("UPDATE `Monitors` SET `Enabled` = 0 WHERE `Id` = %u", id); zmDbDo(sql); } @@ -1366,14 +1304,14 @@ void Monitor::actionResume() { } int Monitor::actionBrightness(int p_brightness) { - if ( purpose != CAPTURE ) { - if ( p_brightness >= 0 ) { + if (purpose != CAPTURE) { + if (p_brightness >= 0) { shared_data->brightness = p_brightness; shared_data->action |= SET_SETTINGS; int wait_loops = 10; - while ( shared_data->action & SET_SETTINGS ) { - if ( wait_loops-- ) { - usleep(100000); + while (shared_data->action & SET_SETTINGS) { + if (wait_loops--) { + std::this_thread::sleep_for(Milliseconds(100)); } else { Warning("Timed out waiting to set brightness"); return -1; @@ -1382,9 +1320,9 @@ int Monitor::actionBrightness(int p_brightness) { } else { shared_data->action |= GET_SETTINGS; int wait_loops = 10; - while ( shared_data->action & GET_SETTINGS ) { - if ( wait_loops-- ) { - usleep(100000); + while (shared_data->action & GET_SETTINGS) { + if (wait_loops--) { + std::this_thread::sleep_for(Milliseconds(100)); } else { Warning("Timed out waiting to get brightness"); return -1; @@ -1397,14 +1335,14 @@ int Monitor::actionBrightness(int p_brightness) { } // end int Monitor::actionBrightness(int p_brightness) int Monitor::actionContrast(int p_contrast) { - if ( purpose != CAPTURE ) { - if ( p_contrast >= 0 ) { + if (purpose != CAPTURE) { + if (p_contrast >= 0) { shared_data->contrast = p_contrast; shared_data->action |= SET_SETTINGS; int wait_loops = 10; - while ( shared_data->action & SET_SETTINGS ) { - if ( wait_loops-- ) { - usleep(100000); + while (shared_data->action & SET_SETTINGS) { + if (wait_loops--) { + std::this_thread::sleep_for(Milliseconds(100)); } else { Warning("Timed out waiting to set contrast"); return -1; @@ -1413,9 +1351,9 @@ int Monitor::actionContrast(int p_contrast) { } else { shared_data->action |= GET_SETTINGS; int wait_loops = 10; - while ( shared_data->action & GET_SETTINGS ) { - if ( wait_loops-- ) { - usleep(100000); + while (shared_data->action & GET_SETTINGS) { + if (wait_loops--) { + std::this_thread::sleep_for(Milliseconds(100)); } else { Warning("Timed out waiting to get contrast"); return -1; @@ -1428,14 +1366,14 @@ int Monitor::actionContrast(int p_contrast) { } // end int Monitor::actionContrast(int p_contrast) int Monitor::actionHue(int p_hue) { - if ( purpose != CAPTURE ) { - if ( p_hue >= 0 ) { + if (purpose != CAPTURE) { + if (p_hue >= 0) { shared_data->hue = p_hue; shared_data->action |= SET_SETTINGS; int wait_loops = 10; - while ( shared_data->action & SET_SETTINGS ) { - if ( wait_loops-- ) { - usleep(100000); + while (shared_data->action & SET_SETTINGS) { + if (wait_loops--) { + std::this_thread::sleep_for(Milliseconds(100)); } else { Warning("Timed out waiting to set hue"); return -1; @@ -1444,9 +1382,9 @@ int Monitor::actionHue(int p_hue) { } else { shared_data->action |= GET_SETTINGS; int wait_loops = 10; - while ( shared_data->action & GET_SETTINGS ) { - if ( wait_loops-- ) { - usleep(100000); + while (shared_data->action & GET_SETTINGS) { + if (wait_loops--) { + std::this_thread::sleep_for(Milliseconds(100)); } else { Warning("Timed out waiting to get hue"); return -1; @@ -1459,14 +1397,14 @@ int Monitor::actionHue(int p_hue) { } // end int Monitor::actionHue(int p_hue) int Monitor::actionColour(int p_colour) { - if ( purpose != CAPTURE ) { - if ( p_colour >= 0 ) { + if (purpose != CAPTURE) { + if (p_colour >= 0) { shared_data->colour = p_colour; shared_data->action |= SET_SETTINGS; int wait_loops = 10; - while ( shared_data->action & SET_SETTINGS ) { - if ( wait_loops-- ) { - usleep(100000); + while (shared_data->action & SET_SETTINGS) { + if (wait_loops--) { + std::this_thread::sleep_for(Milliseconds(100)); } else { Warning("Timed out waiting to set colour"); return -1; @@ -1475,9 +1413,9 @@ int Monitor::actionColour(int p_colour) { } else { shared_data->action |= GET_SETTINGS; int wait_loops = 10; - while ( shared_data->action & GET_SETTINGS ) { - if ( wait_loops-- ) { - usleep(100000); + while (shared_data->action & GET_SETTINGS) { + if (wait_loops--) { + std::this_thread::sleep_for(Milliseconds(100)); } else { Warning("Timed out waiting to get colour"); return -1; @@ -1511,7 +1449,7 @@ void Monitor::DumpZoneImage(const char *zone_string) { // Grab the most revent event image std::string sql = stringtf("SELECT MAX(`Id`) FROM `Events` WHERE `MonitorId`=%d AND `Frames` > 0", id); zmDbRow eventid_row; - if ( eventid_row.fetch(sql.c_str()) ) { + if (eventid_row.fetch(sql)) { uint64_t event_id = atoll(eventid_row[0]); Debug(3, "Got event %" PRIu64, event_id); @@ -1530,21 +1468,27 @@ void Monitor::DumpZoneImage(const char *zone_string) { zone_image->Colourise(ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB); } + extra_zone.Clip(Box( + {0, 0}, + {static_cast(zone_image->Width()), static_cast(zone_image->Height())} + )); + for (const Zone &zone : zones) { - if ( exclude_id && (!extra_colour || extra_zone.getNumCoords()) && zone.Id() == exclude_id ) + if (exclude_id && (!extra_colour || !extra_zone.GetVertices().empty()) && zone.Id() == exclude_id) { continue; + } Rgb colour; - if ( exclude_id && !extra_zone.getNumCoords() && zone.Id() == exclude_id ) { + if (exclude_id && extra_zone.GetVertices().empty() && zone.Id() == exclude_id) { colour = extra_colour; } else { - if ( zone.IsActive() ) { + if (zone.IsActive()) { colour = kRGBRed; - } else if ( zone.IsInclusive() ) { + } else if (zone.IsInclusive()) { colour = kRGBOrange; - } else if ( zone.IsExclusive() ) { + } else if (zone.IsExclusive()) { colour = kRGBPurple; - } else if ( zone.IsPreclusive() ) { + } else if (zone.IsPreclusive()) { colour = kRGBBlue; } else { colour = kRGBWhite; @@ -1554,25 +1498,25 @@ void Monitor::DumpZoneImage(const char *zone_string) { zone_image->Outline(colour, zone.GetPolygon()); } - if ( extra_zone.getNumCoords() ) { + if (!extra_zone.GetVertices().empty()) { zone_image->Fill(extra_colour, 2, extra_zone); zone_image->Outline(extra_colour, extra_zone); } - static char filename[PATH_MAX]; - snprintf(filename, sizeof(filename), "Zones%u.jpg", id); + std::string filename = stringtf("Zones%u.jpg", id); zone_image->WriteJpeg(filename); delete zone_image; } // end void Monitor::DumpZoneImage(const char *zone_string) void Monitor::DumpImage(Image *dump_image) const { - if ( image_count && !(image_count%10) ) { - static char filename[PATH_MAX]; - static char new_filename[PATH_MAX]; - snprintf(filename, sizeof(filename), "Monitor%u.jpg", id); - snprintf(new_filename, sizeof(new_filename), "Monitor%u-new.jpg", id); - if ( dump_image->WriteJpeg(new_filename) ) - rename(new_filename, filename); + if (image_count && !(image_count % 10)) { + + std::string filename = stringtf("Monitor%u.jpg", id); + std::string new_filename = stringtf("Monitor%u-new.jpg", id); + + if (dump_image->WriteJpeg(new_filename)) { + rename(new_filename.c_str(), filename.c_str()); + } } } // end void Monitor::DumpImage(Image *dump_image) @@ -1586,40 +1530,45 @@ bool Monitor::CheckSignal(const Image *image) { int colours = image->Colours(); int index = 0; - for ( int i = 0; i < signal_check_points; i++ ) { - while ( true ) { + for (int i = 0; i < signal_check_points; i++) { + while (true) { // Why the casting to long long? also note that on a 64bit cpu, long long is 128bits index = (int)(((long long)rand()*(long long)(pixels-1))/RAND_MAX); - if ( !config.timestamp_on_capture || !label_format[0] ) + if (!config.timestamp_on_capture || !label_format[0]) break; // Avoid sampling the rows with timestamp in - if ( index < (label_coord.Y()*width) || index >= (label_coord.Y()+Image::LINE_HEIGHT)*width ) + if ( + index < (label_coord.y_ * width) + || + index >= (label_coord.y_ + Image::LINE_HEIGHT) * width + ) { break; + } } - if ( colours == ZM_COLOUR_GRAY8 ) { - if ( *(buffer+index) != grayscale_val ) + if (colours == ZM_COLOUR_GRAY8) { + if (*(buffer+index) != grayscale_val) return true; - } else if ( colours == ZM_COLOUR_RGB24 ) { + } else if (colours == ZM_COLOUR_RGB24) { const uint8_t *ptr = buffer+(index*colours); - if ( usedsubpixorder == ZM_SUBPIX_ORDER_BGR ) { - if ( (RED_PTR_BGRA(ptr) != red_val) || (GREEN_PTR_BGRA(ptr) != green_val) || (BLUE_PTR_BGRA(ptr) != blue_val) ) + if (usedsubpixorder == ZM_SUBPIX_ORDER_BGR) { + if ((RED_PTR_BGRA(ptr) != red_val) || (GREEN_PTR_BGRA(ptr) != green_val) || (BLUE_PTR_BGRA(ptr) != blue_val)) return true; } else { /* Assume RGB */ - if ( (RED_PTR_RGBA(ptr) != red_val) || (GREEN_PTR_RGBA(ptr) != green_val) || (BLUE_PTR_RGBA(ptr) != blue_val) ) + if ((RED_PTR_RGBA(ptr) != red_val) || (GREEN_PTR_RGBA(ptr) != green_val) || (BLUE_PTR_RGBA(ptr) != blue_val)) return true; } - } else if ( colours == ZM_COLOUR_RGB32 ) { - if ( usedsubpixorder == ZM_SUBPIX_ORDER_ARGB || usedsubpixorder == ZM_SUBPIX_ORDER_ABGR ) { - if ( ARGB_ABGR_ZEROALPHA(*(((const Rgb*)buffer)+index)) != ARGB_ABGR_ZEROALPHA(colour_val) ) + } else if (colours == ZM_COLOUR_RGB32) { + if (usedsubpixorder == ZM_SUBPIX_ORDER_ARGB || usedsubpixorder == ZM_SUBPIX_ORDER_ABGR) { + if (ARGB_ABGR_ZEROALPHA(*(((const Rgb*)buffer)+index)) != ARGB_ABGR_ZEROALPHA(colour_val)) return true; } else { /* Assume RGBA or BGRA */ - if ( RGBA_BGRA_ZEROALPHA(*(((const Rgb*)buffer)+index)) != RGBA_BGRA_ZEROALPHA(colour_val) ) + if (RGBA_BGRA_ZEROALPHA(*(((const Rgb*)buffer)+index)) != RGBA_BGRA_ZEROALPHA(colour_val)) return true; } } @@ -1629,8 +1578,7 @@ bool Monitor::CheckSignal(const Image *image) { } // end bool Monitor::CheckSignal(const Image *image) void Monitor::CheckAction() { - struct timeval now; - gettimeofday(&now, nullptr); + SystemTimePoint now = std::chrono::system_clock::now(); if ( shared_data->action ) { // Can there be more than 1 bit set in the action? Shouldn't these be elseifs? @@ -1647,8 +1595,8 @@ void Monitor::CheckAction() { } else { Info("Received suspend indication at count %d, but wasn't active", image_count); } - if ( config.max_suspend_time ) { - auto_resume_time = now.tv_sec + config.max_suspend_time; + if (config.max_suspend_time) { + auto_resume_time = now + Seconds(config.max_suspend_time); } shared_data->action &= ~SUSPEND; } else if ( shared_data->action & RESUME ) { @@ -1662,45 +1610,49 @@ void Monitor::CheckAction() { } } // end if shared_data->action - if ( auto_resume_time && (now.tv_sec >= auto_resume_time) ) { + if (auto_resume_time.time_since_epoch() != Seconds(0) && now >= auto_resume_time) { Info("Auto resuming at count %d", image_count); shared_data->active = true; ref_image.DumpImgBuffer(); // Will get re-assigned by analysis thread - auto_resume_time = 0; } } void Monitor::UpdateCaptureFPS() { - if ( fps_report_interval and - ( + if ( fps_report_interval and + ( !(image_count%fps_report_interval) - or + or ( (image_count < fps_report_interval) and !(image_count%10) ) ) ) { - struct timeval now; - gettimeofday(&now, nullptr); - double now_double = (double)now.tv_sec + (0.000001f * now.tv_usec); - double elapsed = now_double - last_fps_time; + SystemTimePoint now = std::chrono::system_clock::now(); + FPSeconds elapsed = now - last_fps_time; // If we are too fast, we get div by zero. This seems to happen in the case of audio packets. // Also only do the update at most 1/sec - if ( elapsed > 1.0 ) { + if (elapsed > Seconds(1)) { // # of images per interval / the amount of time it took - double new_capture_fps = double((image_count - last_capture_image_count)/elapsed); - unsigned int new_camera_bytes = camera->Bytes(); - unsigned int new_capture_bandwidth = (new_camera_bytes-last_camera_bytes)/elapsed; + double new_capture_fps = (image_count - last_capture_image_count) / elapsed.count(); + uint32 new_camera_bytes = camera->Bytes(); + uint32 new_capture_bandwidth = + static_cast((new_camera_bytes - last_camera_bytes) / elapsed.count()); last_camera_bytes = new_camera_bytes; - Debug(4, "%s: %d - last %d = %d now:%lf, last %lf, elapsed %lf = %lffps", "Capturing", image_count, - last_capture_image_count, image_count - last_capture_image_count, - now_double, last_analysis_fps_time, - elapsed, new_capture_fps - ); + Debug(4, "%s: %d - last %d = %d now:%lf, last %lf, elapsed %lf = %lffps", + "Capturing", + image_count, + last_capture_image_count, + image_count - last_capture_image_count, + FPSeconds(now.time_since_epoch()).count(), + FPSeconds(last_analysis_fps_time.time_since_epoch()).count(), + elapsed.count(), + new_capture_fps); + Info("%s: %d - Capturing at %.2lf fps, capturing bandwidth %ubytes/sec", name.c_str(), image_count, new_capture_fps, new_capture_bandwidth); + shared_data->capture_fps = new_capture_fps; - last_fps_time = now_double; + last_fps_time = now; last_capture_image_count = image_count; std::string sql = stringtf( @@ -1722,35 +1674,36 @@ void Monitor::UpdateAnalysisFPS() { // In startup do faster updates ( (analysis_image_count < fps_report_interval) and !(analysis_image_count%10) ) ) { - //if ( analysis_image_count && fps_report_interval && !(analysis_image_count%fps_report_interval) ) { - struct timeval now; - gettimeofday(&now, nullptr); - double now_double = (double)now.tv_sec + (0.000001f * now.tv_usec); - double elapsed = now_double - last_analysis_fps_time; - Debug(4, "%s: %d - now:%" PRIi64 ".%" PRIi64 " = %lf, last %lf, diff %lf", + SystemTimePoint now = std::chrono::system_clock::now(); + + FPSeconds elapsed = now - last_analysis_fps_time; + Debug(4, "%s: %d - now: %.2f, last %lf, diff %lf", name.c_str(), analysis_image_count, - static_cast(now.tv_sec), - static_cast(now.tv_usec), - now_double, - last_analysis_fps_time, - elapsed); + FPSeconds(now.time_since_epoch()).count(), + FPSeconds(last_analysis_fps_time.time_since_epoch()).count(), + elapsed.count()); - if ( elapsed > 1.0 ) { - double new_analysis_fps = double(motion_frame_count - last_motion_frame_count) / elapsed; + if (elapsed > Seconds(1)) { + double new_analysis_fps = (motion_frame_count - last_motion_frame_count) / elapsed.count(); Info("%s: %d - Analysing at %.2lf fps from %d - %d=%d / %lf - %lf = %lf", - name.c_str(), analysis_image_count, new_analysis_fps, - motion_frame_count, last_motion_frame_count, (motion_frame_count - last_motion_frame_count), - now_double, last_analysis_fps_time, elapsed); + name.c_str(), + analysis_image_count, + new_analysis_fps, + motion_frame_count, + last_motion_frame_count, + (motion_frame_count - last_motion_frame_count), + FPSeconds(now.time_since_epoch()).count(), + FPSeconds(last_analysis_fps_time.time_since_epoch()).count(), + elapsed.count()); - if ( new_analysis_fps != shared_data->analysis_fps ) { + if (new_analysis_fps != shared_data->analysis_fps) { shared_data->analysis_fps = new_analysis_fps; - std::string sql = stringtf( - "UPDATE LOW_PRIORITY Monitor_Status SET AnalysisFPS = %.2lf WHERE MonitorId=%u", - new_analysis_fps, id); + std::string sql = stringtf("UPDATE LOW_PRIORITY Monitor_Status SET AnalysisFPS = %.2lf WHERE MonitorId=%u", + new_analysis_fps, id); dbQueue.push(std::move(sql)); - last_analysis_fps_time = now_double; + last_analysis_fps_time = now; last_motion_frame_count = motion_frame_count; } else { Debug(4, "No change in fps"); @@ -1772,16 +1725,17 @@ bool Monitor::Analyse() { // get_analysis_packet will lock the packet and may wait if analysis_it is at the end ZMLockedPacket *packet_lock = packetqueue.get_packet(analysis_it); if (!packet_lock) return false; - ZMPacket *snap = packet_lock->packet_; + std::shared_ptr snap = packet_lock->packet_; // Is it possible for snap->score to be ! -1 ? Not if everything is working correctly if (snap->score != -1) { - delete packet_lock; - packetqueue.increment_it(analysis_it); Error("skipping because score was %d", snap->score); + packetqueue.unlock(packet_lock); + packetqueue.increment_it(analysis_it); return false; } + // Store the it that points to our snap we will need it later packetqueue_iterator snap_it = *analysis_it; packetqueue.increment_it(analysis_it); @@ -1820,7 +1774,7 @@ bool Monitor::Analyse() { Event::StringSet noteSet; noteSet.insert(trigger_data->trigger_text); noteSetMap[trigger_data->trigger_cause] = noteSet; - } // end if trigger_on + } // end if trigger_on // FIXME this snap might not be the one that caused the signal change. Need to store that in the packet. if (signal_change) { @@ -1848,7 +1802,7 @@ bool Monitor::Analyse() { shared_data->active = signal; if ((function == MODECT or function == MOCORD) and snap->image) ref_image.Assign(*(snap->image)); - }// else + } // end if signal change if (signal) { if (snap->codec_type == AVMEDIA_TYPE_VIDEO) { @@ -1858,17 +1812,17 @@ bool Monitor::Analyse() { // FIXME improve logic here bool first_link = true; Event::StringSet noteSet; - for ( int i = 0; i < n_linked_monitors; i++ ) { + for (int i = 0; i < n_linked_monitors; i++) { // TODO: Shouldn't we try to connect? - if ( linked_monitors[i]->isConnected() ) { + if (linked_monitors[i]->isConnected()) { Debug(1, "Linked monitor %d %s is connected", linked_monitors[i]->Id(), linked_monitors[i]->Name()); - if ( linked_monitors[i]->hasAlarmed() ) { + if (linked_monitors[i]->hasAlarmed()) { Debug(1, "Linked monitor %d %s is alarmed", linked_monitors[i]->Id(), linked_monitors[i]->Name()); - if ( !event ) { - if ( first_link ) { - if ( cause.length() ) + if (!event) { + if (first_link) { + if (cause.length()) cause += ", "; cause += LINKED_CAUSE; first_link = false; @@ -1885,15 +1839,13 @@ bool Monitor::Analyse() { linked_monitors[i]->connect(); } } // end foreach linked_monitor - if ( noteSet.size() > 0 ) + if (noteSet.size() > 0) noteSetMap[LINKED_CAUSE] = noteSet; } // end if linked_monitors - struct timeval *timestamp = snap->timestamp; - /* try to stay behind the decoder. */ if (decoding_enabled) { - while (!snap->image and !snap->decoded and !zm_terminate and !analysis_thread->Stopped()) { + while (!snap->decoded and !zm_terminate and !analysis_thread->Stopped()) { // Need to wait for the decoder thread. Debug(1, "Waiting for decode"); packet_lock->wait(); @@ -1909,6 +1861,8 @@ bool Monitor::Analyse() { } } // end if decoding enabled + SystemTimePoint timestamp = snap->timestamp; + if (Active() and (function == MODECT or function == MOCORD)) { Debug(3, "signal and active and modect"); Event::StringSet zoneSet; @@ -1923,7 +1877,6 @@ bool Monitor::Analyse() { } if (!(analysis_image_count % (motion_frame_skip+1))) { - if (snap->image) { // decoder may not have been able to provide an image if (!ref_image.Buffer()) { @@ -1969,25 +1922,20 @@ bool Monitor::Analyse() { if (function == RECORD or function == MOCORD) { // If doing record, check to see if we need to close the event or not. if (event) { - Debug(2, "Have event %" PRIu64 " in mocord", event->Id()); + Debug(2, "Have event %" PRIu64 " in record", event->Id()); - if (section_length && - ( ( timestamp->tv_sec - video_store_data->recording.tv_sec ) >= section_length ) - && ( - ( (function == MOCORD) && (event_close_mode != CLOSE_TIME) ) - || - ( (function == RECORD) && (event_close_mode == CLOSE_TIME) ) - || ! ( timestamp->tv_sec % section_length ) - ) - ) { - Info("%s: %03d - Closing event %" PRIu64 ", section end forced %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %d", + if (section_length != Seconds(0) && (timestamp - GetVideoWriterStartTime() >= section_length) + && ((function == MOCORD && event_close_mode != CLOSE_TIME) + || (function == RECORD && event_close_mode == CLOSE_TIME) + || std::chrono::duration_cast(timestamp.time_since_epoch()) % section_length == Seconds(0))) { + Info("%s: %03d - Closing event %" PRIu64 ", section end forced %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64 , name.c_str(), image_count, event->Id(), - static_cast(timestamp->tv_sec), - static_cast(video_store_data->recording.tv_sec), - static_cast(timestamp->tv_sec - video_store_data->recording.tv_sec), - section_length); + static_cast(std::chrono::duration_cast(timestamp.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(GetVideoWriterStartTime().time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(timestamp - GetVideoWriterStartTime()).count()), + static_cast(Seconds(section_length).count())); closeEvent(); } // end if section_length } // end if event @@ -2004,23 +1952,27 @@ bool Monitor::Analyse() { // This gets a lock on the starting packet ZMLockedPacket *starting_packet_lock = nullptr; - ZMPacket *starting_packet = nullptr; - if ( *start_it != snap_it ) { + std::shared_ptr starting_packet = nullptr; + if (*start_it != snap_it) { starting_packet_lock = packetqueue.get_packet(start_it); - if (!starting_packet_lock) return false; + if (!starting_packet_lock) { + Warning("Unable to get starting packet lock"); + delete packet_lock; + return false; + } starting_packet = starting_packet_lock->packet_; } else { starting_packet = snap; } - event = new Event(this, *(starting_packet->timestamp), "Continuous", noteSetMap); + event = new Event(this, starting_packet->timestamp, "Continuous", noteSetMap); // Write out starting packets, do not modify packetqueue it will garbage collect itself - while ( starting_packet and ((*start_it) != snap_it) ) { + while (starting_packet and ((*start_it) != snap_it)) { event->AddPacket(starting_packet); // Have added the packet, don't want to unlock it until we have locked the next packetqueue.increment_it(start_it); - if ( (*start_it) == snap_it ) { + if ((*start_it) == snap_it) { if (starting_packet_lock) delete starting_packet_lock; break; } @@ -2035,7 +1987,7 @@ bool Monitor::Analyse() { start_it = nullptr; } else { // Create event from current snap - event = new Event(this, *timestamp, "Continuous", noteSetMap); + event = new Event(this, timestamp, "Continuous", noteSetMap); } shared_data->last_event_id = event->Id(); @@ -2049,7 +2001,8 @@ bool Monitor::Analyse() { } alarm_cause = cause+" Continuous "+alarm_cause; strncpy(shared_data->alarm_cause, alarm_cause.c_str(), sizeof(shared_data->alarm_cause)-1); - video_store_data->recording = event->StartTime(); + SetVideoWriterStartTime(event->StartTime()); + Info("%s: %03d - Opened new event %" PRIu64 ", section start", name.c_str(), analysis_image_count, event->Id()); /* To prevent cancelling out an existing alert\prealarm\alarm state */ @@ -2064,23 +2017,22 @@ bool Monitor::Analyse() { if ((state == IDLE) || (state == TAPE) || (state == PREALARM)) { // If we should end then previous continuous event and start a new non-continuous event if (event && event->Frames() - && (!event->AlarmFrames()) - && (event_close_mode == CLOSE_ALARM) - && ( ( timestamp->tv_sec - video_store_data->recording.tv_sec ) >= min_section_length ) - && ( (!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count-1) ) - ) { + && !event->AlarmFrames() + && event_close_mode == CLOSE_ALARM + && timestamp - GetVideoWriterStartTime() >= min_section_length + && (!pre_event_count || Event::PreAlarmCount() >= alarm_frame_count - 1)) { Info("%s: %03d - Closing event %" PRIu64 ", continuous end, alarm begins", name.c_str(), image_count, event->Id()); closeEvent(); } else if (event) { // This is so if we need more than 1 alarm frame before going into alarm, so it is basically if we have enough alarm frames Debug(3, - "pre_alarm_count in event %d, event frames %d, alarm frames %d event length %" PRIi64 " >=? %d min", + "pre_alarm_count in event %d, event frames %d, alarm frames %d event length %" PRIi64 " >=? %" PRIi64 " min", Event::PreAlarmCount(), event->Frames(), event->AlarmFrames(), - static_cast(timestamp->tv_sec - video_store_data->recording.tv_sec), - min_section_length); + static_cast(std::chrono::duration_cast(timestamp - GetVideoWriterStartTime()).count()), + static_cast(Seconds(min_section_length).count())); } if ((!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count-1)) { // lets construct alarm cause. It will contain cause + names of zones alarmed @@ -2102,7 +2054,7 @@ bool Monitor::Analyse() { (pre_event_count > alarm_frame_count ? pre_event_count : alarm_frame_count) ); ZMLockedPacket *starting_packet_lock = nullptr; - ZMPacket *starting_packet = nullptr; + std::shared_ptr starting_packet = nullptr; if (*start_it != snap_it) { starting_packet_lock = packetqueue.get_packet(start_it); if (!starting_packet_lock) return false; @@ -2111,10 +2063,10 @@ bool Monitor::Analyse() { starting_packet = snap; } - event = new Event(this, *(starting_packet->timestamp), cause, noteSetMap); + event = new Event(this, starting_packet->timestamp, cause, noteSetMap); shared_data->last_event_id = event->Id(); snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile()); - video_store_data->recording = event->StartTime(); + SetVideoWriterStartTime(event->StartTime()); shared_data->state = state = ALARM; // Write out starting packets, do not modify packetqueue it will garbage collect itself @@ -2177,11 +2129,8 @@ bool Monitor::Analyse() { Info("%s: %03d - Gone into alert state", name.c_str(), analysis_image_count); shared_data->state = state = ALERT; } else if (state == ALERT) { - if ( - ( analysis_image_count-last_alarm_count > post_event_count ) - && - ( ( timestamp->tv_sec - video_store_data->recording.tv_sec ) >= min_section_length ) - ) { + if (analysis_image_count - last_alarm_count > post_event_count + && timestamp - GetVideoWriterStartTime() >= min_section_length) { Info("%s: %03d - Left alarm state (%" PRIu64 ") - %d(%d) images", name.c_str(), analysis_image_count, event->Id(), event->Frames(), event->AlarmFrames()); //if ( function != MOCORD || event_close_mode == CLOSE_ALARM || event->Cause() == SIGNAL_CAUSE ) @@ -2199,14 +2148,14 @@ bool Monitor::Analyse() { shared_data->state = state = ((function != MOCORD) ? IDLE : TAPE); } else { Debug(1, - "State %s because image_count(%d)-last_alarm_count(%d) > post_event_count(%d) and timestamp.tv_sec(%" PRIi64 ") - recording.tv_src(%" PRIi64 ") >= min_section_length(%d)", + "State %s because image_count(%d)-last_alarm_count(%d) > post_event_count(%d) and timestamp.tv_sec(%" PRIi64 ") - recording.tv_src(%" PRIi64 ") >= min_section_length(%" PRIi64 ")", State_Strings[state].c_str(), analysis_image_count, last_alarm_count, post_event_count, - static_cast(timestamp->tv_sec), - static_cast(video_store_data->recording.tv_sec), - min_section_length); + static_cast(std::chrono::duration_cast(timestamp.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(GetVideoWriterStartTime().time_since_epoch()).count()), + static_cast(Seconds(min_section_length).count())); } if (Event::PreAlarmCount()) Event::EmptyPreAlarmFrames(); @@ -2230,7 +2179,7 @@ bool Monitor::Analyse() { // incremement pre alarm image count //have_pre_alarmed_frames ++; - Event::AddPreAlarmFrame(snap->image, *timestamp, score, nullptr); + Event::AddPreAlarmFrame(snap->image, timestamp, score, nullptr); } else if (state == ALARM) { for (const Zone &zone : zones) { if (zone.Alarmed()) { @@ -2239,32 +2188,31 @@ bool Monitor::Analyse() { snap->analysis_image = new Image(*(snap->image)); snap->analysis_image->Overlay(*(zone.AlarmImage())); } - } // end if zone is alarmed - } // end foreach zone + } // end if zone is alarmed + } // end foreach zone if (event) { if (noteSetMap.size() > 0) event->updateNotes(noteSetMap); - if ( section_length - && ( ( timestamp->tv_sec - video_store_data->recording.tv_sec ) >= section_length ) - ) { - Warning("%s: %03d - event %" PRIu64 ", has exceeded desired section length. %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %d", + if (section_length != Seconds(0) && (timestamp - GetVideoWriterStartTime() >= section_length)) { + Warning("%s: %03d - event %" PRIu64 ", has exceeded desired section length. %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64, name.c_str(), analysis_image_count, event->Id(), - static_cast(timestamp->tv_sec), static_cast(video_store_data->recording.tv_sec), - static_cast(timestamp->tv_sec - video_store_data->recording.tv_sec), - section_length); + static_cast(std::chrono::duration_cast(timestamp.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(GetVideoWriterStartTime().time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(timestamp - GetVideoWriterStartTime()).count()), + static_cast(Seconds(section_length).count())); closeEvent(); - event = new Event(this, *timestamp, cause, noteSetMap); + event = new Event(this, timestamp, cause, noteSetMap); shared_data->last_event_id = event->Id(); //set up video store data snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile()); - video_store_data->recording = event->StartTime(); + SetVideoWriterStartTime(event->StartTime()); } } else { Error("ALARM but no event"); } } else if ( state == ALERT ) { // Alert means this frame has no motion, but we were alarmed and are still recording. - if ( noteSetMap.size() > 0 ) + if ((noteSetMap.size() > 0) and event) event->updateNotes(noteSetMap); } else if ( state == TAPE ) { // bulk frame code moved to event. @@ -2286,7 +2234,7 @@ bool Monitor::Analyse() { shared_data->last_frame_score = score; } else { Debug(3, "trigger == off"); - if ( event ) { + if (event) { Info("%s: %03d - Closing event %" PRIu64 ", trigger off", name.c_str(), analysis_image_count, event->Id()); closeEvent(); } @@ -2304,17 +2252,17 @@ bool Monitor::Analyse() { snap->image = nullptr; } - packetqueue.unlock(packet_lock); + packetqueue.clearPackets(snap); - if ( snap->image_index > 0 ) { + if (snap->codec_type == AVMEDIA_TYPE_VIDEO) { // Only do these if it's a video packet. shared_data->last_read_index = snap->image_index; analysis_image_count++; - if ( function == MODECT or function == MOCORD ) + if (function == MODECT or function == MOCORD) UpdateAnalysisFPS(); } - shared_data->last_read_time = time(nullptr); - packetqueue.clearPackets(snap); + packetqueue.unlock(packet_lock); + shared_data->last_read_time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); return true; } // end Monitor::Analyse @@ -2332,7 +2280,7 @@ void Monitor::Reload() { } std::string sql = load_monitor_sql + stringtf(" WHERE Id=%d", id); - zmDbRow *row = zmDbFetchOne(sql.c_str()); + zmDbRow *row = zmDbFetchOne(sql); if (!row) { Error("Can't run query: %s", mysql_error(&dbconn)); } else if (MYSQL_ROW dbrow = row->mysql_row()) { @@ -2418,7 +2366,7 @@ void Monitor::ReloadLinkedMonitors(const char *p_linked_monitors) { " AND `Enabled`=1", link_ids[i]); - MYSQL_RES *result = zmDbFetch(sql.c_str()); + MYSQL_RES *result = zmDbFetch(sql); if (!result) { continue; } @@ -2438,11 +2386,11 @@ void Monitor::ReloadLinkedMonitors(const char *p_linked_monitors) { } // end if p_linked_monitors } // end void Monitor::ReloadLinkedMonitors(const char *p_linked_monitors) -std::vector> Monitor::LoadMonitors(std::string &where, Purpose purpose) { +std::vector> Monitor::LoadMonitors(const std::string &where, Purpose purpose) { std::string sql = load_monitor_sql + " WHERE " + where; Debug(1, "Loading Monitors with %s", sql.c_str()); - MYSQL_RES *result = zmDbFetch(sql.c_str()); + MYSQL_RES *result = zmDbFetch(sql); if (!result) { Error("Can't load local monitors: %s", mysql_error(&dbconn)); return {}; @@ -2468,7 +2416,7 @@ std::vector> Monitor::LoadMonitors(std::string &where, return monitors; } -#if ZM_HAS_V4L +#if ZM_HAS_V4L2 std::vector> Monitor::LoadLocalMonitors (const char *device, Purpose purpose) { @@ -2480,7 +2428,7 @@ std::vector> Monitor::LoadLocalMonitors where += stringtf(" AND `ServerId`=%d", staticConfig.SERVER_ID); return LoadMonitors(where, purpose); } -#endif // ZM_HAS_V4L +#endif // ZM_HAS_V4L2 std::vector> Monitor::LoadRemoteMonitors (const char *protocol, const char *host, const char *port, const char *path, Purpose purpose) { @@ -2501,7 +2449,6 @@ std::vector> Monitor::LoadFileMonitors(const char *file return LoadMonitors(where, purpose); } -#if HAVE_LIBAVFORMAT std::vector> Monitor::LoadFfmpegMonitors(const char *file, Purpose purpose) { std::string where = "`Function` != 'None' AND `Type` = 'Ffmpeg'"; if (file[0]) @@ -2510,25 +2457,25 @@ std::vector> Monitor::LoadFfmpegMonitors(const char *fi where += stringtf(" AND `ServerId`=%d", staticConfig.SERVER_ID); return LoadMonitors(where, purpose); } -#endif // HAVE_LIBAVFORMAT /* Returns 0 on success, even if no new images are available (transient error) * Returns -1 on failure. */ int Monitor::Capture() { unsigned int index = image_count % image_buffer_count; + if (image_buffer.empty() or (index >= image_buffer.size())) { + Error("Image Buffer is invalid. Check ImageBufferCount. size is %zu", image_buffer.size()); + return -1; + } - ZMPacket *packet = new ZMPacket(); - packet->timestamp = new struct timeval; + std::shared_ptr packet = std::make_shared(); packet->image_index = image_count; - gettimeofday(packet->timestamp, nullptr); - shared_data->zmc_heartbeat_time = packet->timestamp->tv_sec; - - int captureResult = camera->Capture(*packet); + packet->timestamp = std::chrono::system_clock::now(); + shared_data->zmc_heartbeat_time = std::chrono::system_clock::to_time_t(packet->timestamp); + int captureResult = camera->Capture(packet); Debug(4, "Back from capture result=%d image count %d", captureResult, image_count); if (captureResult < 0) { - Debug(2, "failed capture"); // Unable to capture image // Fake a signal loss image // Not sure what to do here. We will close monitor and kill analysis_thread but what about rtsp server? @@ -2539,24 +2486,23 @@ int Monitor::Capture() { capture_image->Fill(signalcolor); shared_data->signal = false; shared_data->last_write_index = index; - shared_data->last_write_time = image_buffer[index].timestamp->tv_sec; - image_buffer[index].image->Assign(*capture_image); - *(image_buffer[index].timestamp) = *(packet->timestamp); + shared_data->last_write_time = shared_timestamps[index].tv_sec; + image_buffer[index]->Assign(*capture_image); + shared_timestamps[index] = zm::chrono::duration_cast(packet->timestamp.time_since_epoch()); delete capture_image; image_count++; - delete packet; // What about timestamping it? // Don't want to do analysis on it, but we won't due to signal return -1; - } else if ( captureResult > 0 ) { + } else if (captureResult > 0) { shared_data->signal = true; // Assume if getting packets that we are getting something useful. CheckSignalPoints can correct this later. // If we captured, let's assume signal, Decode will detect further if (!decoding_enabled) { shared_data->last_write_index = index; - shared_data->last_write_time = packet->timestamp->tv_sec; + shared_data->last_write_time = std::chrono::system_clock::to_time_t(packet->timestamp); } Debug(2, "Have packet stream_index:%d ?= videostream_id: %d q.vpktcount %d event? %d image_count %d", - packet->packet.stream_index, video_stream_id, packetqueue.packet_count(video_stream_id), ( event ? 1 : 0 ), image_count ); + packet->packet.stream_index, video_stream_id, packetqueue.packet_count(video_stream_id), ( event ? 1 : 0 ), image_count); if (packet->codec_type == AVMEDIA_TYPE_VIDEO) { packet->packet.stream_index = video_stream_id; // Convert to packetQueue's index @@ -2564,13 +2510,11 @@ int Monitor::Capture() { if ( packet->keyframe ) { // avcodec strips out important nals that describe the stream and // stick them in extradata. Need to send them along with keyframes -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) AVStream *stream = camera->getVideoStream(); video_fifo->write( static_cast(stream->codecpar->extradata), stream->codecpar->extradata_size, packet->pts); -#endif } video_fifo->writePacket(*packet); } @@ -2586,27 +2530,22 @@ int Monitor::Capture() { packetqueue.queuePacket(packet); } else { Debug(4, "Not Queueing audio packet"); - delete packet; } // Don't update last_write_index because that is used for live streaming //shared_data->last_write_time = image_buffer[index].timestamp->tv_sec; return 1; } else { Debug(1, "Unknown codec type %d", packet->codec_type); - delete packet; return 1; } // end if audio image_count++; // Will only be queued if there are iterators allocated in the queue. - if ( !packetqueue.queuePacket(packet) ) { - delete packet; - } + packetqueue.queuePacket(packet); UpdateCaptureFPS(); } else { // result == 0 // Question is, do we update last_write_index etc? - delete packet; return 0; } // end if result @@ -2629,10 +2568,9 @@ int Monitor::Capture() { } // end Monitor::Capture bool Monitor::Decode() { - ZMLockedPacket *packet_lock = packetqueue.get_packet(decoder_it); + ZMLockedPacket *packet_lock = packetqueue.get_packet_and_increment_it(decoder_it); if (!packet_lock) return false; - ZMPacket *packet = packet_lock->packet_; - packetqueue.increment_it(decoder_it); + std::shared_ptr packet = packet_lock->packet_; if (packet->codec_type != AVMEDIA_TYPE_VIDEO) { Debug(4, "Not video"); packetqueue.unlock(packet_lock); @@ -2653,11 +2591,33 @@ bool Monitor::Decode() { if (!convert_context) { AVPixelFormat imagePixFormat = (AVPixelFormat)(packet->image->AVPixFormat()); + AVPixelFormat inputPixFormat; + bool changeColorspaceDetails = false; + switch (input_frame->format) { + case AV_PIX_FMT_YUVJ420P: + inputPixFormat = AV_PIX_FMT_YUV420P; + changeColorspaceDetails = true; + break; + case AV_PIX_FMT_YUVJ422P: + inputPixFormat = AV_PIX_FMT_YUV422P; + changeColorspaceDetails = true; + break; + case AV_PIX_FMT_YUVJ444P: + inputPixFormat = AV_PIX_FMT_YUV444P; + changeColorspaceDetails = true; + break; + case AV_PIX_FMT_YUVJ440P: + inputPixFormat = AV_PIX_FMT_YUV440P; + changeColorspaceDetails = true; + break; + default: + inputPixFormat = (AVPixelFormat)input_frame->format; + } convert_context = sws_getContext( input_frame->width, input_frame->height, - (AVPixelFormat)input_frame->format, + inputPixFormat, camera_width, camera_height, imagePixFormat, SWS_BICUBIC, nullptr, nullptr, nullptr); @@ -2671,10 +2631,22 @@ bool Monitor::Decode() { } else { Debug(1, "Setup conversion context for %dx%d %s to %dx%d %s", input_frame->width, input_frame->height, - av_get_pix_fmt_name((AVPixelFormat)input_frame->format), + av_get_pix_fmt_name(inputPixFormat), camera_width, camera_height, av_get_pix_fmt_name(imagePixFormat) ); + if (changeColorspaceDetails) { + // change the range of input data by first reading the current color space and then setting it's range as yuvj. + int dummy[4]; + int srcRange, dstRange; + int brightness, contrast, saturation; + sws_getColorspaceDetails(convert_context, (int**)&dummy, &srcRange, (int**)&dummy, &dstRange, &brightness, &contrast, &saturation); + const int* coefs = sws_getCoefficients(SWS_CS_DEFAULT); + srcRange = 1; // this marks that values are according to yuvj + sws_setColorspaceDetails(convert_context, coefs, srcRange, coefs, dstRange, + brightness, contrast, saturation); + } + } } if (convert_context) { @@ -2682,12 +2654,14 @@ bool Monitor::Decode() { delete packet->image; packet->image = nullptr; } + av_frame_unref(dest_frame); } // end if have convert_context } // end if need transfer to image } else { Debug(1, "No packet.size(%d) or packet->in_frame(%p). Not decoding", packet->packet.size, packet->in_frame); } } // end if need_decoding + Image* capture_image = nullptr; unsigned int index = image_count % image_buffer_count; @@ -2726,9 +2700,9 @@ bool Monitor::Decode() { } } - if ( orientation != ROTATE_0 ) { - Debug(2, "Doing rotation"); - switch ( orientation ) { + if (orientation != ROTATE_0) { + Debug(3, "Doing rotation"); + switch (orientation) { case ROTATE_0 : // No action required break; @@ -2750,33 +2724,36 @@ bool Monitor::Decode() { } if (config.timestamp_on_capture) { - Debug(3, "Timestampprivacy"); + Debug(3, "Timestamping"); TimestampImage(packet->image, packet->timestamp); } - image_buffer[index].image->Assign(*(packet->image)); - *(image_buffer[index].timestamp) = *(packet->timestamp); + image_buffer[index]->Assign(*(packet->image)); + shared_timestamps[index] = zm::chrono::duration_cast(packet->timestamp.time_since_epoch()); } // end if have image packet->decoded = true; - shared_data->signal = ( capture_image and signal_check_points ) ? CheckSignal(capture_image) : true; + shared_data->signal = (capture_image and signal_check_points) ? CheckSignal(capture_image) : true; shared_data->last_write_index = index; - shared_data->last_write_time = packet->timestamp->tv_sec; + shared_data->last_write_time = std::chrono::system_clock::to_time_t(packet->timestamp); packetqueue.unlock(packet_lock); return true; } // end bool Monitor::Decode() -void Monitor::TimestampImage(Image *ts_image, const struct timeval *ts_time) const { - if ( !label_format[0] ) +void Monitor::TimestampImage(Image *ts_image, SystemTimePoint ts_time) const { + if (!label_format[0]) return; // Expand the strftime macros first char label_time_text[256]; tm ts_tm = {}; - strftime(label_time_text, sizeof(label_time_text), label_format.c_str(), localtime_r(&ts_time->tv_sec, &ts_tm)); + time_t ts_time_t = std::chrono::system_clock::to_time_t(ts_time); + strftime(label_time_text, sizeof(label_time_text), label_format.c_str(), localtime_r(&ts_time_t, &ts_tm)); + char label_text[1024]; const char *s_ptr = label_time_text; char *d_ptr = label_text; - while ( *s_ptr && ((d_ptr-label_text) < (unsigned int)sizeof(label_text)) ) { + + while (*s_ptr && ((d_ptr - label_text) < (unsigned int) sizeof(label_text))) { if ( *s_ptr == config.timestamp_code_char[0] ) { bool found_macro = false; switch ( *(s_ptr+1) ) { @@ -2789,7 +2766,10 @@ void Monitor::TimestampImage(Image *ts_image, const struct timeval *ts_time) con found_macro = true; break; case 'f' : - d_ptr += snprintf(d_ptr, sizeof(label_text)-(d_ptr-label_text), "%02ld", ts_time->tv_usec/10000); + typedef std::chrono::duration Centiseconds; + Centiseconds centi_sec = std::chrono::duration_cast( + ts_time.time_since_epoch() - std::chrono::duration_cast(ts_time.time_since_epoch())); + d_ptr += snprintf(d_ptr, sizeof(label_text) - (d_ptr - label_text), "%02ld", centi_sec.count()); found_macro = true; break; } @@ -2834,8 +2814,8 @@ unsigned int Monitor::DetectMotion(const Image &comp_image, Event::StringSet &zo ref_image.Delta(comp_image, &delta_image); if (config.record_diag_images) { - ref_image.WriteJpeg(diag_path_ref.c_str(), config.record_diag_images_fifo); - delta_image.WriteJpeg(diag_path_delta.c_str(), config.record_diag_images_fifo); + ref_image.WriteJpeg(diag_path_ref, config.record_diag_images_fifo); + delta_image.WriteJpeg(diag_path_delta, config.record_diag_images_fifo); } // Blank out all exclusion zones @@ -2881,7 +2861,7 @@ unsigned int Monitor::DetectMotion(const Image &comp_image, Event::StringSet &zo } // end if CheckAlarms } // end foreach zone - Coord alarm_centre; + Vector2 alarm_centre; int top_score = -1; if (alarm) { @@ -2947,8 +2927,8 @@ unsigned int Monitor::DetectMotion(const Image &comp_image, Event::StringSet &zo } // end if alarm if (top_score > 0) { - shared_data->alarm_x = alarm_centre.X(); - shared_data->alarm_y = alarm_centre.Y(); + shared_data->alarm_x = alarm_centre.x_; + shared_data->alarm_y = alarm_centre.y_; Info("Got alarm centre at %d,%d, at count %d", shared_data->alarm_x, shared_data->alarm_y, analysis_image_count); @@ -2967,14 +2947,14 @@ bool Monitor::DumpSettings(char *output, bool verbose) { sprintf( output+strlen(output), "Id : %u\n", id ); sprintf( output+strlen(output), "Name : %s\n", name.c_str() ); sprintf( output+strlen(output), "Type : %s\n", camera->IsLocal()?"Local":(camera->IsRemote()?"Remote":"File") ); -#if ZM_HAS_V4L +#if ZM_HAS_V4L2 if ( camera->IsLocal() ) { LocalCamera* cam = static_cast(camera.get()); sprintf( output+strlen(output), "Device : %s\n", cam->Device().c_str() ); sprintf( output+strlen(output), "Channel : %d\n", cam->Channel() ); sprintf( output+strlen(output), "Standard : %d\n", cam->Standard() ); } else -#endif // ZM_HAS_V4L +#endif // ZM_HAS_V4L2 if ( camera->IsRemote() ) { RemoteCamera* cam = static_cast(camera.get()); sprintf( output+strlen(output), "Protocol : %s\n", cam->Protocol().c_str() ); @@ -2983,27 +2963,25 @@ bool Monitor::DumpSettings(char *output, bool verbose) { sprintf( output+strlen(output), "Path : %s\n", cam->Path().c_str() ); } else if ( camera->IsFile() ) { FileCamera* cam = static_cast(camera.get()); - sprintf( output+strlen(output), "Path : %s\n", cam->Path() ); + sprintf( output+strlen(output), "Path : %s\n", cam->Path().c_str() ); } -#if HAVE_LIBAVFORMAT else if ( camera->IsFfmpeg() ) { FfmpegCamera* cam = static_cast(camera.get()); sprintf( output+strlen(output), "Path : %s\n", cam->Path().c_str() ); } -#endif // HAVE_LIBAVFORMAT sprintf( output+strlen(output), "Width : %u\n", camera->Width() ); sprintf( output+strlen(output), "Height : %u\n", camera->Height() ); -#if ZM_HAS_V4L +#if ZM_HAS_V4L2 if ( camera->IsLocal() ) { LocalCamera* cam = static_cast(camera.get()); sprintf( output+strlen(output), "Palette : %d\n", cam->Palette() ); } -#endif // ZM_HAS_V4L +#endif // ZM_HAS_V4L2 sprintf(output+strlen(output), "Colours : %u\n", camera->Colours() ); sprintf(output+strlen(output), "Subpixel Order : %u\n", camera->SubpixelOrder() ); sprintf(output+strlen(output), "Event Prefix : %s\n", event_prefix.c_str() ); sprintf(output+strlen(output), "Label Format : %s\n", label_format.c_str() ); - sprintf(output+strlen(output), "Label Coord : %d,%d\n", label_coord.X(), label_coord.Y() ); + sprintf(output+strlen(output), "Label Coord : %d,%d\n", label_coord.x_, label_coord.y_ ); sprintf(output+strlen(output), "Label Size : %d\n", label_size ); sprintf(output+strlen(output), "Image Buffer Count : %d\n", image_buffer_count ); sprintf(output+strlen(output), "Warmup Count : %d\n", warmup_count ); @@ -3011,10 +2989,10 @@ bool Monitor::DumpSettings(char *output, bool verbose) { sprintf(output+strlen(output), "Post Event Count : %d\n", post_event_count ); sprintf(output+strlen(output), "Stream Replay Buffer : %d\n", stream_replay_buffer ); sprintf(output+strlen(output), "Alarm Frame Count : %d\n", alarm_frame_count ); - sprintf(output+strlen(output), "Section Length : %d\n", section_length); - sprintf(output+strlen(output), "Min Section Length : %d\n", min_section_length); - sprintf(output+strlen(output), "Maximum FPS : %.2f\n", capture_delay?(double)DT_PREC_3/capture_delay:0.0); - sprintf(output+strlen(output), "Alarm Maximum FPS : %.2f\n", alarm_capture_delay?(double)DT_PREC_3/alarm_capture_delay:0.0); + sprintf(output+strlen(output), "Section Length : %" PRIi64 "\n", static_cast(Seconds(section_length).count())); + sprintf(output+strlen(output), "Min Section Length : %" PRIi64 "\n", static_cast(Seconds(min_section_length).count())); + sprintf(output+strlen(output), "Maximum FPS : %.2f\n", capture_delay != Seconds(0) ? 1 / FPSeconds(capture_delay).count() : 0.0); + sprintf(output+strlen(output), "Alarm Maximum FPS : %.2f\n", alarm_capture_delay != Seconds(0) ? 1 / FPSeconds(alarm_capture_delay).count() : 0.0); sprintf(output+strlen(output), "Reference Blend %%ge : %d\n", ref_blend_perc); sprintf(output+strlen(output), "Alarm Reference Blend %%ge : %d\n", alarm_ref_blend_perc); sprintf(output+strlen(output), "Track Motion : %d\n", track_motion); @@ -3026,7 +3004,7 @@ bool Monitor::DumpSettings(char *output, bool verbose) { function==MOCORD?"Continuous Record with Motion Detection":( function==NODECT?"Externally Triggered only, no Motion Detection":"Unknown" )))))); - sprintf(output+strlen(output), "Zones : %lu\n", zones.size()); + sprintf(output+strlen(output), "Zones : %zu\n", zones.size()); for (const Zone &zone : zones) { zone.DumpSettings(output+strlen(output), verbose); } @@ -3060,28 +3038,20 @@ int Monitor::PrimeCapture() { if (rtsp_server) { if (video_stream_id >= 0) { AVStream *videoStream = camera->getVideoStream(); - snprintf(shared_data->video_fifo_path, sizeof(shared_data->video_fifo_path)-1, "%s/video_fifo_%u.%s", - staticConfig.PATH_SOCKS.c_str(), - id, -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - avcodec_get_name(videoStream->codecpar->codec_id) -#else - avcodec_get_name(videoStream->codec->codec_id) -#endif - ); + snprintf(shared_data->video_fifo_path, sizeof(shared_data->video_fifo_path) - 1, "%s/video_fifo_%u.%s", + staticConfig.PATH_SOCKS.c_str(), + id, + avcodec_get_name(videoStream->codecpar->codec_id) + ); video_fifo = new Fifo(shared_data->video_fifo_path, true); } if (record_audio and (audio_stream_id >= 0)) { AVStream *audioStream = camera->getAudioStream(); if (audioStream && CODEC(audioStream)) { - snprintf(shared_data->audio_fifo_path, sizeof(shared_data->audio_fifo_path)-1, "%s/audio_fifo_%u.%s", - staticConfig.PATH_SOCKS.c_str(), id, -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - avcodec_get_name(audioStream->codecpar->codec_id) -#else - avcodec_get_name(audioStream->codec->codec_id) -#endif - ); + snprintf(shared_data->audio_fifo_path, sizeof(shared_data->audio_fifo_path) - 1, "%s/audio_fifo_%u.%s", + staticConfig.PATH_SOCKS.c_str(), id, + avcodec_get_name(audioStream->codecpar->codec_id) + ); audio_fifo = new Fifo(shared_data->audio_fifo_path, true); } else { Warning("No audioStream %p or codec?", audioStream); @@ -3093,7 +3063,7 @@ int Monitor::PrimeCapture() { if (!decoder_it) decoder_it = packetqueue.get_video_it(false); if (!decoder) { Debug(1, "Creating decoder thread"); - decoder = ZM::make_unique(this); + decoder = zm::make_unique(this); } else { Debug(1, "Restartg decoder thread"); decoder->Start(); @@ -3108,7 +3078,7 @@ int Monitor::PrimeCapture() { } if (!analysis_thread) { Debug(1, "Starting an analysis thread for monitor (%d)", id); - analysis_thread = ZM::make_unique(this); + analysis_thread = zm::make_unique(this); } else { Debug(1, "Restarting analysis thread for monitor (%d)", id); analysis_thread->Start(); @@ -3171,21 +3141,20 @@ void Monitor::get_ref_image() { Debug(1, "Waiting for capture daemon lastwriteindex(%d) lastwritetime(%" PRIi64 ")", shared_data->last_write_index, static_cast(shared_data->last_write_time)); - if ( snap_lock and ! snap_lock->packet_->image ) { + if (snap_lock and ! snap_lock->packet_->image) { delete snap_lock; // can't analyse it anyways, incremement packetqueue.increment_it(analysis_it); } - //usleep(10000); } - if ( zm_terminate ) + if (zm_terminate) return; - ZMPacket *snap = snap_lock->packet_; + std::shared_ptr snap = snap_lock->packet_; Debug(1, "get_ref_image: packet.stream %d ?= video_stream %d, packet image id %d packet image %p", snap->packet.stream_index, video_stream_id, snap->image_index, snap->image ); // Might not have been decoded yet FIXME - if ( snap->image ) { + if (snap->image) { ref_image.Assign(width, height, camera->Colours(), camera->SubpixelOrder(), snap->image->Buffer(), camera->ImageSize()); Debug(2, "Have ref image about to unlock"); @@ -3193,25 +3162,26 @@ void Monitor::get_ref_image() { Debug(2, "Have no ref image about to unlock"); } delete snap_lock; -} +} // get_ref_image std::vector Monitor::Groups() { // At the moment, only load groups once. - if ( !groups.size() ) { + if (!groups.size()) { std::string sql = stringtf( "SELECT `Id`, `ParentId`, `Name` FROM `Groups` WHERE `Groups.Id` IN " "(SELECT `GroupId` FROM `Groups_Monitors` WHERE `MonitorId`=%d)", id); - MYSQL_RES *result = zmDbFetch(sql.c_str()); - if ( !result ) { + MYSQL_RES *result = zmDbFetch(sql); + if (!result) { Error("Can't load groups: %s", mysql_error(&dbconn)); return groups; } int n_groups = mysql_num_rows(result); Debug(1, "Got %d groups", n_groups); - while ( MYSQL_ROW dbrow = mysql_fetch_row(result) ) { + groups.reserve(n_groups); + while (MYSQL_ROW dbrow = mysql_fetch_row(result)) { groups.push_back(new Group(dbrow)); } - if ( mysql_errno(&dbconn) ) { + if (mysql_errno(&dbconn)) { Error("Can't fetch row: %s", mysql_error(&dbconn)); } mysql_free_result(result); diff --git a/src/zm_monitor.h b/src/zm_monitor.h index 0efc8a130..974ab87cc 100644 --- a/src/zm_monitor.h +++ b/src/zm_monitor.h @@ -211,7 +211,7 @@ protected: #if ZM_MEM_MAPPED int map_fd; - char mem_file[PATH_MAX]; + std::string mem_file; #else // ZM_MEM_MAPPED int shm_id; #endif // ZM_MEM_MAPPED @@ -305,7 +305,7 @@ protected: std::string event_prefix; // The prefix applied to event names as they are created std::string label_format; // The format of the timestamp on the images - Coord label_coord; // The coordinates of the timestamp on the images + Vector2 label_coord; // The coordinates of the timestamp on the images int label_size; // Size of the timestamp on the images int32_t image_buffer_count; // Size of circular image buffer, kept in /dev/shm int32_t max_image_buffer_count; // Max # of video packets to keep in packet queue @@ -313,15 +313,15 @@ protected: int pre_event_count; // How many images to hold and prepend to an alarm event int post_event_count; // How many unalarmed images must occur before the alarm state is reset int stream_replay_buffer; // How many frames to store to support DVR functions, IGNORED from this object, passed directly into zms now - int section_length; // How long events should last in continuous modes - int min_section_length; // Minimum event length when using event_close_mode == ALARM + Seconds section_length; // How long events should last in continuous modes + Seconds min_section_length; // Minimum event length when using event_close_mode == ALARM bool adaptive_skip; // Whether to use the newer adaptive algorithm for this monitor int frame_skip; // How many frames to skip in continuous modes int motion_frame_skip; // How many frames to skip in motion detection double analysis_fps_limit; // Target framerate for video analysis - unsigned int analysis_update_delay; // How long we wait before updating analysis parameters - int capture_delay; // How long we wait between capture frames - int alarm_capture_delay; // How long we wait between capture frames when in alarm state + Microseconds analysis_update_delay; // How long we wait before updating analysis parameters + Microseconds capture_delay; // How long we wait between capture frames + Microseconds alarm_capture_delay; // How long we wait between capture frames when in alarm state int alarm_frame_count; // How many alarm frames are required before an event is triggered int alert_to_alarm_frame_count; // How many alarm frames (consecutive score frames) are required to return alarm from alert // value for now is the same number configured in alarm_frame_count, maybe getting his own parameter some day @@ -354,17 +354,17 @@ protected: int last_section_mod; int buffer_count; State state; - time_t start_time; - double last_fps_time; - double last_analysis_fps_time; - time_t auto_resume_time; + SystemTimePoint start_time; + SystemTimePoint last_fps_time; + SystemTimePoint last_analysis_fps_time; + SystemTimePoint auto_resume_time; unsigned int last_motion_score; EventCloseMode event_close_mode; #if ZM_MEM_MAPPED int map_fd; - char mem_file[PATH_MAX]; + std::string mem_file; #else // ZM_MEM_MAPPED int shm_id; #endif // ZM_MEM_MAPPED @@ -376,8 +376,7 @@ protected: struct timeval *shared_timestamps; unsigned char *shared_images; - ZMPacket *image_buffer; - ZMPacket next_buffer; /* Used by four field deinterlacing */ + std::vector image_buffer; int video_stream_id; // will be filled in PrimeCapture int audio_stream_id; // will be filled in PrimeCapture @@ -425,7 +424,6 @@ protected: public: explicit Monitor(); - explicit Monitor(unsigned int p_id); ~Monitor(); @@ -454,7 +452,7 @@ public: inline unsigned int Id() const { return id; } inline const char *Name() const { return name.c_str(); } - inline unsigned int ServerId() { return server_id; } + inline unsigned int ServerId() const { return server_id; } inline Storage *getStorage() { if ( ! storage ) { storage = new Storage(storage_id); @@ -487,7 +485,7 @@ public: } inline bool Exif() const { return embed_exif; } inline bool RTSPServer() const { return rtsp_server; } - inline bool RecordAudio() { return record_audio; } + inline bool RecordAudio() const { return record_audio; } /* inline Purpose Purpose() { return purpose }; @@ -514,8 +512,12 @@ public: uint64_t GetVideoWriterEventId() const { return video_store_data->current_event; } void SetVideoWriterEventId( uint64_t p_event_id ) { video_store_data->current_event = p_event_id; } - struct timeval GetVideoWriterStartTime() const { return video_store_data->recording; } - void SetVideoWriterStartTime(const struct timeval &t) { video_store_data->recording = t; } + SystemTimePoint GetVideoWriterStartTime() const { + return SystemTimePoint(zm::chrono::duration_cast(video_store_data->recording)); + } + void SetVideoWriterStartTime(SystemTimePoint t) { + video_store_data->recording = zm::chrono::duration_cast(t.time_since_epoch()); + } unsigned int GetPreEventCount() const { return pre_event_count; }; int32_t GetImageBufferCount() const { return image_buffer_count; }; @@ -526,20 +528,20 @@ public: AVStream *GetVideoStream() const { return camera ? camera->getVideoStream() : nullptr; }; AVCodecContext *GetVideoCodecContext() const { return camera ? camera->getVideoCodecContext() : nullptr; }; - const std::string GetSecondPath() const { return second_path; }; - const std::string GetVideoFifoPath() const { return shared_data ? shared_data->video_fifo_path : ""; }; - const std::string GetAudioFifoPath() const { return shared_data ? shared_data->audio_fifo_path : ""; }; - const std::string GetRTSPStreamName() const { return rtsp_streamname; }; + std::string GetSecondPath() const { return second_path; }; + std::string GetVideoFifoPath() const { return shared_data ? shared_data->video_fifo_path : ""; }; + std::string GetAudioFifoPath() const { return shared_data ? shared_data->audio_fifo_path : ""; }; + std::string GetRTSPStreamName() const { return rtsp_streamname; }; int GetImage(int32_t index=-1, int scale=100); ZMPacket *getSnapshot( int index=-1 ) const; - struct timeval GetTimestamp( int index=-1 ) const; + SystemTimePoint GetTimestamp(int index = -1) const; void UpdateAdaptiveSkip(); useconds_t GetAnalysisRate(); - unsigned int GetAnalysisUpdateDelay() const { return analysis_update_delay; } + Microseconds GetAnalysisUpdateDelay() const { return analysis_update_delay; } unsigned int GetCaptureMaxFPS() const { return capture_max_fps; } - int GetCaptureDelay() const { return capture_delay; } - int GetAlarmCaptureDelay() const { return alarm_capture_delay; } + Microseconds GetCaptureDelay() const { return capture_delay; } + Microseconds GetAlarmCaptureDelay() const { return alarm_capture_delay; } unsigned int GetLastReadIndex() const; unsigned int GetLastWriteIndex() const; uint64_t GetLastEventId() const; @@ -550,9 +552,11 @@ public: void ForceAlarmOff(); void CancelForced(); TriggerState GetTriggerState() const { return trigger_data ? trigger_data->trigger_state : TRIGGER_CANCEL; } - inline time_t getStartupTime() const { return shared_data->startup_time; } - inline void setStartupTime( time_t p_time ) { shared_data->startup_time = p_time; } - inline void setHeartbeatTime( time_t p_time ) { shared_data->zmc_heartbeat_time = p_time; } + SystemTimePoint GetStartupTime() const { return std::chrono::system_clock::from_time_t(shared_data->startup_time); } + void SetStartupTime(SystemTimePoint time) { shared_data->startup_time = std::chrono::system_clock::to_time_t(time); } + void SetHeartbeatTime(SystemTimePoint time) { + shared_data->zmc_heartbeat_time = std::chrono::system_clock::to_time_t(time); + } void get_ref_image(); int LabelSize() const { return label_size; } @@ -583,7 +587,7 @@ public: bool Analyse(); bool Decode(); void DumpImage( Image *dump_image ) const; - void TimestampImage( Image *ts_image, const struct timeval *ts_time ) const; + void TimestampImage(Image *ts_image, SystemTimePoint ts_time) const; void closeEvent(); void Reload(); @@ -595,31 +599,27 @@ public: std::vector Groups(); StringVector GroupNames(); - static std::vector> LoadMonitors(std::string &sql, Purpose purpose); // Returns # of Monitors loaded, 0 on failure. -#if ZM_HAS_V4L + static std::vector> LoadMonitors(const std::string &sql, Purpose purpose); // Returns # of Monitors loaded, 0 on failure. +#if ZM_HAS_V4L2 static std::vector> LoadLocalMonitors(const char *device, Purpose purpose); -#endif // ZM_HAS_V4L +#endif // ZM_HAS_V4L2 static std::vector> LoadRemoteMonitors(const char *protocol, const char *host, const char*port, const char*path, Purpose purpose); static std::vector> LoadFileMonitors(const char *file, Purpose purpose); -#if HAVE_LIBAVFORMAT static std::vector> LoadFfmpegMonitors(const char *file, Purpose purpose); -#endif // HAVE_LIBAVFORMAT static std::shared_ptr Load(unsigned int id, bool load_zones, Purpose purpose); void Load(MYSQL_ROW dbrow, bool load_zones, Purpose purpose); //void writeStreamImage( Image *image, struct timeval *timestamp, int scale, int mag, int x, int y ); //void StreamImages( int scale=100, int maxfps=10, time_t ttl=0, int msq_id=0 ); //void StreamImagesRaw( int scale=100, int maxfps=10, time_t ttl=0 ); //void StreamImagesZip( int scale=100, int maxfps=10, time_t ttl=0 ); -#if HAVE_LIBAVCODEC //void StreamMpeg( const char *format, int scale=100, int maxfps=10, int bitrate=100000 ); -#endif // HAVE_LIBAVCODEC double get_capture_fps( ) const { return shared_data ? shared_data->capture_fps : 0.0; } double get_analysis_fps( ) const { return shared_data ? shared_data->analysis_fps : 0.0; } - int Importance() { return importance; } + int Importance() const { return importance; } }; #define MOD_ADD( var, delta, limit ) (((var)+(limit)+(delta))%(limit)) diff --git a/src/zm_monitorstream.cpp b/src/zm_monitorstream.cpp index 63f85d71b..4e5e96f4a 100644 --- a/src/zm_monitorstream.cpp +++ b/src/zm_monitorstream.cpp @@ -85,11 +85,11 @@ void MonitorStream::processCommand(const CmdMsg *msg) { Debug(1, "Got PAUSE command"); paused = true; delayed = true; - last_frame_sent = TV_2_FLOAT(now); + last_frame_sent = now; break; case CMD_PLAY : Debug(1, "Got PLAY command"); - if ( paused ) { + if (paused) { paused = false; delayed = true; } @@ -97,7 +97,7 @@ void MonitorStream::processCommand(const CmdMsg *msg) { break; case CMD_VARPLAY : Debug(1, "Got VARPLAY command"); - if ( paused ) { + if (paused) { paused = false; delayed = true; } @@ -110,7 +110,7 @@ void MonitorStream::processCommand(const CmdMsg *msg) { break; case CMD_FASTFWD : Debug(1, "Got FAST FWD command"); - if ( paused ) { + if (paused) { paused = false; delayed = true; } @@ -135,27 +135,27 @@ void MonitorStream::processCommand(const CmdMsg *msg) { } break; case CMD_SLOWFWD : - Debug( 1, "Got SLOW FWD command" ); + Debug(1, "Got SLOW FWD command"); paused = true; delayed = true; replay_rate = ZM_RATE_BASE; step = 1; break; case CMD_SLOWREV : - Debug( 1, "Got SLOW REV command" ); + Debug(1, "Got SLOW REV command"); paused = true; delayed = true; replay_rate = ZM_RATE_BASE; step = -1; break; case CMD_FASTREV : - Debug( 1, "Got FAST REV command" ); - if ( paused ) { + Debug(1, "Got FAST REV command"); + if (paused) { paused = false; delayed = true; } // Set play rate - switch ( replay_rate ) { + switch (replay_rate) { case -2 * ZM_RATE_BASE : replay_rate = -5 * ZM_RATE_BASE; break; @@ -255,7 +255,7 @@ void MonitorStream::processCommand(const CmdMsg *msg) { } status_data; status_data.id = monitor->Id(); - if ( ! monitor->ShmValid() ) { + if (!monitor->ShmValid()) { status_data.fps = 0.0; status_data.capture_fps = 0.0; status_data.analysis_fps = 0.0; @@ -265,7 +265,14 @@ void MonitorStream::processCommand(const CmdMsg *msg) { status_data.forced = false; status_data.buffer_level = 0; } else { - status_data.fps = monitor->GetFPS(); + FPSeconds elapsed = now - last_fps_update; + if (elapsed.count()) { + actual_fps = (frame_count - last_frame_count) / elapsed.count(); + last_frame_count = frame_count; + last_fps_update = now; + } + + status_data.fps = actual_fps; status_data.capture_fps = monitor->get_capture_fps(); status_data.analysis_fps = monitor->get_analysis_fps(); status_data.state = monitor->shared_data->state; @@ -280,7 +287,7 @@ void MonitorStream::processCommand(const CmdMsg *msg) { status_data.delayed = delayed; status_data.paused = paused; status_data.rate = replay_rate; - status_data.delay = TV_2_FLOAT(now) - TV_2_FLOAT(last_frame_timestamp); + status_data.delay = FPSeconds(now - last_frame_timestamp).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", status_data.fps, @@ -320,39 +327,37 @@ void MonitorStream::processCommand(const CmdMsg *msg) { //updateFrameRate(monitor->GetFPS()); } // end void MonitorStream::processCommand(const CmdMsg *msg) -bool MonitorStream::sendFrame(const char *filepath, struct timeval *timestamp) { +bool MonitorStream::sendFrame(const std::string &filepath, SystemTimePoint timestamp) { bool send_raw = ((scale>=ZM_SCALE_BASE)&&(zoom==ZM_SCALE_BASE)); if ( ( type != STREAM_JPEG ) || - ( (!config.timestamp_on_capture) && timestamp ) + (!config.timestamp_on_capture) ) send_raw = false; - if ( !send_raw ) { - Image temp_image(filepath); + if (!send_raw) { + Image temp_image(filepath.c_str()); return sendFrame(&temp_image, timestamp); } else { int img_buffer_size = 0; static unsigned char img_buffer[ZM_MAX_IMAGE_SIZE]; - FILE *fdj = nullptr; - if ( (fdj = fopen(filepath, "r")) ) { + if (FILE *fdj = fopen(filepath.c_str(), "r")) { img_buffer_size = fread(img_buffer, 1, sizeof(img_buffer), fdj); fclose(fdj); } else { - Error("Can't open %s: %s", filepath, strerror(errno)); + Error("Can't open %s: %s", filepath.c_str(), strerror(errno)); return false; } // Calculate how long it takes to actually send the frame - struct timeval frameStartTime; - gettimeofday(&frameStartTime, nullptr); + TimePoint send_start_time = std::chrono::steady_clock::now(); if ( - (0 > fprintf(stdout, "Content-Length: %d\r\nX-Timestamp: %d.%06d\r\n\r\n", - img_buffer_size, (int)timestamp->tv_sec, (int)timestamp->tv_usec)) + (0 > fprintf(stdout, "Content-Length: %d\r\nX-Timestamp: %.6f\r\n\r\n", + img_buffer_size, std::chrono::duration_cast(timestamp.time_since_epoch()).count())) || (fwrite(img_buffer, img_buffer_size, 1, stdout) != 1) ) { @@ -363,52 +368,52 @@ bool MonitorStream::sendFrame(const char *filepath, struct timeval *timestamp) { fputs("\r\n", stdout); fflush(stdout); - struct timeval frameEndTime; - gettimeofday(&frameEndTime, nullptr); + TimePoint send_end_time = std::chrono::steady_clock::now(); + TimePoint::duration frame_send_time = send_end_time - send_start_time; - int frameSendTime = tvDiffMsec(frameStartTime, frameEndTime); - if ( frameSendTime > 1000/maxfps ) { + if (frame_send_time > Milliseconds(lround(Milliseconds::period::den / maxfps))) { maxfps /= 2; - Info("Frame send time %d msec too slow, throttling maxfps to %.2f", frameSendTime, maxfps); + Info("Frame send time %" PRIi64 " ms too slow, throttling maxfps to %.2f", + static_cast(std::chrono::duration_cast(frame_send_time).count()), + maxfps); } - last_frame_sent = TV_2_FLOAT(now); + last_frame_sent = now; return true; } return false; -} // end bool MonitorStream::sendFrame(const char *filepath, struct timeval *timestamp) +} -bool MonitorStream::sendFrame(Image *image, struct timeval *timestamp) { +bool MonitorStream::sendFrame(Image *image, SystemTimePoint timestamp) { Image *send_image = prepareImage(image); - if ( !config.timestamp_on_capture && timestamp ) + if (!config.timestamp_on_capture) { monitor->TimestampImage(send_image, timestamp); + } fputs("--" BOUNDARY "\r\n", stdout); -#if HAVE_LIBAVCODEC if ( type == STREAM_MPEG ) { if ( !vid_stream ) { vid_stream = new VideoStream("pipe:", format, bitrate, effective_fps, send_image->Colours(), send_image->SubpixelOrder(), send_image->Width(), send_image->Height()); fprintf(stdout, "Content-type: %s\r\n\r\n", vid_stream->MimeType()); vid_stream->OpenStream(); } - static struct timeval base_time; - struct DeltaTimeval delta_time; - if ( !frame_count ) - base_time = *timestamp; - DELTA_TIMEVAL(delta_time, *timestamp, base_time, DT_PREC_3); - /* double pts = */ vid_stream->EncodeFrame(send_image->Buffer(), send_image->Size(), config.mpeg_timed_frames, delta_time.delta); - } else -#endif // HAVE_LIBAVCODEC - { + + static SystemTimePoint base_time; + if (!frame_count) { + base_time = timestamp; + } + SystemTimePoint::duration delta_time = timestamp - base_time; + + /* 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]; int img_buffer_size = 0; unsigned char *img_buffer = temp_img_buffer; // Calculate how long it takes to actually send the frame - struct timeval frameStartTime; - gettimeofday(&frameStartTime, nullptr); + TimePoint send_start_time = std::chrono::steady_clock::now(); switch ( type ) { case STREAM_JPEG : @@ -436,8 +441,8 @@ bool MonitorStream::sendFrame(Image *image, struct timeval *timestamp) { return false; } if ( - ( 0 > fprintf(stdout, "Content-Length: %d\r\nX-Timestamp: %d.%06d\r\n\r\n", - img_buffer_size, (int)timestamp->tv_sec, (int)timestamp->tv_usec) ) + (0 > fprintf(stdout, "Content-Length: %d\r\nX-Timestamp: %.6f\r\n\r\n", + img_buffer_size, std::chrono::duration_cast(timestamp.time_since_epoch()).count())) || (fwrite(img_buffer, img_buffer_size, 1, stdout) != 1) ) { @@ -450,27 +455,29 @@ bool MonitorStream::sendFrame(Image *image, struct timeval *timestamp) { fputs("\r\n", stdout); fflush(stdout); - struct timeval frameEndTime; - gettimeofday(&frameEndTime, nullptr); + TimePoint send_end_time = std::chrono::steady_clock::now(); + TimePoint::duration frame_send_time = send_end_time - send_start_time; - int frameSendTime = tvDiffMsec(frameStartTime, frameEndTime); - if ( frameSendTime > 1000/maxfps ) { + if (frame_send_time > Milliseconds(lround(Milliseconds::period::den / maxfps))) { maxfps /= 1.5; - Warning("Frame send time %d msec too slow, throttling maxfps to %.2f", - frameSendTime, maxfps); + Warning("Frame send time %" PRIi64 " msec too slow, throttling maxfps to %.2f", + static_cast(std::chrono::duration_cast(frame_send_time).count()), + maxfps); } } // Not mpeg - last_frame_sent = TV_2_FLOAT(now); + last_frame_sent = now; return true; -} // end bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp ) +} void MonitorStream::runStream() { if (type == STREAM_SINGLE) { // Not yet migrated over to stream class - if (checkInitialised()) + if (checkInitialised()) { SingleImage(scale); - else + } else { + fputs("Content-Type: multipart/x-mixed-replace; boundary=" BOUNDARY "\r\n\r\n", stdout); sendTextFrame("Unable to send image"); + } return; } @@ -503,8 +510,7 @@ 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; - time_t stream_start_time; - time(&stream_start_time); + SystemTimePoint stream_start_time = std::chrono::system_clock::now(); frame_count = 0; @@ -518,9 +524,9 @@ void MonitorStream::runStream() { // Last image and timestamp when paused, will be resent occasionally to prevent timeout Image *paused_image = nullptr; - struct timeval paused_timestamp; + SystemTimePoint paused_timestamp; - if ( connkey && ( playback_buffer > 0 ) ) { + if (connkey && (playback_buffer > 0)) { // 15 is the max length for the swap path suffix, /zmswap-whatever, assuming max 6 digits for monitor id const int max_swap_len_suffix = 15; @@ -529,45 +535,43 @@ void MonitorStream::runStream() { int subfolder2_length = snprintf(nullptr, 0, "/zmswap-q%06d", connkey) + 1; int total_swap_path_length = swap_path_length + subfolder1_length + subfolder2_length; - if ( total_swap_path_length + max_swap_len_suffix > PATH_MAX ) { + if (total_swap_path_length + max_swap_len_suffix > PATH_MAX) { Error("Swap Path is too long. %d > %d ", total_swap_path_length+max_swap_len_suffix, PATH_MAX); } else { swap_path = staticConfig.PATH_SWAP; Debug(3, "Checking swap path folder: %s", swap_path.c_str()); - if ( checkSwapPath(swap_path.c_str(), true) ) { + if (checkSwapPath(swap_path.c_str(), true)) { swap_path += stringtf("/zmswap-m%d", monitor->Id()); Debug(4, "Checking swap path subfolder: %s", swap_path.c_str()); - if ( checkSwapPath(swap_path.c_str(), true) ) { + if (checkSwapPath(swap_path.c_str(), true)) { swap_path += stringtf("/zmswap-q%06d", connkey); Debug(4, "Checking swap path subfolder: %s", swap_path.c_str()); - if ( checkSwapPath(swap_path.c_str(), true) ) { + if (checkSwapPath(swap_path.c_str(), true)) { buffered_playback = true; } } } - if ( !buffered_playback ) { + if (!buffered_playback) { Error("Unable to validate swap image path, disabling buffered playback"); } else { Debug(2, "Assigning temporary buffer"); temp_image_buffer = new SwapImage[temp_image_buffer_count]; - memset(temp_image_buffer, 0, sizeof(*temp_image_buffer)*temp_image_buffer_count); Debug(2, "Assigned temporary buffer"); } } } else { Debug(2, "Not using playback_buffer"); - } // end if connkey & playback_buffer + } // end if connkey && playback_buffer while (!zm_terminate) { - bool got_command = false; - if ( feof(stdout) ) { + if (feof(stdout)) { Debug(2, "feof stdout"); break; - } else if ( ferror(stdout) ) { + } else if (ferror(stdout)) { Debug(2, "ferror stdout"); break; } else if (!monitor->ShmValid()) { @@ -575,112 +579,106 @@ void MonitorStream::runStream() { break; } - gettimeofday(&now, nullptr); + now = std::chrono::system_clock::now(); bool was_paused = paused; - if ( connkey ) { - while ( checkCommandQueue() && !zm_terminate ) { + bool got_command = false; // commands like zoom should output a frame even if paused + if (connkey) { + while (checkCommandQueue() && !zm_terminate) { // Loop in here until all commands are processed. Debug(2, "Have checking command Queue for connkey: %d", connkey); got_command = true; } // Update modified time of the socket .lock file so that we can tell which ones are stale. - if ( now.tv_sec - last_comm_update.tv_sec > 3600 ) { + if (now - last_comm_update > Hours(1)) { touch(sock_path_lock); last_comm_update = now; } } // end if connkey - if ( paused ) { - if ( !was_paused ) { + if (paused) { + if (!was_paused) { int index = monitor->shared_data->last_write_index % monitor->image_buffer_count; Debug(1, "Saving paused image from index %d",index); - paused_image = new Image(*monitor->image_buffer[index].image); - paused_timestamp = *(monitor->image_buffer[index].timestamp); + paused_image = new Image(*monitor->image_buffer[index]); + paused_timestamp = SystemTimePoint(zm::chrono::duration_cast(monitor->shared_timestamps[index])); } - } else if ( paused_image ) { - Debug(1, "Clearing paused_image"); + } else if (paused_image) { delete paused_image; paused_image = nullptr; } - if ( buffered_playback && delayed ) { - if ( temp_read_index == temp_write_index ) { + if (buffered_playback && delayed) { + if (temp_read_index == temp_write_index) { // Go back to live viewing Debug(1, "Exceeded temporary streaming buffer"); - // Clear paused flag paused = false; - // Clear delayed_play flag delayed = false; replay_rate = ZM_RATE_BASE; } else { - if ( !paused ) { + if (!paused) { int temp_index = MOD_ADD(temp_read_index, 0, temp_image_buffer_count); - // Debug( 3, "tri: %d, ti: %d", temp_read_index, temp_index ); SwapImage *swap_image = &temp_image_buffer[temp_index]; - if ( !swap_image->valid ) { + if (!swap_image->valid) { paused = true; delayed = true; temp_read_index = MOD_ADD(temp_read_index, (replay_rate>=0?-1:1), temp_image_buffer_count); } else { - // Debug( 3, "siT: %f, lfT: %f", TV_2_FLOAT( swap_image->timestamp ), TV_2_FLOAT( last_frame_timestamp ) ); - double expected_delta_time = ((TV_2_FLOAT(swap_image->timestamp) - TV_2_FLOAT(last_frame_timestamp)) * ZM_RATE_BASE)/replay_rate; - double actual_delta_time = TV_2_FLOAT(now) - last_frame_sent; + 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; - // Debug( 3, "eDT: %.3lf, aDT: %.3f, lFS:%.3f, NOW:%.3f", expected_delta_time, actual_delta_time, last_frame_sent, TV_2_FLOAT( now ) ); // If the next frame is due - if ( actual_delta_time > expected_delta_time ) { + if (actual_delta_time > expected_delta_time) { // Debug( 2, "eDT: %.3lf, aDT: %.3f", expected_delta_time, actual_delta_time ); - if ( temp_index%frame_mod == 0 ) { + if ((temp_index % frame_mod) == 0) { Debug(2, "Sending delayed frame %d", temp_index); // Send the next frame - if ( ! sendFrame(temp_image_buffer[temp_index].file_name, &temp_image_buffer[temp_index].timestamp) ) { + if (!sendFrame(temp_image_buffer[temp_index].file_name, temp_image_buffer[temp_index].timestamp)) { zm_terminate = true; } - memcpy(&last_frame_timestamp, &(swap_image->timestamp), sizeof(last_frame_timestamp)); + frame_count++; + last_frame_timestamp = swap_image->timestamp; // frame_sent = true; } - temp_read_index = MOD_ADD(temp_read_index, (replay_rate>0?1:-1), temp_image_buffer_count); + temp_read_index = MOD_ADD(temp_read_index, (replay_rate > 0 ? 1 : -1), temp_image_buffer_count); } } - } else if ( step != 0 ) { + } else if (step != 0) { temp_read_index = MOD_ADD(temp_read_index, (step>0?1:-1), temp_image_buffer_count); SwapImage *swap_image = &temp_image_buffer[temp_read_index]; // Send the next frame - if ( !sendFrame( + if (!sendFrame( temp_image_buffer[temp_read_index].file_name, - &temp_image_buffer[temp_read_index].timestamp - ) ) { + temp_image_buffer[temp_read_index].timestamp) + ) { zm_terminate = true; } - memcpy( - &last_frame_timestamp, - &(swap_image->timestamp), - sizeof(last_frame_timestamp) - ); + frame_count++; + + last_frame_timestamp = swap_image->timestamp; // frame_sent = true; step = 0; } else { //paused? int temp_index = MOD_ADD(temp_read_index, 0, temp_image_buffer_count); - double actual_delta_time = TV_2_FLOAT(now) - last_frame_sent; - if ( got_command || (actual_delta_time > 5) ) { + if (got_command || (now - last_frame_sent > Seconds(5))) { // Send keepalive Debug(2, "Sending keepalive frame %d", temp_index); // Send the next frame - if ( !sendFrame(temp_image_buffer[temp_index].file_name, &temp_image_buffer[temp_index].timestamp) ) { + if (!sendFrame(temp_image_buffer[temp_index].file_name, temp_image_buffer[temp_index].timestamp)) { zm_terminate = true; } + frame_count++; // frame_sent = true; } } // end if (!paused) or step or paused } // end if have exceeded buffer or not - if ( temp_read_index == temp_write_index ) { + if (temp_read_index == temp_write_index) { // Go back to live viewing Warning("Rewound over write index, resuming live play"); // Clear paused flag @@ -689,33 +687,33 @@ void MonitorStream::runStream() { delayed = false; replay_rate = ZM_RATE_BASE; } - } // end if ( buffered_playback && delayed ) + } // end if (buffered_playback && delayed) - if ( last_read_index != monitor->shared_data->last_write_index ) { + if (last_read_index != monitor->shared_data->last_write_index) { // have a new image to send - int index = monitor->shared_data->last_write_index % monitor->image_buffer_count; // % shouldn't be neccessary - if ( (frame_mod == 1) || ((frame_count%frame_mod) == 0) ) { - if ( !paused && !delayed ) { + int index = monitor->shared_data->last_write_index % monitor->image_buffer_count; + if ((frame_mod == 1) || ((frame_count%frame_mod) == 0)) { + 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)", index, frame_mod, frame_count, paused, delayed); // Send the next frame // - ZMPacket *snap = &monitor->image_buffer[index]; + // Perhaps we should use NOW instead. + last_frame_timestamp = + SystemTimePoint(zm::chrono::duration_cast(monitor->shared_timestamps[index])); + Image *image = monitor->image_buffer[index]; - if ( !sendFrame(snap->image, snap->timestamp) ) { + if (!sendFrame(image, last_frame_timestamp)) { Debug(2, "sendFrame failed, quiting."); zm_terminate = true; break; } - // Perhaps we should use NOW instead. - last_frame_timestamp = *(snap->timestamp); - //frame_sent = true; - // - if ( frame_count == 0 ) { + frame_count++; + 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(snap->image, snap->timestamp) ) { + if (!sendFrame(image, last_frame_timestamp)) { Debug(2, "sendFrame failed, quiting."); zm_terminate = true; break; @@ -724,26 +722,29 @@ void MonitorStream::runStream() { temp_read_index = temp_write_index; } else { - if ( delayed && !buffered_playback ) { + if (delayed && !buffered_playback) { Debug(2, "Can't delay when not buffering."); delayed = false; } - if ( last_zoom != zoom ) { + if (last_zoom != zoom) { Debug(2, "Sending 2 frames because change in zoom %d ?= %d", last_zoom, zoom); - if ( !sendFrame(paused_image, &paused_timestamp) ) + if (!sendFrame(paused_image, paused_timestamp)) zm_terminate = true; - if ( !sendFrame(paused_image, &paused_timestamp) ) + if (!sendFrame(paused_image, paused_timestamp)) zm_terminate = true; + frame_count++; + frame_count++; } else { - double actual_delta_time = TV_2_FLOAT(now) - last_frame_sent; - if ( actual_delta_time > 5 ) { - if ( paused_image ) { + SystemTimePoint::duration actual_delta_time = now - last_frame_sent; + if (actual_delta_time > Seconds(5)) { + if (paused_image) { // Send keepalive - Debug(2, "Sending keepalive frame because delta time %.2f > 5", - actual_delta_time); + Debug(2, "Sending keepalive frame because delta time %.2f s > 5 s", + FPSeconds(actual_delta_time).count()); // Send the next frame - if ( !sendFrame(paused_image, &paused_timestamp) ) + if (!sendFrame(paused_image, paused_timestamp)) zm_terminate = true; + frame_count++; } else { Debug(2, "Would have sent keepalive frame, but had no paused_image"); } @@ -752,27 +753,21 @@ void MonitorStream::runStream() { } // end if paused or not } // end if should send frame - if ( buffered_playback && !paused ) { - if ( monitor->shared_data->valid ) { - if ( monitor->shared_timestamps[index].tv_sec ) { + if (buffered_playback && !paused) { + if (monitor->shared_data->valid) { + if (monitor->shared_timestamps[index].tv_sec) { int temp_index = temp_write_index%temp_image_buffer_count; Debug(2, "Storing frame %d", temp_index); if ( !temp_image_buffer[temp_index].valid ) { - snprintf( - temp_image_buffer[temp_index].file_name, - sizeof(temp_image_buffer[0].file_name), - "%s/zmswap-i%05d.jpg", - swap_path.c_str(), - temp_index); + temp_image_buffer[temp_index].file_name = stringtf("%s/zmswap-i%05d.jpg", swap_path.c_str(), temp_index); temp_image_buffer[temp_index].valid = true; } - temp_image_buffer[temp_index].timestamp = monitor->shared_timestamps[index]; - monitor->image_buffer[index].image->WriteJpeg( - temp_image_buffer[temp_index].file_name, - config.jpeg_file_quality - ); + + temp_image_buffer[temp_index].timestamp = + SystemTimePoint(zm::chrono::duration_cast(monitor->shared_timestamps[index])); + monitor->image_buffer[index]->WriteJpeg(temp_image_buffer[temp_index].file_name, config.jpeg_file_quality); temp_write_index = MOD_ADD(temp_write_index, 1, temp_image_buffer_count); - if ( temp_write_index == temp_read_index ) { + if (temp_write_index == temp_read_index) { // Go back to live viewing Warning("Exceeded temporary buffer, resuming live play"); paused = false; @@ -786,67 +781,67 @@ void MonitorStream::runStream() { Warning("Unable to store frame as shared memory invalid"); } } // end if buffered playback - frame_count++; } else { Debug(3, "Waiting for capture last_write_index=%u", monitor->shared_data->last_write_index); } // end if ( (unsigned int)last_read_index != monitor->shared_data->last_write_index ) - unsigned long sleep_time = (unsigned long)((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*abs(replay_rate*2))); - if ( sleep_time > MonitorStream::MAX_SLEEP_USEC ) { + FPSeconds sleep_time = + FPSeconds(ZM_RATE_BASE / ((base_fps ? base_fps : 1) * (replay_rate ? abs(replay_rate * 2) : 2))); + + if (sleep_time > MonitorStream::MAX_SLEEP) { // Shouldn't sleep for long because we need to check command queue, etc. - sleep_time = MonitorStream::MAX_SLEEP_USEC; - Debug(3, "Sleeping for MAX_SLEEP_USEC %luus", sleep_time); + sleep_time = MonitorStream::MAX_SLEEP; + Debug(3, "Sleeping for MAX_SLEEP_USEC %" PRIi64 " us", + static_cast(std::chrono::duration_cast(sleep_time).count())); } else { - Debug(3, "Sleeping for %luus", sleep_time); + Debug(3, "Sleeping for %" PRIi64 " us", + static_cast(std::chrono::duration_cast(sleep_time).count())); } - usleep(sleep_time); - if ( ttl ) { - if ( (now.tv_sec - stream_start_time) > ttl ) { - Debug(2, "now(%" PRIi64 ") - start(%" PRIi64 " ) > ttl(%" PRIi64 ") break", - static_cast(now.tv_sec), - static_cast(stream_start_time), - static_cast(ttl)); - break; - } + std::this_thread::sleep_for(sleep_time); + + if (ttl > Seconds(0) && (now - stream_start_time) > ttl) { + Debug(2, "now - start > ttl (%" PRIi64 " us). break", + static_cast(std::chrono::duration_cast(ttl).count())); + break; } - if ( !last_frame_sent ) { + + 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.tv_sec; + 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 + } // end while ! zm_terminate - if ( buffered_playback ) { + if (buffered_playback) { Debug(1, "Cleaning swap files from %s", swap_path.c_str()); - struct stat stat_buf; - if ( stat(swap_path.c_str(), &stat_buf) < 0 ) { - if ( errno != ENOENT ) { + struct stat stat_buf = {}; + if (stat(swap_path.c_str(), &stat_buf) < 0) { + if (errno != ENOENT) { Error("Can't stat '%s': %s", swap_path.c_str(), strerror(errno)); } - } else if ( !S_ISDIR(stat_buf.st_mode) ) { + } else if (!S_ISDIR(stat_buf.st_mode)) { Error("Swap image path '%s' is not a directory", swap_path.c_str()); } else { - char glob_pattern[PATH_MAX] = ""; - - snprintf(glob_pattern, sizeof(glob_pattern), "%s/*.*", swap_path.c_str()); + std::string glob_pattern = stringtf("%s/*.*", swap_path.c_str()); glob_t pglob; - int glob_status = glob(glob_pattern, 0, 0, &pglob); - if ( glob_status != 0 ) { - if ( glob_status < 0 ) { - Error("Can't glob '%s': %s", glob_pattern, strerror(errno)); + + int glob_status = glob(glob_pattern.c_str(), 0, 0, &pglob); + if (glob_status != 0) { + if (glob_status < 0) { + Error("Can't glob '%s': %s", glob_pattern.c_str(), strerror(errno)); } else { - Debug(1, "Can't glob '%s': %d", glob_pattern, glob_status); + Debug(1, "Can't glob '%s': %d", glob_pattern.c_str(), glob_status); } } else { - for ( unsigned int i = 0; i < pglob.gl_pathc; i++ ) { - if ( unlink(pglob.gl_pathv[i]) < 0 ) { + for (unsigned int i = 0; i < pglob.gl_pathc; i++) { + if (unlink(pglob.gl_pathv[i]) < 0) { Error("Can't unlink '%s': %s", pglob.gl_pathv[i], strerror(errno)); } } } globfree(&pglob); - if ( rmdir(swap_path.c_str()) < 0 ) { + if (rmdir(swap_path.c_str()) < 0) { Error("Can't rmdir '%s': %s", swap_path.c_str(), strerror(errno)); } } // end if checking for swap_path @@ -861,20 +856,20 @@ void MonitorStream::SingleImage(int scale) { Image scaled_image; while ((monitor->shared_data->last_write_index >= monitor->image_buffer_count) and !zm_terminate) { Debug(1, "Waiting for capture to begin"); - usleep(100000); + std::this_thread::sleep_for(Milliseconds(100)); } int index = monitor->shared_data->last_write_index % monitor->image_buffer_count; Debug(1, "write index: %d %d", monitor->shared_data->last_write_index, index); - ZMPacket *snap = &(monitor->image_buffer[index]); - Image *snap_image = snap->image; + Image *snap_image = monitor->image_buffer[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, snap->timestamp); + if (!config.timestamp_on_capture) { + monitor->TimestampImage(snap_image, + SystemTimePoint(zm::chrono::duration_cast(monitor->shared_timestamps[index]))); } snap_image->EncodeJpeg(img_buffer, &img_buffer_size); diff --git a/src/zm_monitorstream.h b/src/zm_monitorstream.h index 0c1f92777..04d2be25c 100644 --- a/src/zm_monitorstream.h +++ b/src/zm_monitorstream.h @@ -21,15 +21,14 @@ #define ZM_MONITORSTREAM_H #include "zm_stream.h" -#include class MonitorStream : public StreamBase { protected: - typedef struct SwapImage { - bool valid; - struct timeval timestamp; - char file_name[PATH_MAX]; - } SwapImage; + struct SwapImage { + bool valid = false; + SystemTimePoint timestamp; + std::string file_name; + }; private: SwapImage *temp_image_buffer; @@ -38,15 +37,14 @@ class MonitorStream : public StreamBase { int temp_write_index; protected: - time_t ttl; + Microseconds ttl; int playback_buffer; bool delayed; - int frame_count; protected: bool checkSwapPath(const char *path, bool create_path); - bool sendFrame(const char *filepath, struct timeval *timestamp); - bool sendFrame(Image *image, struct timeval *timestamp); + bool sendFrame(const std::string &filepath, SystemTimePoint timestamp); + bool sendFrame(Image *image, SystemTimePoint timestamp); void processCommand(const CmdMsg *msg) override; void SingleImage(int scale=100); void SingleImageRaw(int scale=100); @@ -55,21 +53,21 @@ class MonitorStream : public StreamBase { #endif public: - MonitorStream() : + MonitorStream() : temp_image_buffer(nullptr), temp_image_buffer_count(0), temp_read_index(0), temp_write_index(0), ttl(0), playback_buffer(0), - delayed(false), - frame_count(0) { - } + delayed(false) + {} + void setStreamBuffer(int p_playback_buffer) { playback_buffer = p_playback_buffer; } void setStreamTTL(time_t p_ttl) { - ttl = p_ttl; + ttl = Seconds(p_ttl); } bool setStreamStart(int monitor_id) { return loadMonitor(monitor_id); diff --git a/src/zm_mpeg.cpp b/src/zm_mpeg.cpp index 5ab1ad5dc..b8f7eeb66 100644 --- a/src/zm_mpeg.cpp +++ b/src/zm_mpeg.cpp @@ -21,14 +21,7 @@ #include "zm_logger.h" #include "zm_rgb.h" -#include -#include - -#if HAVE_LIBAVCODEC -extern "C" { -#include -#include -} +#include "zm_time.h" bool VideoStream::initialised = false; @@ -47,59 +40,8 @@ void VideoStream::Initialise( ) { void VideoStream::SetupFormat( ) { /* allocate the output media context */ ofc = nullptr; -#if (LIBAVFORMAT_VERSION_CHECK(53, 2, 0, 2, 0) && (LIBAVFORMAT_VERSION_MICRO >= 100)) avformat_alloc_output_context2(&ofc, nullptr, format, filename); -#else - AVFormatContext *s = avformat_alloc_context(); - if ( !s ) { - Fatal("avformat_alloc_context failed %d \"%s\"", (size_t)ofc, av_err2str((size_t)ofc)); - return; - } - AVOutputFormat *oformat; - if ( format ) { -#if LIBAVFORMAT_VERSION_CHECK(52, 45, 0, 45, 0) - oformat = av_guess_format(format, nullptr, nullptr); -#else - oformat = guess_format(format, nullptr, nullptr); -#endif - if ( !oformat ) { - Fatal("Requested output format '%s' is not a suitable output format", format); - } - } else { -#if LIBAVFORMAT_VERSION_CHECK(52, 45, 0, 45, 0) - oformat = av_guess_format(nullptr, filename, nullptr); -#else - oformat = guess_format(nullptr, filename, nullptr); -#endif - if ( !oformat ) { - Fatal("Unable to find a suitable output format for '%s'", format); - } - } - s->oformat = oformat; - - if ( s->oformat->priv_data_size > 0 ) { - s->priv_data = av_mallocz(s->oformat->priv_data_size); - if ( !(s->priv_data) ) { - Fatal("Could not allocate private data for output format."); - } -#if LIBAVFORMAT_VERSION_CHECK(52, 92, 0, 92, 0) - if ( s->oformat->priv_class ) { - *(const AVClass**)s->priv_data = s->oformat->priv_class; - av_opt_set_defaults(s->priv_data); - } -#endif - } else { - Debug(1, "No allocating priv_data"); - s->priv_data = nullptr; - } - - if ( filename ) { - snprintf(s->filename, sizeof(s->filename), "%s", filename); - } - - ofc = s; -#endif if ( !ofc ) { Fatal("avformat_alloc_..._context failed"); } @@ -162,11 +104,7 @@ void VideoStream::SetupCodec( int colours, int subpixelorder, int width, int hei codec_id = a->id; Debug(1, "Using codec \"%s\"", codec_name); } else { -#if (LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 11, 0) && (LIBAVFORMAT_VERSION_MICRO >= 100)) Debug(1, "Could not find codec \"%s\". Using default \"%s\"", codec_name, avcodec_get_name(codec_id)); -#else - Debug(1, "Could not find codec \"%s\". Using default \"%d\"", codec_name, codec_id); -#endif } } @@ -176,24 +114,12 @@ void VideoStream::SetupCodec( int colours, int subpixelorder, int width, int hei if ( codec_id != AV_CODEC_ID_NONE ) { codec = avcodec_find_encoder(codec_id); if ( !codec ) { -#if (LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 11, 0) && (LIBAVFORMAT_VERSION_MICRO >= 100)) Fatal("Could not find encoder for '%s'", avcodec_get_name(codec_id)); -#else - Fatal("Could not find encoder for '%d'", codec_id); -#endif } -#if (LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 11, 0) && (LIBAVFORMAT_VERSION_MICRO >= 100)) Debug(1, "Found encoder for '%s'", avcodec_get_name(codec_id)); -#else - Debug(1, "Found encoder for '%d'", codec_id); -#endif -#if LIBAVFORMAT_VERSION_CHECK(53, 10, 0, 17, 0) ost = avformat_new_stream( ofc, codec ); -#else - ost = av_new_stream( ofc, 0 ); -#endif if ( !ost ) { Fatal( "Could not alloc stream" ); @@ -202,13 +128,8 @@ void VideoStream::SetupCodec( int colours, int subpixelorder, int width, int hei Debug( 1, "Allocated stream (%d) !=? (%d)", ost->id , ofc->nb_streams - 1 ); ost->id = ofc->nb_streams - 1; -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - codec_context = avcodec_alloc_context3(nullptr); //avcodec_parameters_to_context(codec_context, ost->codecpar); -#else - codec_context = ost->codec; -#endif codec_context->codec_id = codec->id; codec_context->codec_type = codec->type; @@ -217,11 +138,7 @@ void VideoStream::SetupCodec( int colours, int subpixelorder, int width, int hei 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. -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) codec_context->flags |= AV_CODEC_FLAG_QSCALE; -#else - codec_context->flags |= CODEC_FLAG_QSCALE; -#endif codec_context->global_quality = FF_QP2LAMBDA * (31 - (31 * (bitrate / 100.0))); } else { codec_context->bit_rate = bitrate; @@ -247,15 +164,10 @@ void VideoStream::SetupCodec( int colours, int subpixelorder, int width, int hei // some formats want stream headers to be separate if ( of->flags & AVFMT_GLOBALHEADER ) -#if LIBAVCODEC_VERSION_CHECK(56, 35, 0, 64, 0) codec_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; -#else - codec_context->flags |= CODEC_FLAG_GLOBAL_HEADER; -#endif -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + avcodec_parameters_from_context(ost->codecpar, codec_context); zm_dump_codecpar(ost->codecpar); -#endif } else { Fatal( "of->video_codec == AV_CODEC_ID_NONE" ); } @@ -292,12 +204,8 @@ bool VideoStream::OpenStream( ) { Debug(1,"Opening codec"); /* open the codec */ -#if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0) - if ( (ret = avcodec_open(codec_context, codec)) < 0 ) -#else - if ( (ret = avcodec_open2(codec_context, codec, nullptr)) < 0 ) -#endif - { + + if ((ret = avcodec_open2(codec_context, codec, nullptr)) < 0) { Error("Could not open codec. Error code %d \"%s\"", ret, av_err2str(ret)); return false; } @@ -314,11 +222,7 @@ bool VideoStream::OpenStream( ) { opicture->height = codec_context->height; opicture->format = codec_context->pix_fmt; -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) int size = av_image_get_buffer_size(codec_context->pix_fmt, codec_context->width, codec_context->height, 1); -#else - int size = avpicture_get_size(codec_context->pix_fmt, codec_context->width, codec_context->height); -#endif uint8_t *opicture_buf = (uint8_t *)av_malloc(size); if ( !opicture_buf ) { @@ -326,59 +230,39 @@ bool VideoStream::OpenStream( ) { Error( "Could not allocate opicture_buf" ); return false; } -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) av_image_fill_arrays(opicture->data, opicture->linesize, opicture_buf, codec_context->pix_fmt, codec_context->width, codec_context->height, 1); -#else - avpicture_fill( (AVPicture *)opicture, opicture_buf, codec_context->pix_fmt, - codec_context->width, codec_context->height ); -#endif /* if the output format is not identical to the input format, then a temporary picture is needed too. It is then converted to the required output format */ tmp_opicture = nullptr; if ( codec_context->pix_fmt != pf ) { -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - tmp_opicture = av_frame_alloc( ); -#else - tmp_opicture = avcodec_alloc_frame( ); -#endif + tmp_opicture = av_frame_alloc(); + if ( !tmp_opicture ) { Error( "Could not allocate tmp_opicture" ); return false; } -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) int size = av_image_get_buffer_size( pf, codec_context->width, codec_context->height,1 ); -#else - int size = avpicture_get_size( pf, codec_context->width, codec_context->height ); -#endif + uint8_t *tmp_opicture_buf = (uint8_t *)av_malloc( size ); if ( !tmp_opicture_buf ) { av_frame_free( &tmp_opicture ); Error( "Could not allocate tmp_opicture_buf" ); return false; } -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) + av_image_fill_arrays(tmp_opicture->data, tmp_opicture->linesize, tmp_opicture_buf, pf, codec_context->width, codec_context->height, 1); -#else - avpicture_fill( (AVPicture *)tmp_opicture, - tmp_opicture_buf, pf, codec_context->width, codec_context->height ); -#endif } } // end if ost /* open the output file, if needed */ if ( !(of->flags & AVFMT_NOFILE) ) { -#if LIBAVFORMAT_VERSION_CHECK(53, 15, 0, 21, 0) ret = avio_open2( &ofc->pb, filename, AVIO_FLAG_WRITE, nullptr, nullptr ); -#elif LIBAVFORMAT_VERSION_CHECK(52, 102, 0, 102, 0) - ret = avio_open( &ofc->pb, filename, AVIO_FLAG_WRITE ); -#else - ret = url_fopen( &ofc->pb, filename, AVIO_FLAG_WRITE ); -#endif + if ( ret < 0 ) { Error("Could not open '%s'", filename); return false; @@ -391,12 +275,8 @@ bool VideoStream::OpenStream( ) { } video_outbuf = nullptr; -#if LIBAVFORMAT_VERSION_CHECK(57, 0, 0, 0, 0) if (codec_context->codec_type == AVMEDIA_TYPE_VIDEO && codec_context->codec_id == AV_CODEC_ID_RAWVIDEO) { -#else - if ( !(of->flags & AVFMT_RAWPICTURE) ) { -#endif /* allocate output buffer */ /* XXX: API change will be done */ // TODO: Make buffer dynamic. @@ -407,17 +287,9 @@ bool VideoStream::OpenStream( ) { } } -#if LIBAVFORMAT_VERSION_CHECK(52, 101, 0, 101, 0) av_dump_format(ofc, 0, filename, 1); -#else - dump_format(ofc, 0, filename, 1); -#endif -#if !LIBAVFORMAT_VERSION_CHECK(53, 2, 0, 4, 0) - ret = av_write_header(ofc); -#else ret = avformat_write_header(ofc, nullptr); -#endif if ( ret < 0 ) { Error("?_write_header failed with error %d \"%s\"", ret, av_err2str(ret)); @@ -532,11 +404,7 @@ VideoStream::~VideoStream( ) { if ( !(of->flags & AVFMT_NOFILE) ) { /* close the output file */ -#if LIBAVFORMAT_VERSION_CHECK(52, 105, 0, 105, 0) avio_close( ofc->pb ); -#else - url_fclose( ofc->pb ); -#endif } /* free the stream */ @@ -595,20 +463,14 @@ double VideoStream::EncodeFrame( const uint8_t *buffer, int buffer_size, bool _a double VideoStream::ActuallyEncodeFrame( const uint8_t *buffer, int buffer_size, bool add_timestamp, unsigned int timestamp ) { if ( codec_context->pix_fmt != pf ) { -#ifdef HAVE_LIBSWSCALE static struct SwsContext *img_convert_ctx = nullptr; -#endif // HAVE_LIBSWSCALE memcpy( tmp_opicture->data[0], buffer, buffer_size ); -#ifdef HAVE_LIBSWSCALE 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 ); if ( !img_convert_ctx ) Panic( "Unable to initialise image scaling context" ); } sws_scale( img_convert_ctx, tmp_opicture->data, tmp_opicture->linesize, 0, codec_context->height, opicture->data, opicture->linesize ); -#else // HAVE_LIBSWSCALE - Fatal( "swscale is required for MPEG mode" ); -#endif // HAVE_LIBSWSCALE } else { memcpy( opicture->data[0], buffer, buffer_size ); } @@ -616,28 +478,18 @@ double VideoStream::ActuallyEncodeFrame( const uint8_t *buffer, int buffer_size, AVPacket *pkt = packet_buffers[packet_index]; av_init_packet( pkt ); - int got_packet = 0; -#if LIBAVFORMAT_VERSION_CHECK(57, 0, 0, 0, 0) + int got_packet = 0; if (codec_context->codec_type == AVMEDIA_TYPE_VIDEO && codec_context->codec_id == AV_CODEC_ID_RAWVIDEO) { -#else - if ( of->flags & AVFMT_RAWPICTURE ) { -#endif - -#if LIBAVCODEC_VERSION_CHECK(52, 30, 2, 30, 2) pkt->flags |= AV_PKT_FLAG_KEY; -#else - pkt->flags |= PKT_FLAG_KEY; -#endif pkt->stream_index = ost->index; pkt->data = (uint8_t *)opicture_ptr; pkt->size = sizeof (AVPicture); - got_packet = 1; + got_packet = 1; } else { opicture_ptr->pts = codec_context->frame_number; opicture_ptr->quality = codec_context->global_quality; -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) avcodec_send_frame(codec_context, opicture_ptr); int ret = avcodec_receive_packet(codec_context, pkt); if ( ret < 0 ) { @@ -648,28 +500,11 @@ double VideoStream::ActuallyEncodeFrame( const uint8_t *buffer, int buffer_size, } else { got_packet = 1; } -#else -#if LIBAVFORMAT_VERSION_CHECK(54, 1, 0, 2, 100) - int ret = avcodec_encode_video2( codec_context, pkt, opicture_ptr, &got_packet ); - if ( ret != 0 ) { - Fatal( "avcodec_encode_video2 failed with errorcode %d \"%s\"", ret, av_err2str( ret ) ); - } -#else - int out_size = avcodec_encode_video( codec_context, video_outbuf, video_outbuf_size, opicture_ptr ); - got_packet = out_size > 0 ? 1 : 0; - pkt->data = got_packet ? video_outbuf : nullptr; - pkt->size = got_packet ? out_size : 0; -#endif -#endif if ( got_packet ) { // if ( c->coded_frame->key_frame ) // { -//#if LIBAVCODEC_VERSION_CHECK(52, 30, 2, 30, 2) // pkt->flags |= AV_PKT_FLAG_KEY; -//#else -// pkt->flags |= PKT_FLAG_KEY; -//#endif // } if ( pkt->pts != (int64_t)AV_NOPTS_VALUE ) { @@ -692,43 +527,33 @@ int VideoStream::SendPacket(AVPacket *packet) { if ( ret != 0 ) { Fatal( "Error %d while writing video frame: %s", ret, av_err2str( errno ) ); } -#if LIBAVCODEC_VERSION_CHECK(57, 8, 0, 12, 100) av_packet_unref( packet ); -#else - av_free_packet( packet ); -#endif return ret; } -void *VideoStream::StreamingThreadCallback(void *ctx){ - - Debug( 1, "StreamingThreadCallback started" ); - - if (ctx == nullptr) return nullptr; +void *VideoStream::StreamingThreadCallback(void *ctx) { + Debug(1, "StreamingThreadCallback started"); - VideoStream* videoStream = reinterpret_cast(ctx); + if (ctx == nullptr) { + return nullptr; + } - const uint64_t nanosecond_multiplier = 1000000000; + VideoStream *videoStream = reinterpret_cast(ctx); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - uint64_t target_interval_ns = nanosecond_multiplier * ( ((double)videoStream->codec_context->time_base.num) / (videoStream->codec_context->time_base.den) ); -#else - uint64_t target_interval_ns = nanosecond_multiplier * ( ((double)videoStream->codec_context->time_base.num) / (videoStream->codec_context->time_base.den) ); -#endif - uint64_t frame_count = 0; - timespec start_time; - clock_gettime(CLOCK_MONOTONIC, &start_time); - uint64_t start_time_ns = (start_time.tv_sec*nanosecond_multiplier) + start_time.tv_nsec; - while(videoStream->do_streaming) { - timespec current_time; - clock_gettime(CLOCK_MONOTONIC, ¤t_time); - uint64_t current_time_ns = (current_time.tv_sec*nanosecond_multiplier) + current_time.tv_nsec; - uint64_t target_ns = start_time_ns + (target_interval_ns * frame_count); - - if ( current_time_ns < target_ns ) { - // It's not time to render a frame yet. - usleep( (target_ns - current_time_ns) * 0.001 ); - } + TimePoint::duration target_interval = std::chrono::duration_cast(FPSeconds( + videoStream->codec_context->time_base.num / static_cast(videoStream->codec_context->time_base.den))); + + uint64_t frame_count = 0; + TimePoint start_time = std::chrono::steady_clock::now(); + + while (videoStream->do_streaming) { + TimePoint current_time = std::chrono::steady_clock::now(); + TimePoint target = start_time + (target_interval * frame_count); + + if (current_time < target) { + // It's not time to render a frame yet. + std::this_thread::sleep_for(target - current_time); + } // By sending the last rendered frame we deliver frames to the client more accurate. // If we're encoding the frame before sending it there will be lag. @@ -739,32 +564,29 @@ void *VideoStream::StreamingThreadCallback(void *ctx){ if (packet->size) { videoStream->SendPacket(packet); } -#if LIBAVCODEC_VERSION_CHECK(57, 8, 0, 12, 100) - av_packet_unref( packet); -#else - av_free_packet( packet ); -#endif + av_packet_unref(packet); + videoStream->packet_index = videoStream->packet_index ? 0 : 1; // Lock buffer and render next frame. - - if ( pthread_mutex_lock( videoStream->buffer_copy_lock ) != 0 ) { - Fatal( "StreamingThreadCallback: pthread_mutex_lock failed." ); - } - - if ( videoStream->buffer_copy ) { - // Encode next frame. - videoStream->ActuallyEncodeFrame( videoStream->buffer_copy, videoStream->buffer_copy_used, videoStream->add_timestamp, videoStream->timestamp ); - } - - if ( pthread_mutex_unlock( videoStream->buffer_copy_lock ) != 0 ) { - Fatal( "StreamingThreadCallback: pthread_mutex_unlock failed." ); - } - - frame_count++; - } - - return nullptr; -} + if (pthread_mutex_lock(videoStream->buffer_copy_lock) != 0) { + Fatal("StreamingThreadCallback: pthread_mutex_lock failed."); + } -#endif // HAVE_LIBAVCODEC + if (videoStream->buffer_copy) { + // Encode next frame. + videoStream->ActuallyEncodeFrame(videoStream->buffer_copy, + videoStream->buffer_copy_used, + videoStream->add_timestamp, + videoStream->timestamp); + } + + if (pthread_mutex_unlock(videoStream->buffer_copy_lock) != 0) { + Fatal("StreamingThreadCallback: pthread_mutex_unlock failed."); + } + + frame_count++; + } + + return nullptr; +} diff --git a/src/zm_mpeg.h b/src/zm_mpeg.h index 4999f1328..b6a6f49be 100644 --- a/src/zm_mpeg.h +++ b/src/zm_mpeg.h @@ -23,8 +23,6 @@ #include "zm_ffmpeg.h" #include -#if HAVE_LIBAVCODEC - class VideoStream { protected: struct MimeData { @@ -83,6 +81,4 @@ public: double EncodeFrame( const uint8_t *buffer, int buffer_size, bool add_timestamp=false, unsigned int timestamp=0 ); }; -#endif // HAVE_LIBAVCODEC - #endif // ZM_MPEG_H diff --git a/src/zm_packet.cpp b/src/zm_packet.cpp index 7aef2ddca..3361667e0 100644 --- a/src/zm_packet.cpp +++ b/src/zm_packet.cpp @@ -21,7 +21,6 @@ #include "zm_ffmpeg.h" #include "zm_image.h" #include "zm_logger.h" -#include using namespace std; AVPixelFormat target_format = AV_PIX_FMT_NONE; @@ -31,7 +30,6 @@ ZMPacket::ZMPacket() : stream(nullptr), in_frame(nullptr), out_frame(nullptr), - timestamp(nullptr), buffer(nullptr), image(nullptr), analysis_image(nullptr), @@ -40,7 +38,27 @@ ZMPacket::ZMPacket() : image_index(-1), codec_imgsize(0), pts(0), - decoded(0) + decoded(false) +{ + av_init_packet(&packet); + packet.size = 0; // So we can detect whether it has been filled. +} + +ZMPacket::ZMPacket(Image *i, SystemTimePoint tv) : + keyframe(0), + stream(nullptr), + in_frame(nullptr), + out_frame(nullptr), + timestamp(tv), + buffer(nullptr), + image(i), + analysis_image(nullptr), + score(-1), + codec_type(AVMEDIA_TYPE_UNKNOWN), + image_index(-1), + codec_imgsize(0), + pts(0), + decoded(false) { av_init_packet(&packet); packet.size = 0; // So we can detect whether it has been filled. @@ -51,7 +69,7 @@ ZMPacket::ZMPacket(ZMPacket &p) : stream(nullptr), in_frame(nullptr), out_frame(nullptr), - timestamp(nullptr), + timestamp(p.timestamp), buffer(nullptr), image(nullptr), analysis_image(nullptr), @@ -60,7 +78,7 @@ ZMPacket::ZMPacket(ZMPacket &p) : image_index(-1), codec_imgsize(0), pts(0), - decoded(0) + decoded(false) { av_init_packet(&packet); packet.size = 0; @@ -68,18 +86,15 @@ ZMPacket::ZMPacket(ZMPacket &p) : if ( zm_av_packet_ref(&packet, &p.packet) < 0 ) { Error("error refing packet"); } - timestamp = new struct timeval; - *timestamp = *p.timestamp; } ZMPacket::~ZMPacket() { zm_av_packet_unref(&packet); - if ( in_frame ) av_frame_free(&in_frame); - if ( out_frame ) av_frame_free(&out_frame); - if ( buffer ) av_freep(&buffer); - if ( analysis_image ) delete analysis_image; - if ( image ) delete image; - if ( timestamp ) delete timestamp; + if (in_frame) av_frame_free(&in_frame); + if (out_frame) av_frame_free(&out_frame); + if (buffer) av_freep(&buffer); + delete analysis_image; + delete image; } /* returns < 0 on error, 0 on not ready, int bytes consumed on success @@ -90,7 +105,7 @@ ZMPacket::~ZMPacket() { int ZMPacket::decode(AVCodecContext *ctx) { Debug(4, "about to decode video, image_index is (%d)", image_index); - if ( in_frame ) { + if (in_frame) { Error("Already have a frame?"); } else { in_frame = zm_av_frame_alloc(); @@ -100,8 +115,8 @@ int ZMPacket::decode(AVCodecContext *ctx) { //av_packet_rescale_ts(&packet, AV_TIME_BASE_Q, ctx->time_base); int ret = zm_send_packet_receive_frame(ctx, in_frame, packet); - if ( ret < 0 ) { - if ( AVERROR(EAGAIN) != ret ) { + if (ret < 0) { + if (AVERROR(EAGAIN) != ret) { Warning("Unable to receive frame : code %d %s.", ret, av_make_error_string(ret).c_str()); } @@ -109,14 +124,14 @@ int ZMPacket::decode(AVCodecContext *ctx) { return 0; } int bytes_consumed = ret; - if ( ret > 0 ) { + if (ret > 0) { zm_dump_video_frame(in_frame, "got frame"); #if HAVE_LIBAVUTIL_HWCONTEXT_H #if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0) - if ( fix_deprecated_pix_fmt(ctx->sw_pix_fmt) != fix_deprecated_pix_fmt(static_cast(in_frame->format)) ) { - Debug(1, "Have different format ctx->pix_fmt %s ?= ctx->sw_pix_fmt %s in_frame->format %s.", + if (fix_deprecated_pix_fmt(ctx->sw_pix_fmt) != fix_deprecated_pix_fmt(static_cast(in_frame->format))) { + Debug(3, "Have different format ctx->pix_fmt %s ?= ctx->sw_pix_fmt %s in_frame->format %s.", av_get_pix_fmt_name(ctx->pix_fmt), av_get_pix_fmt_name(ctx->sw_pix_fmt), av_get_pix_fmt_name(static_cast(in_frame->format)) @@ -163,7 +178,7 @@ int ZMPacket::decode(AVCodecContext *ctx) { /* retrieve data from GPU to CPU */ zm_dump_video_frame(in_frame, "Before hwtransfer"); ret = av_hwframe_transfer_data(new_frame, in_frame, 0); - if ( ret < 0 ) { + if (ret < 0) { Error("Unable to transfer frame: %s, continuing", av_make_error_string(ret).c_str()); av_frame_free(&in_frame); @@ -171,7 +186,7 @@ int ZMPacket::decode(AVCodecContext *ctx) { return 0; } ret = av_frame_copy_props(new_frame, in_frame); - if ( ret < 0 ) { + if (ret < 0) { Error("Unable to copy props: %s, continuing", av_make_error_string(ret).c_str()); } @@ -188,7 +203,7 @@ int ZMPacket::decode(AVCodecContext *ctx) { } else #endif #endif - Debug(2, "Same pix format %s so not hwtransferring. sw_pix_fmt is %s", + Debug(3, "Same pix format %s so not hwtransferring. sw_pix_fmt is %s", av_get_pix_fmt_name(ctx->pix_fmt), av_get_pix_fmt_name(ctx->sw_pix_fmt) ); @@ -202,12 +217,12 @@ int ZMPacket::decode(AVCodecContext *ctx) { } // end ZMPacket::decode Image *ZMPacket::get_image(Image *i) { - if ( !in_frame ) { + if (!in_frame) { Error("Can't get image without frame.. maybe need to decode first"); return nullptr; } - if ( !image ) { - if ( !i ) { + if (!image) { + if (!i) { Error("Need a pre-allocated image buffer"); return nullptr; } @@ -223,11 +238,11 @@ Image *ZMPacket::set_image(Image *i) { } AVPacket *ZMPacket::set_packet(AVPacket *p) { - if ( zm_av_packet_ref(&packet, p) < 0 ) { + if (zm_av_packet_ref(&packet, p) < 0) { Error("error refing packet"); } - //ZM_DUMP_PACKET(packet, "zmpacket:"); - gettimeofday(timestamp, nullptr); + + timestamp = std::chrono::system_clock::now(); keyframe = p->flags & AV_PKT_FLAG_KEY; return &packet; } @@ -240,10 +255,11 @@ AVFrame *ZMPacket::get_out_frame(int width, int height, AVPixelFormat format) { return nullptr; } -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) + int alignment = 32; + if (width%alignment) alignment = 1; codec_imgsize = av_image_get_buffer_size( - format, width, height, 32); + format, width, height, alignment); Debug(1, "buffer size %u from %s %dx%d", codec_imgsize, av_get_pix_fmt_name(format), width, height); buffer = (uint8_t *)av_malloc(codec_imgsize); int ret; @@ -254,25 +270,12 @@ AVFrame *ZMPacket::get_out_frame(int width, int height, AVPixelFormat format) { format, width, height, - 32))<0) { + alignment))<0) { Error("Failed to fill_arrays %s", av_make_error_string(ret).c_str()); av_frame_free(&out_frame); return nullptr; } -#else - codec_imgsize = avpicture_get_size( - format, - width, - >height); - buffer = (uint8_t *)av_malloc(codec_imgsize); - avpicture_fill( - (AVPicture *)out_frame, - buffer, - format, - width, - height - ); -#endif + out_frame->width = width; out_frame->height = height; out_frame->format = format; diff --git a/src/zm_packet.h b/src/zm_packet.h index a7deca325..66a6250ca 100644 --- a/src/zm_packet.h +++ b/src/zm_packet.h @@ -21,6 +21,7 @@ #define ZM_PACKET_H #include "zm_logger.h" +#include "zm_time.h" #include "zm_zone.h" #include @@ -31,16 +32,13 @@ extern "C" { #include } -#ifdef __FreeBSD__ -#include -#endif // __FreeBSD__ - class Image; class ZMPacket { public: std::mutex mutex_; + // The condition has to be in the packet because it is shared between locks std::condition_variable condition_; int keyframe; @@ -48,7 +46,7 @@ class ZMPacket { AVPacket packet; // Input packet, undecoded AVFrame *in_frame; // Input image, decoded Theoretically only filled if needed. AVFrame *out_frame; // output image, Only filled if needed. - struct timeval *timestamp; + SystemTimePoint timestamp; uint8_t *buffer; // buffer used in image Image *image; Image *analysis_image; @@ -69,7 +67,7 @@ class ZMPacket { int is_keyframe() { return keyframe; }; int decode( AVCodecContext *ctx ); - explicit ZMPacket(Image *image); + explicit ZMPacket(Image *image, SystemTimePoint tv); explicit ZMPacket(ZMPacket &packet); ZMPacket(); ~ZMPacket(); @@ -81,11 +79,11 @@ class ZMPacket { class ZMLockedPacket { public: - ZMPacket *packet_; + std::shared_ptr packet_; std::unique_lock lck_; bool locked; - explicit ZMLockedPacket(ZMPacket *p) : + explicit ZMLockedPacket(std::shared_ptr p) : packet_(p), lck_(packet_->mutex_, std::defer_lock), locked(false) { diff --git a/src/zm_packetqueue.cpp b/src/zm_packetqueue.cpp index a1f149bb4..67ba35c0f 100644 --- a/src/zm_packetqueue.cpp +++ b/src/zm_packetqueue.cpp @@ -24,7 +24,6 @@ #include "zm_ffmpeg.h" #include "zm_packet.h" #include "zm_signal.h" -#include PacketQueue::PacketQueue(): video_stream_id(-1), @@ -75,8 +74,7 @@ PacketQueue::~PacketQueue() { * Thus it will ensure that the same packet never gets queued twice. */ -bool PacketQueue::queuePacket(ZMPacket* add_packet) { - Debug(4, "packetqueue queuepacket %p %d", add_packet, add_packet->image_index); +bool PacketQueue::queuePacket(std::shared_ptr add_packet) { if (iterators.empty()) { Debug(4, "No iterators so no one needs us to queue packets."); return false; @@ -97,7 +95,7 @@ bool PacketQueue::queuePacket(ZMPacket* add_packet) { if (add_packet->keyframe) { // Have a new keyframe, so delete everything while ((*pktQueue.begin() != add_packet) and (packet_counts[video_stream_id] > max_video_packet_count)) { - ZMPacket *zm_packet = *pktQueue.begin(); + std::shared_ptr zm_packet = *pktQueue.begin(); ZMLockedPacket *lp = new ZMLockedPacket(zm_packet); if (!lp->trylock()) { Debug(1, "Found locked packet when trying to free up video packets. Can't continue"); @@ -129,13 +127,16 @@ bool PacketQueue::queuePacket(ZMPacket* add_packet) { packet_counts[video_stream_id], max_video_packet_count, pktQueue.size()); - delete zm_packet; } // end while } } // end if too many video packets - if ((max_video_packet_count > 0) and (packet_counts[video_stream_id] > max_video_packet_count)) { - Error("Unable to free up older packets. Not queueing this video packet."); - return false; + if (max_video_packet_count > 0) { + while (packet_counts[video_stream_id] > max_video_packet_count) { + Error("Unable to free up older packets. Waiting."); + condition.wait(lck); + if (deleting or zm_terminate) + return false; + } } } // end if this packet is a video packet @@ -166,7 +167,7 @@ bool PacketQueue::queuePacket(ZMPacket* add_packet) { return true; } // end bool PacketQueue::queuePacket(ZMPacket* zm_packet) -void PacketQueue::clearPackets(ZMPacket *add_packet) { +void PacketQueue::clearPackets(const std::shared_ptr &add_packet) { // Only do queueCleaning if we are adding a video keyframe, so that we guarantee that there is one. // No good. Have to satisfy two conditions: // 1. packetqueue starts with a video keyframe @@ -176,6 +177,9 @@ void PacketQueue::clearPackets(ZMPacket *add_packet) { // // So start at the beginning, counting video packets until the next keyframe. // Then if deleting those packets doesn't break 1 and 2, then go ahead and delete them. + if (deleting) return; + if (!pktQueue.size()) return; + if (keep_keyframes and ! ( add_packet->packet.stream_index == video_stream_id and @@ -193,7 +197,6 @@ void PacketQueue::clearPackets(ZMPacket *add_packet) { return; } std::unique_lock lck(mutex); - if (!pktQueue.size()) return; // If analysis_it isn't at the end, we need to keep that many additional packets int tail_count = 0; @@ -211,7 +214,7 @@ void PacketQueue::clearPackets(ZMPacket *add_packet) { if (!keep_keyframes) { // If not doing passthrough, we don't care about starting with a keyframe so logic is simpler while ((*pktQueue.begin() != add_packet) and (packet_counts[video_stream_id] > pre_event_video_packet_count + tail_count)) { - ZMPacket *zm_packet = *pktQueue.begin(); + std::shared_ptr zm_packet = *pktQueue.begin(); ZMLockedPacket *lp = new ZMLockedPacket(zm_packet); if (!lp->trylock()) break; delete lp; @@ -231,20 +234,24 @@ void PacketQueue::clearPackets(ZMPacket *add_packet) { packet_counts[video_stream_id], pre_event_video_packet_count, pktQueue.size()); - delete zm_packet; + //delete zm_packet; } // end while return; } packetqueue_iterator it = pktQueue.begin(); packetqueue_iterator next_front = pktQueue.begin(); - int video_packets_to_delete = 0; // This is a count of how many packets we will delete so we know when to stop looking // First packet is special because we know it is a video keyframe and only need to check for lock - ZMPacket *zm_packet = *it; + std::shared_ptr zm_packet = *it; + if (zm_packet == add_packet) { + return; + } + Debug(1, "trying lock on first packet"); ZMLockedPacket *lp = new ZMLockedPacket(zm_packet); if (lp->trylock()) { + int video_packets_to_delete = 0; // This is a count of how many packets we will delete so we know when to stop looking Debug(1, "Have lock on first packet"); ++it; delete lp; @@ -280,7 +287,7 @@ void PacketQueue::clearPackets(ZMPacket *add_packet) { break; } } - it++; + ++it; } // end while } } // end if first packet not locked @@ -288,10 +295,10 @@ void PacketQueue::clearPackets(ZMPacket *add_packet) { ( *it == add_packet ), ( next_front == pktQueue.begin() ) ); - if ( next_front != pktQueue.begin() ) { - while ( pktQueue.begin() != next_front ) { - ZMPacket *zm_packet = *pktQueue.begin(); - if ( !zm_packet ) { + if (next_front != pktQueue.begin()) { + while (pktQueue.begin() != next_front) { + zm_packet = *pktQueue.begin(); + if (!zm_packet) { Error("NULL zm_packet in queue"); continue; } @@ -306,7 +313,7 @@ void PacketQueue::clearPackets(ZMPacket *add_packet) { pktQueue.size()); pktQueue.pop_front(); packet_counts[zm_packet->packet.stream_index] -= 1; - delete zm_packet; + //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 @@ -314,131 +321,32 @@ void PacketQueue::clearPackets(ZMPacket *add_packet) { return; } // end voidPacketQueue::clearPackets(ZMPacket* zm_packet) -ZMLockedPacket* PacketQueue::popPacket( ) { - Debug(4, "pktQueue size %zu", pktQueue.size()); - if ( pktQueue.empty() ) { - return nullptr; - } - Debug(4, "poPacket Mutex locking"); - std::unique_lock lck(mutex); - - ZMPacket *zm_packet = pktQueue.front(); - for ( - std::list::iterator iterators_it = iterators.begin(); - iterators_it != iterators.end(); - ++iterators_it - ) { - packetqueue_iterator *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) == zm_packet ) { - Debug(4, "Bumping it because it is at the front that we are deleting"); - ++(*iterators_it); - } - } // end foreach iterator - - ZMLockedPacket *lp = new ZMLockedPacket (zm_packet); - lp->lock(); - - pktQueue.pop_front(); - packet_counts[zm_packet->packet.stream_index] -= 1; - - return lp; -} // popPacket - - -/* Keeps frames_to_keep frames of the provided stream, which theoretically is the video stream - * Basically it starts at the end, moving backwards until it finds the minimum video frame. - * Then it should probably move forward to find a keyframe. The first video frame must always be a keyframe. - * So really frames_to_keep is a maximum which isn't so awesome.. maybe we should go back farther to find the keyframe in which case - * frames_to_keep in a minimum - */ - -unsigned int PacketQueue::clear(unsigned int frames_to_keep, int stream_id) { - Debug(3, "Clearing all but %d frames, queue has %zu", frames_to_keep, pktQueue.size()); - - if ( pktQueue.empty() ) { - return 0; - } - - // If size is <= frames_to_keep since it could contain audio, we can't possibly do anything - if ( pktQueue.size() <= frames_to_keep ) { - return 0; - } - Debug(5, "Locking in clear"); - std::unique_lock lck(mutex); - - packetqueue_iterator it = pktQueue.end()--; // point to last element instead of end - ZMPacket *zm_packet = nullptr; - - while ( (it != pktQueue.begin()) and frames_to_keep ) { - zm_packet = *it; - AVPacket *av_packet = &(zm_packet->packet); - - Debug(3, "Looking at packet with stream index (%d) with keyframe(%d), Image_index(%d) frames_to_keep is (%d)", - av_packet->stream_index, zm_packet->keyframe, zm_packet->image_index, frames_to_keep ); - - // Want frames_to_keep video keyframes. Otherwise, we may not have enough - if ( av_packet->stream_index == stream_id ) { - frames_to_keep --; - } - it --; - } - - // Either at beginning or frames_to_keep == 0 - - if ( it == pktQueue.begin() ) { - if ( frames_to_keep ) { - Warning("Couldn't remove any packets, needed %d", frames_to_keep); - } - mutex.unlock(); - return 0; - } - - int delete_count = 0; - - // Else not at beginning, are pointing at packet before the last video packet - while ( pktQueue.begin() != it ) { - Debug(4, "Deleting a packet from the front, count is (%d), queue size is %zu", - delete_count, pktQueue.size()); - zm_packet = pktQueue.front(); - for ( - std::list::iterator iterators_it = iterators.begin(); - iterators_it != iterators.end(); - ++iterators_it - ) { - packetqueue_iterator *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) == zm_packet ) { - Debug(4, "Bumping it because it is at the front that we are deleting"); - ++(*iterators_it); - } - } // end foreach iterator - packet_counts[zm_packet->packet.stream_index] --; - pktQueue.pop_front(); - //if ( zm_packet->image_index == -1 ) - delete zm_packet; - - delete_count += 1; - } // while our iterator is not the first packet - Debug(3, "Deleted %d packets, %zu remaining", delete_count, pktQueue.size()); - return delete_count; -} // end unsigned int PacketQueue::clear( unsigned int frames_to_keep, int stream_id ) - void PacketQueue::clear() { deleting = true; condition.notify_all(); + if (!packet_counts) // special case, not initialised + return; Debug(1, "Clearing packetqueue"); std::unique_lock lck(mutex); while (!pktQueue.empty()) { - ZMPacket *packet = pktQueue.front(); + std::shared_ptr packet = pktQueue.front(); // Someone might have this packet, but not for very long and since we have locked the queue they won't be able to get another one ZMLockedPacket *lp = new ZMLockedPacket(packet); lp->lock(); + Debug(1, + "Deleting a packet with stream index:%d image_index:%d with keyframe:%d, video frames in queue:%d max: %d, queuesize:%zu", + packet->packet.stream_index, + packet->image_index, + packet->keyframe, + packet_counts[video_stream_id], + pre_event_video_packet_count, + pktQueue.size()); pktQueue.pop_front(); delete lp; - delete packet; + //delete packet; } + Debug(1, "Packetqueue is clear, deleting iterators"); for ( std::list::iterator iterators_it = iterators.begin(); @@ -449,163 +357,119 @@ void PacketQueue::clear() { *iterator_it = pktQueue.begin(); } // end foreach iterator - if ( packet_counts ) delete[] packet_counts; + if (packet_counts) delete[] packet_counts; packet_counts = nullptr; max_stream_id = -1; + Debug(1, "Packetqueue is clear, notifying"); condition.notify_all(); } -// clear queue keeping only specified duration of video -- return number of pkts removed -unsigned int PacketQueue::clear(struct timeval *duration, int streamId) { - - if ( pktQueue.empty() ) { - return 0; - } - Debug(4, "Locking in clear"); - std::unique_lock lck(mutex); - - struct timeval keep_from; - std::list::reverse_iterator it = pktQueue.rbegin(); - - struct timeval *t = (*it)->timestamp; - timersub(t, duration, &keep_from); - ++it; - - Debug(3, "Looking for frame before queue keep time with stream id (%d), queue has %zu packets", - streamId, pktQueue.size()); - for ( ; it != pktQueue.rend(); ++it) { - ZMPacket *zm_packet = *it; - AVPacket *av_packet = &(zm_packet->packet); - if ( - (av_packet->stream_index == streamId) - and - timercmp(zm_packet->timestamp, &keep_from, <=) - ) { - Debug(3, "Found frame before keep time with stream index %d at %" PRIi64 ".%" PRIi64, - av_packet->stream_index, - static_cast(zm_packet->timestamp->tv_sec), - static_cast(zm_packet->timestamp->tv_usec)); - break; - } - } - - if ( it == pktQueue.rend() ) { - Debug(1, "Didn't find a frame before queue preserve time. keeping all"); - mutex.unlock(); - return 0; - } - - Debug(3, "Looking for keyframe"); - for ( ; it != pktQueue.rend(); ++it) { - ZMPacket *zm_packet = *it; - AVPacket *av_packet = &(zm_packet->packet); - if ( - (av_packet->flags & AV_PKT_FLAG_KEY) - and - (av_packet->stream_index == streamId) - ) { - Debug(3, "Found keyframe before start with stream index %d at %" PRIi64 ".%" PRIi64, - av_packet->stream_index, - static_cast(zm_packet->timestamp->tv_sec), - static_cast(zm_packet->timestamp->tv_usec)); - break; - } - } - if ( it == pktQueue.rend() ) { - Debug(1, "Didn't find a keyframe before event starttime. keeping all" ); - return 0; - } - - unsigned int deleted_frames = 0; - ZMPacket *zm_packet = nullptr; - while ( distance(it, pktQueue.rend()) > 1 ) { - zm_packet = pktQueue.front(); - for ( - std::list::iterator iterators_it = iterators.begin(); - iterators_it != iterators.end(); - ++iterators_it - ) { - packetqueue_iterator *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) == zm_packet ) { - Debug(4, "Bumping it because it is at the front that we are deleting"); - ++(*iterators_it); - } - } // end foreach iterator - pktQueue.pop_front(); - packet_counts[zm_packet->packet.stream_index] -= 1; - delete zm_packet; - deleted_frames += 1; - } - Debug(3, "Deleted %d frames", deleted_frames); - return deleted_frames; -} - unsigned int PacketQueue::size() { return pktQueue.size(); } int PacketQueue::packet_count(int stream_id) { - if ( stream_id < 0 or stream_id > max_stream_id ) { + if (stream_id < 0 or stream_id > max_stream_id) { Error("Invalid stream_id %d max is %d", stream_id, max_stream_id); return -1; } return packet_counts[stream_id]; } // end int PacketQueue::packet_count(int stream_id) - // Returns a packet. Packet will be locked ZMLockedPacket *PacketQueue::get_packet(packetqueue_iterator *it) { if (deleting or zm_terminate) return nullptr; - Debug(4, "Locking in get_packet using it %p queue end? %d, packet %p", - std::addressof(*it), (*it == pktQueue.end()), *(*it)); - std::unique_lock lck(mutex); - Debug(4, "Have Lock in get_packet"); + Debug(4, "Locking in get_packet using it %p queue end? %d", + std::addressof(*it), (*it == pktQueue.end())); ZMLockedPacket *lp = nullptr; - while (!lp) { - while (*it == pktQueue.end()) { - if (deleting or zm_terminate) { - Debug(1, "terminated, leaving"); - condition.notify_all(); + { // scope for lock + std::unique_lock lck(mutex); + Debug(4, "Have Lock in get_packet"); + while (!lp) { + while ((*it == pktQueue.end()) and !(deleting or zm_terminate)) { + Debug(2, "waiting. Queue size %zu it == end? %d", pktQueue.size(), (*it == pktQueue.end())); + condition.wait(lck); + } + if (deleting or zm_terminate) break; + + std::shared_ptr p = *(*it); + if (!p) { + Error("Null p?!"); return nullptr; } + Debug(3, "get_packet using it %p locking index %d", + std::addressof(*it), p->image_index); + + lp = new ZMLockedPacket(p); + if (lp->trylock()) { + Debug(2, "Locked packet %d, unlocking packetqueue mutex", p->image_index); + return lp; + } + delete lp; + lp = nullptr; Debug(2, "waiting. Queue size %zu it == end? %d", pktQueue.size(), (*it == pktQueue.end())); condition.wait(lck); - } - if (deleting or zm_terminate) { - Debug(1, "terminated, leaving"); - condition.notify_all(); - return nullptr; - } + } // end while !lp + } // end scope for lock - ZMPacket *p = *(*it); - if (!p) { - Error("Null p?!"); - return nullptr; - } - Debug(4, "get_packet using it %p locking index %d, packet %p", - std::addressof(*it), p->image_index, p); - // Packets are only deleted by packetqueue, so lock must be held. - // We shouldn't have to trylock. Someone else might hold the lock but not for long - - lp = new ZMLockedPacket(p); - if (lp->trylock()) { - Debug(2, "Locked packet %d, unlocking packetqueue mutex", p->image_index); - - return lp; - } - delete lp; - lp = nullptr; - Debug(2, "waiting. Queue size %zu it == end? %d", pktQueue.size(), (*it == pktQueue.end())); - condition.wait(lck); - } // end while !lp - return nullptr; + if (!lp) { + Debug(1, "terminated, leaving"); + condition.notify_all(); + } + return lp; } // end ZMLockedPacket *PacketQueue::get_packet(it) +// Returns a packet. Packet will be locked +ZMLockedPacket *PacketQueue::get_packet_and_increment_it(packetqueue_iterator *it) { + if (deleting or zm_terminate) + return nullptr; + + Debug(4, "Locking in get_packet using it %p queue end? %d", + std::addressof(*it), (*it == pktQueue.end())); + + ZMLockedPacket *lp = nullptr; + { // scope for lock + std::unique_lock lck(mutex); + Debug(4, "Have Lock in get_packet"); + while (!lp) { + while ((*it == pktQueue.end()) and !(deleting or zm_terminate)) { + Debug(2, "waiting. Queue size %zu it == end? %d", pktQueue.size(), (*it == pktQueue.end())); + condition.wait(lck); + } + if (deleting or zm_terminate) break; + + std::shared_ptr p = *(*it); + if (!p) { + Error("Null p?!"); + return nullptr; + } + Debug(3, "get_packet using it %p locking index %d", + std::addressof(*it), p->image_index); + + lp = new ZMLockedPacket(p); + if (lp->trylock()) { + Debug(2, "Locked packet %d, unlocking packetqueue mutex, incrementing it", p->image_index); + ++(*it); + return lp; + } + delete lp; + lp = nullptr; + Debug(2, "waiting. Queue size %zu it == end? %d", pktQueue.size(), (*it == pktQueue.end())); + condition.wait(lck); + } // end while !lp + } // end scope for lock + + if (!lp) { + Debug(1, "terminated, leaving"); + condition.notify_all(); + } + return lp; +} // end ZMLockedPacket *PacketQueue::get_packet_and_increment_it(it) + void PacketQueue::unlock(ZMLockedPacket *lp) { delete lp; condition.notify_all(); @@ -613,7 +477,7 @@ void PacketQueue::unlock(ZMLockedPacket *lp) { bool PacketQueue::increment_it(packetqueue_iterator *it) { Debug(2, "Incrementing %p, queue size %zu, end? %d", it, pktQueue.size(), ((*it) == pktQueue.end())); - if ((*it) == pktQueue.end() or deleting) { + if (((*it) == pktQueue.end()) or deleting) { return false; } std::unique_lock lck(mutex); @@ -629,7 +493,7 @@ bool PacketQueue::increment_it(packetqueue_iterator *it) { // Increment it only considering packets for a given stream bool PacketQueue::increment_it(packetqueue_iterator *it, int stream_id) { Debug(2, "Incrementing %p, queue size %zu, end? %d", it, pktQueue.size(), (*it == pktQueue.end())); - if ( *it == pktQueue.end() ) { + if (*it == pktQueue.end()) { return false; } @@ -655,7 +519,7 @@ packetqueue_iterator *PacketQueue::get_event_start_packet_it( iterators.push_back(it); *it = snapshot_it; - ZMPacket *packet = *(*it); + std::shared_ptr packet = *(*it); ZM_DUMP_PACKET(packet->packet, ""); // Step one count back pre_event_count frames as the minimum // Do not assume that snapshot_it is video @@ -673,10 +537,10 @@ packetqueue_iterator *PacketQueue::get_event_start_packet_it( } (*it)--; } + packet = *(*it); } // it either points to beginning or we have seen pre_event_count video packets. - packet = *(*it); if (pre_event_count) { if (packet->image_index < (int)pre_event_count) { // probably just starting up @@ -686,6 +550,9 @@ packetqueue_iterator *PacketQueue::get_event_start_packet_it( } ZM_DUMP_PACKET(packet->packet, ""); return it; + } else if (!keep_keyframes) { + // Are encoding, so don't care about keyframes + return it; } while ((*it) != pktQueue.begin()) { @@ -702,9 +569,9 @@ packetqueue_iterator *PacketQueue::get_event_start_packet_it( } // end packetqueue_iterator *PacketQueue::get_event_start_packet_it void PacketQueue::dumpQueue() { - std::list::reverse_iterator it; + std::list>::reverse_iterator it; for ( it = pktQueue.rbegin(); it != pktQueue.rend(); ++ it ) { - ZMPacket *zm_packet = *it; + std::shared_ptr zm_packet = *it; ZM_DUMP_PACKET(zm_packet->packet, ""); } } @@ -732,8 +599,8 @@ packetqueue_iterator * PacketQueue::get_video_it(bool wait) { } } - while ( *it != pktQueue.end() ) { - ZMPacket *zm_packet = *(*it); + while (*it != pktQueue.end()) { + std::shared_ptr zm_packet = *(*it); if (!zm_packet) { Error("Null zmpacket in queue!?"); free_it(it); @@ -764,19 +631,19 @@ void PacketQueue::free_it(packetqueue_iterator *it) { } } -bool PacketQueue::is_there_an_iterator_pointing_to_packet(ZMPacket *zm_packet) { +bool PacketQueue::is_there_an_iterator_pointing_to_packet(const std::shared_ptr &zm_packet) { for ( std::list::iterator iterators_it = iterators.begin(); iterators_it != iterators.end(); ++iterators_it ) { packetqueue_iterator *iterator_it = *iterators_it; - if ( *iterator_it == pktQueue.end() ) { + if (*iterator_it == pktQueue.end()) { continue; } Debug(4, "Checking iterator %p == packet ? %d", std::addressof(*iterator_it), ( *(*iterator_it) == zm_packet )); // Have to check each iterator and make sure it doesn't point to the packet we are about to delete - if ( *(*iterator_it) == zm_packet ) { + if (*(*iterator_it) == zm_packet) { return true; } } // end foreach iterator @@ -786,13 +653,13 @@ bool PacketQueue::is_there_an_iterator_pointing_to_packet(ZMPacket *zm_packet) { void PacketQueue::setMaxVideoPackets(int p) { max_video_packet_count = p; Debug(1, "Setting max_video_packet_count to %d", p); - if ( max_video_packet_count < 0 ) + if (max_video_packet_count < 0) max_video_packet_count = 0 ; } void PacketQueue::setPreEventVideoPackets(int p) { pre_event_video_packet_count = p; Debug(1, "Setting pre_event_video_packet_count to %d", p); - if ( pre_event_video_packet_count < 1 ) + if (pre_event_video_packet_count < 1) pre_event_video_packet_count = 1; // We can simplify a lot of logic in queuePacket if we can assume at least 1 packet in queue } diff --git a/src/zm_packetqueue.h b/src/zm_packetqueue.h index bc9932f24..7e2f367fc 100644 --- a/src/zm_packetqueue.h +++ b/src/zm_packetqueue.h @@ -22,16 +22,17 @@ #include #include #include +#include class ZMPacket; class ZMLockedPacket; -typedef std::list::iterator packetqueue_iterator; +typedef std::list>::iterator packetqueue_iterator; class PacketQueue { public: // For now just to ease development - std::list pktQueue; - std::list::iterator analysis_it; + std::list> pktQueue; + std::list>::iterator analysis_it; int video_stream_id; int max_video_packet_count; // allow a negative value to someday mean unlimited @@ -49,32 +50,27 @@ class PacketQueue { public: PacketQueue(); virtual ~PacketQueue(); - std::list::const_iterator end() const { return pktQueue.end(); } - std::list::const_iterator begin() const { return pktQueue.begin(); } + std::list>::const_iterator end() const { return pktQueue.end(); } + std::list>::const_iterator begin() const { return pktQueue.begin(); } int addStream(); void setMaxVideoPackets(int p); void setPreEventVideoPackets(int p); void setKeepKeyframes(bool k) { keep_keyframes = k; }; - bool queuePacket(ZMPacket* packet); - ZMLockedPacket * popPacket(); - bool popVideoPacket(ZMPacket* packet); - bool popAudioPacket(ZMPacket* packet); - unsigned int clear(unsigned int video_frames_to_keep, int stream_id); - unsigned int clear(struct timeval *duration, int streamid); + bool queuePacket(std::shared_ptr packet); void clear(); void dumpQueue(); unsigned int size(); unsigned int get_packet_count(int stream_id) const { return packet_counts[stream_id]; }; - void clear_unwanted_packets(timeval *recording, int pre_event_count, int mVideoStreamId); - void clearPackets(ZMPacket *); + void clearPackets(const std::shared_ptr &packet); int packet_count(int stream_id); bool increment_it(packetqueue_iterator *it); bool increment_it(packetqueue_iterator *it, int stream_id); ZMLockedPacket *get_packet(packetqueue_iterator *); + ZMLockedPacket *get_packet_and_increment_it(packetqueue_iterator *); packetqueue_iterator *get_video_it(bool wait); packetqueue_iterator *get_stream_it(int stream_id); void free_it(packetqueue_iterator *); @@ -83,7 +79,7 @@ class PacketQueue { packetqueue_iterator snapshot_it, unsigned int pre_event_count ); - bool is_there_an_iterator_pointing_to_packet(ZMPacket *zm_packet); + bool is_there_an_iterator_pointing_to_packet(const std::shared_ptr &zm_packet); void unlock(ZMLockedPacket *lp); }; diff --git a/src/zm_poly.cpp b/src/zm_poly.cpp index 94f153b98..6485bc192 100644 --- a/src/zm_poly.cpp +++ b/src/zm_poly.cpp @@ -19,91 +19,103 @@ #include "zm_poly.h" +#include "zm_line.h" #include -void Polygon::calcArea() { - double float_area = 0.0L; - for ( int i = 0, j = n_coords-1; i < n_coords; j = i++ ) { - double trap_area = ((coords[i].X()-coords[j].X())*((coords[i].Y()+coords[j].Y())))/2.0L; +Polygon::Polygon(std::vector vertices) : vertices_(std::move(vertices)), area(0) { + UpdateExtent(); + UpdateArea(); + UpdateCentre(); +} + +void Polygon::UpdateExtent() { + if (vertices_.empty()) + return; + + int min_x = vertices_[0].x_; + int max_x = 0; + int min_y = vertices_[0].y_; + int max_y = 0; + for (const Vector2 &vertex : vertices_) { + min_x = std::min(min_x, vertex.x_); + max_x = std::max(max_x, vertex.x_); + min_y = std::min(min_y, vertex.y_); + max_y = std::max(max_y, vertex.y_); + } + + extent = Box({min_x, min_y}, {max_x, max_y}); +} + +void Polygon::UpdateArea() { + double float_area = 0.0; + for (size_t i = 0, j = vertices_.size() - 1; i < vertices_.size(); j = i++) { + double trap_area = ((vertices_[i].x_ - vertices_[j].x_) * ((vertices_[i].y_ + vertices_[j].y_))) / 2.0; float_area += trap_area; - //printf( "%.2f (%.2f)\n", float_area, trap_area ); } - area = (int)round(fabs(float_area)); + + area = static_cast(std::lround(std::fabs(float_area))); } -void Polygon::calcCentre() { - if ( !area && n_coords ) - calcArea(); - double float_x = 0.0L, float_y = 0.0L; - for ( int i = 0, j = n_coords-1; i < n_coords; j = i++ ) { - float_x += ((coords[i].Y()-coords[j].Y())*((coords[i].X()*2)+(coords[i].X()*coords[j].X())+(coords[j].X()*2))); - float_y += ((coords[j].X()-coords[i].X())*((coords[i].Y()*2)+(coords[i].Y()*coords[j].Y())+(coords[j].Y()*2))); +void Polygon::UpdateCentre() { + if (!area && !vertices_.empty()) + UpdateArea(); + + double float_x = 0.0; + double float_y = 0.0; + for (size_t i = 0, j = vertices_.size() - 1; i < vertices_.size(); j = i++) { + float_x += ((vertices_[i].y_ - vertices_[j].y_) + * ((vertices_[i].x_ * 2) + (vertices_[i].x_ * vertices_[j].x_) + (vertices_[j].x_ * 2))); + float_y += ((vertices_[j].x_ - vertices_[i].x_) + * ((vertices_[i].y_ * 2) + (vertices_[i].y_ * vertices_[j].y_) + (vertices_[j].y_ * 2))); } - float_x /= (6*area); - float_y /= (6*area); - centre = Coord( (int)round(float_x), (int)round(float_y) ); + float_x /= (6 * area); + float_y /= (6 * area); + + centre = Vector2(static_cast(std::lround(float_x)), static_cast(std::lround(float_y))); } -Polygon::Polygon(int p_n_coords, const Coord *p_coords) : n_coords(p_n_coords) { - coords = new Coord[n_coords]; - - int min_x = -1; - int max_x = -1; - int min_y = -1; - int max_y = -1; - for ( int i = 0; i < n_coords; i++ ) { - coords[i] = p_coords[i]; - if ( min_x == -1 || coords[i].X() < min_x ) - min_x = coords[i].X(); - if ( max_x == -1 || coords[i].X() > max_x ) - max_x = coords[i].X(); - if ( min_y == -1 || coords[i].Y() < min_y ) - min_y = coords[i].Y(); - if ( max_y == -1 || coords[i].Y() > max_y ) - max_y = coords[i].Y(); - } - extent = Box( min_x, min_y, max_x, max_y ); - calcArea(); - calcCentre(); -} - -Polygon::Polygon(const Polygon &p_polygon) : - n_coords(p_polygon.n_coords), - extent(p_polygon.extent), - area(p_polygon.area), - centre(p_polygon.centre) -{ - coords = new Coord[n_coords]; - for( int i = 0; i < n_coords; i++ ) { - coords[i] = p_polygon.coords[i]; - } -} - -Polygon &Polygon::operator=(const Polygon &p_polygon) { - n_coords = p_polygon.n_coords; - - Coord *new_coords = new Coord[n_coords]; - for (int i = 0; i < n_coords; i++) { - new_coords[i] = p_polygon.coords[i]; - } - delete[] coords; - coords = new_coords; - - extent = p_polygon.extent; - area = p_polygon.area; - centre = p_polygon.centre; - return *this; -} - -bool Polygon::isInside( const Coord &coord ) const { +bool Polygon::Contains(const Vector2 &coord) const { bool inside = false; - for ( int i = 0, j = n_coords-1; i < n_coords; j = i++ ) { - if ( (((coords[i].Y() <= coord.Y()) && (coord.Y() < coords[j].Y()) ) - || ((coords[j].Y() <= coord.Y()) && (coord.Y() < coords[i].Y()))) - && (coord.X() < (coords[j].X() - coords[i].X()) * (coord.Y() - coords[i].Y()) / (coords[j].Y() - coords[i].Y()) + coords[i].X())) - { + for (size_t i = 0, j = vertices_.size() - 1; i < vertices_.size(); j = i++) { + if ((((vertices_[i].y_ <= coord.y_) && (coord.y_ < vertices_[j].y_)) || ((vertices_[j].y_ <= coord.y_) && (coord.y_ < vertices_[i].y_))) + && (coord.x_ < (vertices_[j].x_ - vertices_[i].x_) * (coord.y_ - vertices_[i].y_) / (vertices_[j].y_ - vertices_[i].y_) + vertices_[i].x_)) { inside = !inside; } } return inside; } + +// Clip the polygon to a rectangular boundary box using the Sutherland-Hodgman algorithm +void Polygon::Clip(const Box &boundary) { + std::vector clipped_vertices = vertices_; + + for (LineSegment const &clip_edge : boundary.Edges()) { + // convert our line segment to an infinite line + Line clip_line = Line(clip_edge); + + std::vector to_clip = clipped_vertices; + clipped_vertices.clear(); + + for (size_t i = 0; i < to_clip.size(); ++i) { + Vector2 vert1 = to_clip[i]; + Vector2 vert2 = to_clip[(i + 1) % to_clip.size()]; + + bool vert1_left = clip_line.IsPointLeftOfOrColinear(vert1); + bool vert2_left = clip_line.IsPointLeftOfOrColinear(vert2); + + if (vert2_left) { + if (!vert1_left) { + clipped_vertices.push_back(Line(vert1, vert2).Intersection(clip_line)); + } + clipped_vertices.push_back(vert2); + } else if (vert1_left) { + clipped_vertices.push_back(Line(vert1, vert2).Intersection(clip_line)); + } + } + } + + vertices_ = clipped_vertices; + UpdateExtent(); + UpdateArea(); + UpdateCentre(); +} diff --git a/src/zm_poly.h b/src/zm_poly.h index 21b7f14bb..c25033262 100644 --- a/src/zm_poly.h +++ b/src/zm_poly.h @@ -21,96 +21,36 @@ #define ZM_POLY_H #include "zm_box.h" +#include -class Coord; - -// -// Class used for storing a box, which is defined as a region -// defined by two coordinates -// +// This class represents convex or concave non-self-intersecting polygons. class Polygon { -protected: - struct Edge { - int min_y; - int max_y; - double min_x; - double _1_m; + public: + Polygon() : area(0) {} + explicit Polygon(std::vector vertices); - static int CompareYX( const void *p1, const void *p2 ) { - const Edge *e1 = reinterpret_cast(p1), *e2 = reinterpret_cast(p2); - if ( e1->min_y == e2->min_y ) - return int(e1->min_x - e2->min_x); - else - return int(e1->min_y - e2->min_y); - } - static int CompareX( const void *p1, const void *p2 ) { - const Edge *e1 = reinterpret_cast(p1), *e2 = reinterpret_cast(p2); - return int(e1->min_x - e2->min_x); - } - }; + const std::vector &GetVertices() const { + return vertices_; + } - struct Slice { - int min_x; - int max_x; - int n_edges; - int *edges; + const Box &Extent() const { return extent; } + int32 Area() const { return area; } + const Vector2 &Centre() const { return centre; } - Slice() { - min_x = 0; - max_x = 0; - n_edges = 0; - edges = nullptr; - } - ~Slice() { - delete edges; - } - }; + bool Contains(const Vector2 &coord) const; -protected: - int n_coords; - Coord *coords; + void Clip(const Box &boundary); + + private: + void UpdateExtent(); + void UpdateArea(); + void UpdateCentre(); + + private: + std::vector vertices_; Box extent; - int area; - Coord centre; - -protected: - void initialiseEdges(); - void calcArea(); - void calcCentre(); - -public: - inline Polygon() : n_coords(0), coords(nullptr), area(0) { - } - Polygon(int p_n_coords, const Coord *p_coords); - Polygon(const Polygon &p_polygon); - ~Polygon() { - delete[] coords; - } - - Polygon &operator=( const Polygon &p_polygon ); - - inline int getNumCoords() const { return n_coords; } - inline const Coord &getCoord( int index ) const { - return coords[index]; - } - - inline const Box &Extent() const { return extent; } - inline int LoX() const { return extent.LoX(); } - inline int LoX(int p_lo_x) { return extent.LoX(p_lo_x); } - inline int HiX() const { return extent.HiX(); } - inline int HiX(int p_hi_x) { return extent.HiX(p_hi_x); } - inline int LoY() const { return extent.LoY(); } - inline int LoY(int p_lo_y) { return extent.LoY(p_lo_y); } - inline int HiY() const { return extent.HiY(); } - inline int HiY(int p_hi_y) { return extent.HiY(p_hi_y); } - inline int Width() const { return extent.Width(); } - inline int Height() const { return extent.Height(); } - - inline int Area() const { return area; } - inline const Coord &Centre() const { - return centre; - } - bool isInside( const Coord &coord ) const; + int32 area; + Vector2 centre; }; #endif // ZM_POLY_H diff --git a/src/zm_remote_camera.cpp b/src/zm_remote_camera.cpp index 5e3dfefae..9cb948352 100644 --- a/src/zm_remote_camera.cpp +++ b/src/zm_remote_camera.cpp @@ -100,7 +100,8 @@ void RemoteCamera::Initialise() { int ret = getaddrinfo(host.c_str(), port.c_str(), &hints, &hp); if ( ret != 0 ) { - Fatal( "Can't getaddrinfo(%s port %s): %s", host.c_str(), port.c_str(), gai_strerror(ret) ); + Error( "Can't getaddrinfo(%s port %s): %s", host.c_str(), port.c_str(), gai_strerror(ret) ); + return; } struct addrinfo *p = nullptr; int addr_count = 0; diff --git a/src/zm_remote_camera.h b/src/zm_remote_camera.h index b4d9af3bc..e08975af2 100644 --- a/src/zm_remote_camera.h +++ b/src/zm_remote_camera.h @@ -83,7 +83,7 @@ public: virtual int Disconnect() = 0; virtual int PreCapture() override { return 0; }; virtual int PrimeCapture() override { return 0; }; - virtual int Capture(ZMPacket &p) override = 0; + virtual int Capture(std::shared_ptr &p) override = 0; virtual int PostCapture() override = 0; int Read(int fd, char*buf, int size); }; diff --git a/src/zm_remote_camera_http.cpp b/src/zm_remote_camera_http.cpp index 6eab03fb8..6169149da 100644 --- a/src/zm_remote_camera_http.cpp +++ b/src/zm_remote_camera_http.cpp @@ -144,6 +144,14 @@ void RemoteCameraHttp::Initialise() { int RemoteCameraHttp::Connect() { struct addrinfo *p = nullptr; + if (!hp) { + RemoteCamera::Initialise(); + if (!hp) { + Error("Unable to resolve address for remote camera, aborting"); + return -1; + } + } + for ( p = hp; p != nullptr; p = p->ai_next ) { sd = socket( p->ai_family, p->ai_socktype, p->ai_protocol ); if ( sd < 0 ) { @@ -300,15 +308,17 @@ int RemoteCameraHttp::ReadData(Buffer &buffer, unsigned int bytes_expected) { } // end readData int RemoteCameraHttp::GetData() { - time_t start_time = time(nullptr); - int buffer_len = 0; - while (!(buffer_len = ReadData(buffer))) { - if (zm_terminate or ( (time(nullptr) - start_time) > ZM_WATCH_MAX_DELAY )) - return -1; - Debug(4, "Timeout waiting for REGEXP HEADER"); - usleep(100000); - } - return buffer_len; + TimePoint start_time = std::chrono::steady_clock::now(); + int buffer_len; + while (!(buffer_len = ReadData(buffer))) { + if (zm_terminate or std::chrono::steady_clock::now() - start_time > FPSeconds(config.watch_max_delay)) { + return -1; + } + + Debug(4, "Timeout waiting for REGEXP HEADER"); + std::this_thread::sleep_for(Milliseconds(100)); + } + return buffer_len; } int RemoteCameraHttp::GetResponse() { @@ -1069,7 +1079,7 @@ int RemoteCameraHttp::PreCapture() { return 1; } // end int RemoteCameraHttp::PreCapture() -int RemoteCameraHttp::Capture(ZMPacket &packet) { +int RemoteCameraHttp::Capture(std::shared_ptr &packet) { int content_length = GetResponse(); if (content_length == 0) { Warning("Unable to capture image, retrying"); @@ -1080,15 +1090,15 @@ int RemoteCameraHttp::Capture(ZMPacket &packet) { return -1; } - if (!packet.image) { + if (!packet->image) { Debug(4, "Allocating image"); - packet.image = new Image(width, height, colours, subpixelorder); + packet->image = new Image(width, height, colours, subpixelorder); } - Image *image = packet.image; - packet.keyframe = 1; - packet.codec_type = AVMEDIA_TYPE_VIDEO; - packet.packet.stream_index = mVideoStreamId; - packet.stream = mVideoStream; + Image *image = packet->image; + packet->keyframe = 1; + packet->codec_type = AVMEDIA_TYPE_VIDEO; + packet->packet.stream_index = mVideoStreamId; + packet->stream = mVideoStream; switch (format) { case JPEG : diff --git a/src/zm_remote_camera_http.h b/src/zm_remote_camera_http.h index dc793459b..794ccba78 100644 --- a/src/zm_remote_camera_http.h +++ b/src/zm_remote_camera_http.h @@ -69,7 +69,7 @@ public: int GetResponse(); int PrimeCapture() override; int PreCapture() override; - int Capture( ZMPacket &p ) override; + int Capture(std::shared_ptr &p) override; int PostCapture() override; int Close() override { Disconnect(); return 0; }; }; diff --git a/src/zm_remote_camera_nvsocket.cpp b/src/zm_remote_camera_nvsocket.cpp index f66d31fcb..92f806eed 100644 --- a/src/zm_remote_camera_nvsocket.cpp +++ b/src/zm_remote_camera_nvsocket.cpp @@ -184,9 +184,9 @@ int RemoteCameraNVSocket::PrimeCapture() { return 0; } -int RemoteCameraNVSocket::Capture( ZMPacket &zm_packet ) { - if ( SendRequest("GetNextImage\n") < 0 ) { - Warning( "Unable to capture image, retrying" ); +int RemoteCameraNVSocket::Capture(std::shared_ptr &zm_packet) { + if (SendRequest("GetNextImage\n") < 0) { + Warning("Unable to capture image, retrying"); return 0; } int bytes_read = Read(sd, buffer, imagesize); @@ -195,17 +195,17 @@ int RemoteCameraNVSocket::Capture( ZMPacket &zm_packet ) { return 0; } uint32_t end; - if ( Read(sd, (char *) &end , sizeof(end)) < 0 ) { + if (Read(sd, (char *) &end , sizeof(end)) < 0) { Warning("Unable to capture image, retrying"); return 0; } - if ( end != 0xFFFFFFFF) { + if (end != 0xFFFFFFFF) { Warning("End Bytes Failed\n"); return 0; } - zm_packet.image->Assign(width, height, colours, subpixelorder, buffer, imagesize); - zm_packet.keyframe = 1; + zm_packet->image->Assign(width, height, colours, subpixelorder, buffer, imagesize); + zm_packet->keyframe = 1; return 1; } diff --git a/src/zm_remote_camera_nvsocket.h b/src/zm_remote_camera_nvsocket.h index 1cd740af2..d3b5ce8e9 100644 --- a/src/zm_remote_camera_nvsocket.h +++ b/src/zm_remote_camera_nvsocket.h @@ -54,7 +54,7 @@ public: int SendRequest(std::string); int GetResponse(); int PrimeCapture() override; - int Capture(ZMPacket &p) override; + int Capture(std::shared_ptr &p) override; int PostCapture() override; int Close() override { return 0; }; }; diff --git a/src/zm_remote_camera_rtsp.cpp b/src/zm_remote_camera_rtsp.cpp index 78c947faa..8cdecdf94 100644 --- a/src/zm_remote_camera_rtsp.cpp +++ b/src/zm_remote_camera_rtsp.cpp @@ -23,8 +23,6 @@ #include "zm_monitor.h" #include "zm_packet.h" -#if HAVE_LIBAVFORMAT - RemoteCameraRtsp::RemoteCameraRtsp( const Monitor *monitor, const std::string &p_method, @@ -113,7 +111,7 @@ void RemoteCameraRtsp::Terminate() { } int RemoteCameraRtsp::Connect() { - rtspThread = ZM::make_unique(monitor->Id(), method, protocol, host, port, path, auth, rtsp_describe); + rtspThread = zm::make_unique(monitor->Id(), method, protocol, host, port, path, auth, rtsp_describe); return 0; } @@ -128,10 +126,11 @@ int RemoteCameraRtsp::Disconnect() { int RemoteCameraRtsp::PrimeCapture() { Debug(2, "Waiting for sources"); - for ( int i = 0; (i < 100) && !rtspThread->hasSources(); i++ ) { - usleep(100000); + for (int i = 0; i < 100 && !rtspThread->hasSources(); i++) { + std::this_thread::sleep_for(Microseconds(100)); } - if ( !rtspThread->hasSources() ) { + + if (!rtspThread->hasSources()) { Error("No RTSP sources"); return -1; } @@ -175,12 +174,8 @@ int RemoteCameraRtsp::PrimeCapture() { Debug(3, "Unable to locate audio stream"); // Get a pointer to the codec context for the video stream -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - mVideoCodecContext = avcodec_alloc_context3(NULL); + mVideoCodecContext = avcodec_alloc_context3(nullptr); avcodec_parameters_to_context(mVideoCodecContext, mFormatContext->streams[mVideoStreamId]->codecpar); -#else - mVideoCodecContext = mFormatContext->streams[mVideoStreamId]->codec; -#endif // Find the decoder for the video stream AVCodec *codec = avcodec_find_decoder(mVideoCodecContext->codec_id); @@ -188,18 +183,10 @@ int RemoteCameraRtsp::PrimeCapture() { Panic("Unable to locate codec %d decoder", mVideoCodecContext->codec_id); // Open codec -#if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0) - if ( avcodec_open(mVideoCodecContext, codec) < 0 ) -#else - if ( avcodec_open2(mVideoCodecContext, codec, 0) < 0 ) -#endif + if ( avcodec_open2(mVideoCodecContext, codec, nullptr) < 0 ) Panic("Can't open codec"); -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) int pSize = av_image_get_buffer_size(imagePixFormat, width, height, 1); -#else - int pSize = avpicture_get_size(imagePixFormat, width, height); -#endif if ( (unsigned int)pSize != imagesize ) { Fatal("Image size mismatch. Required: %d Available: %llu", pSize, imagesize); @@ -218,16 +205,16 @@ int RemoteCameraRtsp::PreCapture() { return 1; } -int RemoteCameraRtsp::Capture(ZMPacket &zm_packet) { +int RemoteCameraRtsp::Capture(std::shared_ptr &zm_packet) { int frameComplete = false; - AVPacket *packet = &zm_packet.packet; - if ( !zm_packet.image ) { + 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); + zm_packet->image = new Image(width, height, colours, subpixelorder); } - while ( !frameComplete ) { + while (!frameComplete) { buffer.clear(); if (!rtspThread || rtspThread->IsStopped()) return -1; @@ -254,7 +241,7 @@ int RemoteCameraRtsp::Capture(ZMPacket &zm_packet) { continue; } else if ( nalType == 5 ) { packet->flags |= AV_PKT_FLAG_KEY; - zm_packet.keyframe = 1; + zm_packet->keyframe = 1; // IDR buffer += lastSps; buffer += lastPps; @@ -275,36 +262,23 @@ int RemoteCameraRtsp::Capture(ZMPacket &zm_packet) { gettimeofday(&now, NULL); packet->pts = packet->dts = now.tv_sec*1000000+now.tv_usec; - int bytes_consumed = zm_packet.decode(mVideoCodecContext); + 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-> -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - codecpar -#else - codec -#endif - ->width ) { + zm_dump_video_frame(zm_packet->in_frame, "remote_rtsp_decode"); + if (!mVideoStream->codecpar->width) { zm_dump_codec(mVideoCodecContext); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) zm_dump_codecpar(mVideoStream->codecpar); - mVideoStream->codecpar->width = zm_packet.in_frame->width; - mVideoStream->codecpar->height = zm_packet.in_frame->height; -#else - mVideoStream->codec->width = zm_packet.in_frame->width; - mVideoStream->codec->height = zm_packet.in_frame->height; -#endif -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + mVideoStream->codecpar->width = zm_packet->in_frame->width; + mVideoStream->codecpar->height = zm_packet->in_frame->height; zm_dump_codecpar(mVideoStream->codecpar); -#endif } - zm_packet.codec_type = mVideoCodecContext->codec_type; - zm_packet.stream = mVideoStream; + 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; @@ -320,4 +294,3 @@ int RemoteCameraRtsp::Capture(ZMPacket &zm_packet) { int RemoteCameraRtsp::PostCapture() { return 1; } -#endif // HAVE_LIBAVFORMAT diff --git a/src/zm_remote_camera_rtsp.h b/src/zm_remote_camera_rtsp.h index c4734ebee..4880f6f35 100644 --- a/src/zm_remote_camera_rtsp.h +++ b/src/zm_remote_camera_rtsp.h @@ -48,10 +48,8 @@ protected: int frameCount; -#if HAVE_LIBAVFORMAT AVFormatContext *mFormatContext; _AVPIXELFORMAT imagePixFormat; -#endif // HAVE_LIBAVFORMAT public: RemoteCameraRtsp( @@ -79,7 +77,7 @@ public: int PrimeCapture() override; int PreCapture() override; - int Capture(ZMPacket &p) override; + int Capture(std::shared_ptr &p) override; int PostCapture() override; int Close() override { return 0; }; diff --git a/src/zm_rtp_ctrl.cpp b/src/zm_rtp_ctrl.cpp index 9d74359fc..25d34f0ff 100644 --- a/src/zm_rtp_ctrl.cpp +++ b/src/zm_rtp_ctrl.cpp @@ -23,8 +23,6 @@ #include "zm_rtp.h" #include "zm_rtsp.h" -#if HAVE_LIBAVFORMAT - RtpCtrlThread::RtpCtrlThread(RtspThread &rtspThread, RtpSource &rtpSource) : mRtspThread(rtspThread), mRtpSource(rtpSource), mTerminate(false) { @@ -250,10 +248,10 @@ int RtpCtrlThread::recvPackets( unsigned char *buffer, ssize_t nBytes ) { void RtpCtrlThread::Run() { Debug( 2, "Starting control thread %x on port %d", mRtpSource.getSsrc(), mRtpSource.getLocalCtrlPort() ); - ZM::SockAddrInet localAddr, remoteAddr; + zm::SockAddrInet localAddr, remoteAddr; bool sendReports; - ZM::UdpInetSocket rtpCtrlServer; + zm::UdpInetSocket rtpCtrlServer; if ( mRtpSource.getLocalHost() != "" ) { if ( !rtpCtrlServer.bind( mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalCtrlPort() ) ) Fatal( "Failed to bind RTCP server" ); @@ -271,17 +269,17 @@ void RtpCtrlThread::Run() { // The only reason I can think of why we would have a timeout period is so that we can regularly send RR packets. // Why 10 seconds? If anything I think this should be whatever timeout value was given in the DESCRIBE response - ZM::Select select( 10 ); + zm::Select select(Seconds(10)); select.addReader( &rtpCtrlServer ); unsigned char buffer[ZM_NETWORK_BUFSIZ]; - time_t last_receive = time(nullptr); - 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. + 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) { - time_t now = time(nullptr); - ZM::Select::CommsList readable = select.getReadable(); + TimePoint now = std::chrono::steady_clock::now(); + zm::Select::CommsList readable = select.getReadable(); if ( readable.size() == 0 ) { if ( ! timeout ) { // With this code here, we will send an SDES and RR packet every 10 seconds @@ -289,23 +287,23 @@ void RtpCtrlThread::Run() { unsigned char *bufferPtr = buffer; bufferPtr += generateRr( bufferPtr, sizeof(buffer)-(bufferPtr-buffer) ); bufferPtr += generateSdes( bufferPtr, sizeof(buffer)-(bufferPtr-buffer) ); - Debug(3, "Preventing timeout by sending %zd bytes on sd %d. Time since last receive: %" PRIi64, - bufferPtr - buffer, rtpCtrlServer.getWriteDesc(), static_cast(now - last_receive)); + Debug(3, "Preventing timeout by sending %zd bytes on sd %d. Time since last receive: %.2f s", + bufferPtr - buffer, rtpCtrlServer.getWriteDesc(), FPSeconds(now - last_receive).count()); if ( (nBytes = rtpCtrlServer.send(buffer, bufferPtr-buffer)) < 0 ) Error("Unable to send: %s", strerror(errno)); timeout = true; continue; } else { - Debug(1, "RTCP timed out. Time since last receive: %" PRIi64, static_cast(now - last_receive)); + Debug(1, "RTCP timed out. Time since last receive: %.2f s", FPSeconds(now - last_receive).count()); continue; //break; } } else { timeout = false; - last_receive = time(nullptr); + last_receive = std::chrono::steady_clock::now(); } - for ( ZM::Select::CommsList::iterator iter = readable.begin(); iter != readable.end(); ++iter ) { - if ( ZM::UdpInetSocket *socket = dynamic_cast(*iter) ) { + for (zm::Select::CommsList::iterator iter = readable.begin(); iter != readable.end(); ++iter ) { + if ( zm::UdpInetSocket *socket = dynamic_cast(*iter) ) { ssize_t nBytes = socket->recv( buffer, sizeof(buffer) ); Debug( 4, "Read %zd bytes on sd %d", nBytes, socket->getReadDesc() ); @@ -334,5 +332,3 @@ void RtpCtrlThread::Run() { rtpCtrlServer.close(); mRtspThread.Stop(); } - -#endif // HAVE_LIBAVFORMAT diff --git a/src/zm_rtp_data.cpp b/src/zm_rtp_data.cpp index aed02aea6..e8ce628f2 100644 --- a/src/zm_rtp_data.cpp +++ b/src/zm_rtp_data.cpp @@ -23,8 +23,6 @@ #include "zm_rtsp.h" #include "zm_signal.h" -#if HAVE_LIBAVFORMAT - RtpDataThread::RtpDataThread(RtspThread &rtspThread, RtpSource &rtpSource) : mRtspThread(rtspThread), mRtpSource(rtpSource), mTerminate(false) { @@ -65,8 +63,8 @@ void RtpDataThread::Run() { Debug(2, "Starting data thread %d on port %d", mRtpSource.getSsrc(), mRtpSource.getLocalDataPort()); - ZM::SockAddrInet localAddr; - ZM::UdpInetServer rtpDataSocket; + zm::SockAddrInet localAddr; + zm::UdpInetServer rtpDataSocket; if ( mRtpSource.getLocalHost() != "" ) { if ( !rtpDataSocket.bind(mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalDataPort()) ) Fatal("Failed to bind RTP server"); @@ -78,19 +76,19 @@ void RtpDataThread::Run() { } Debug(3, "Bound to %s:%d", mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalDataPort()); - ZM::Select select(3); + zm::Select select(Seconds(3)); select.addReader(&rtpDataSocket); unsigned char buffer[ZM_NETWORK_BUFSIZ]; while ( !zm_terminate && !mTerminate && (select.wait() >= 0) ) { - ZM::Select::CommsList readable = select.getReadable(); + zm::Select::CommsList readable = select.getReadable(); if ( readable.size() == 0 ) { Error("RTP timed out"); Stop(); break; } - for ( ZM::Select::CommsList::iterator iter = readable.begin(); iter != readable.end(); ++iter ) { - if ( ZM::UdpInetServer *socket = dynamic_cast(*iter) ) { + for (zm::Select::CommsList::iterator iter = readable.begin(); iter != readable.end(); ++iter ) { + if ( zm::UdpInetServer *socket = dynamic_cast(*iter) ) { int nBytes = socket->recv(buffer, sizeof(buffer)); Debug(4, "Got %d bytes on sd %d", nBytes, socket->getReadDesc()); if ( nBytes ) { @@ -107,5 +105,3 @@ void RtpDataThread::Run() { rtpDataSocket.close(); mRtspThread.Stop(); } - -#endif // HAVE_LIBAVFORMAT diff --git a/src/zm_rtp_source.cpp b/src/zm_rtp_source.cpp index ff69a6069..1862c1886 100644 --- a/src/zm_rtp_source.cpp +++ b/src/zm_rtp_source.cpp @@ -25,8 +25,6 @@ #include #include -#if HAVE_LIBAVCODEC - RtpSource::RtpSource( int id, const std::string &localHost, @@ -68,12 +66,12 @@ RtpSource::RtpSource( mRtpFactor = mRtpClock; - mBaseTimeReal = tvNow(); - mBaseTimeNtp = tvZero(); + mBaseTimeReal = std::chrono::system_clock::now(); + mBaseTimeNtp = {}; mBaseTimeRtp = rtpTime; - mLastSrTimeReal = tvZero(); - mLastSrTimeNtp = tvZero(); + mLastSrTimeReal = {}; + mLastSrTimeNtp = {}; mLastSrTimeRtp = 0; if ( mCodecId != AV_CODEC_ID_H264 && mCodecId != AV_CODEC_ID_MPEG4 ) @@ -161,13 +159,16 @@ bool RtpSource::updateSeq(uint16_t seq) { } void RtpSource::updateJitter( const RtpDataHeader *header ) { - if ( mRtpFactor > 0 ) { - uint32_t localTimeRtp = mBaseTimeRtp + uint32_t(tvDiffSec(mBaseTimeReal) * mRtpFactor); + if (mRtpFactor > 0) { + SystemTimePoint now = std::chrono::system_clock::now(); + FPSeconds time_diff = std::chrono::duration_cast(now - mBaseTimeReal); + + uint32_t localTimeRtp = mBaseTimeRtp + static_cast(time_diff.count() * mRtpFactor); uint32_t packetTransit = localTimeRtp - ntohl(header->timestampN); Debug(5, "Delta rtp = %.6f\n Local RTP time = %x Packet RTP time = %x Packet transit RTP time = %x", - tvDiffSec(mBaseTimeReal), + time_diff.count(), localTimeRtp, ntohl(header->timestampN), packetTransit); @@ -192,12 +193,13 @@ void RtpSource::updateRtcpData( uint32_t ntpTimeSecs, uint32_t ntpTimeFrac, uint32_t rtpTime) { - struct timeval ntpTime = tvMake(ntpTimeSecs, suseconds_t((USEC_PER_SEC*(ntpTimeFrac>>16))/(1<<16))); + timeval ntpTime = zm::chrono::duration_cast( + Seconds(ntpTimeSecs) + Microseconds((Microseconds::period::den * (ntpTimeFrac >> 16)) / (1 << 16))); Debug(5, "ntpTime: %ld.%06ld, rtpTime: %x", ntpTime.tv_sec, ntpTime.tv_usec, rtpTime); if ( mBaseTimeNtp.tv_sec == 0 ) { - mBaseTimeReal = tvNow(); + mBaseTimeReal = std::chrono::system_clock::now(); mBaseTimeNtp = ntpTime; mBaseTimeRtp = rtpTime; } else if ( !mRtpClock ) { @@ -206,12 +208,14 @@ void RtpSource::updateRtcpData( mLastSrTimeNtp.tv_sec, mLastSrTimeNtp.tv_usec, rtpTime, ntpTime.tv_sec, ntpTime.tv_usec, rtpTime); - double diffNtpTime = tvDiffSec( mBaseTimeNtp, ntpTime ); + FPSeconds diffNtpTime = + zm::chrono::duration_cast(ntpTime) - zm::chrono::duration_cast(mBaseTimeNtp); + uint32_t diffRtpTime = rtpTime - mBaseTimeRtp; - mRtpFactor = (uint32_t)(diffRtpTime / diffNtpTime); + mRtpFactor = static_cast(diffRtpTime / diffNtpTime.count()); Debug( 5, "NTP-diff: %.6f RTP-diff: %d RTPfactor: %d", - diffNtpTime, diffRtpTime, mRtpFactor); + diffNtpTime.count(), diffRtpTime, mRtpFactor); } mLastSrTimeNtpSecs = ntpTimeSecs; mLastSrTimeNtpFrac = ntpTimeFrac; @@ -371,5 +375,3 @@ bool RtpSource::getFrame(Buffer &buffer) { Debug(4, "Copied %d bytes", buffer.size()); return true; } - -#endif // HAVE_LIBAVCODEC diff --git a/src/zm_rtp_source.h b/src/zm_rtp_source.h index 0d1bdee83..a39e8225f 100644 --- a/src/zm_rtp_source.h +++ b/src/zm_rtp_source.h @@ -24,13 +24,12 @@ #include "zm_config.h" #include "zm_define.h" #include "zm_ffmpeg.h" +#include "zm_time.h" #include #include #include #include -#if HAVE_LIBAVCODEC - struct RtpDataHeader; class RtpSource @@ -70,7 +69,7 @@ private: // Time keys uint32_t mRtpClock; uint32_t mRtpFactor; - struct timeval mBaseTimeReal; + SystemTimePoint mBaseTimeReal; struct timeval mBaseTimeNtp; uint32_t mBaseTimeRtp; @@ -193,6 +192,4 @@ public: } }; -#endif // HAVE_LIBAVCODEC - #endif // ZM_RTP_SOURCE_H diff --git a/src/zm_rtsp.cpp b/src/zm_rtsp.cpp index 4efe0a43b..1daecf18b 100644 --- a/src/zm_rtsp.cpp +++ b/src/zm_rtsp.cpp @@ -26,8 +26,6 @@ #include -#if HAVE_LIBAVFORMAT - int RtspThread::smMinDataPort = 0; int RtspThread::smMaxDataPort = 0; RtspThread::PortSet RtspThread::smAssignedPorts; @@ -88,9 +86,8 @@ bool RtspThread::recvResponse(std::string &response) { int RtspThread::requestPorts() { if ( !smMinDataPort ) { - char sql[ZM_SQL_SML_BUFSIZ]; //FIXME Why not load specifically by Id? This will get ineffeicient with a lot of monitors - strncpy(sql, "SELECT `Id` FROM `Monitors` WHERE `Function` != 'None' AND `Type` = 'Remote' AND `Protocol` = 'rtsp' AND `Method` = 'rtpUni' ORDER BY `Id` ASC", sizeof(sql)); + std::string sql = "SELECT `Id` FROM `Monitors` WHERE `Function` != 'None' AND `Type` = 'Remote' AND `Protocol` = 'rtsp' AND `Method` = 'rtpUni' ORDER BY `Id` ASC"; MYSQL_RES *result = zmDbFetch(sql); @@ -188,11 +185,7 @@ RtspThread::~RtspThread() { mThread.join(); if ( mFormatContext ) { -#if LIBAVFORMAT_VERSION_CHECK(52, 96, 0, 96, 0) avformat_free_context(mFormatContext); -#else - av_free_format_context(mFormatContext); -#endif mFormatContext = nullptr; } if ( mSessDesc ) { @@ -337,7 +330,8 @@ void RtspThread::Run() { authTried = true; sendCommand(message); // FIXME Why sleep 1? - usleep(10000); + std::this_thread::sleep_for(Microseconds(10)); + res = recvResponse(response); if ( !res && respCode==401 ) mNeedAuth = true; @@ -398,14 +392,7 @@ void RtspThread::Run() { if ( mFormatContext->nb_streams >= 1 ) { for ( unsigned int i = 0; i < mFormatContext->nb_streams; i++ ) { SessionDescriptor::MediaDescriptor *mediaDesc = mSessDesc->getStream(i); -#if LIBAVFORMAT_VERSION_CHECK(57, 33, 0, 33, 0) - if ( mFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ) -#elif (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) - if ( mFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO ) -#else - if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO ) -#endif - { + if (mFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { // Check if control Url is absolute or relative controlUrl = mediaDesc->getControlUrl(); if (trackUrl == controlUrl) { @@ -418,11 +405,7 @@ void RtspThread::Run() { } } rtpClock = mediaDesc->getClock(); -#if LIBAVFORMAT_VERSION_CHECK(57, 33, 0, 33, 0) codecId = mFormatContext->streams[i]->codecpar->codec_id; -#else - codecId = mFormatContext->streams[i]->codec->codec_id; -#endif break; } // end if is video } // end foreach stream @@ -455,15 +438,18 @@ void RtspThread::Run() { lines = Split(response, "\r\n"); std::string session; - int timeout = 0; + Seconds timeout = Seconds(0); char transport[256] = ""; for ( size_t i = 0; i < lines.size(); i++ ) { if ( ( lines[i].size() > 8 ) && ( lines[i].substr(0, 8) == "Session:" ) ) { StringVector sessionLine = Split(lines[i].substr(9), ";"); session = TrimSpaces(sessionLine[0]); - if ( sessionLine.size() == 2 ) - sscanf(TrimSpaces(sessionLine[1]).c_str(), "timeout=%d", &timeout); + if ( sessionLine.size() == 2 ){ + int32 timeout_val = 0; + sscanf(TrimSpaces(sessionLine[1]).c_str(), "timeout=%d", &timeout_val); + timeout = Seconds(timeout_val); + } } sscanf(lines[i].c_str(), "Transport: %s", transport); } @@ -471,7 +457,9 @@ void RtspThread::Run() { if ( session.empty() ) Fatal("Unable to get session identifier from response '%s'", response.c_str()); - Debug(2, "Got RTSP session %s, timeout %d secs", session.c_str(), timeout); + Debug(2, "Got RTSP session %s, timeout %" PRIi64 " secs", + session.c_str(), + static_cast(Seconds(timeout).count())); if ( !transport[0] ) Fatal("Unable to get transport details from response '%s'", response.c_str()); @@ -534,12 +522,18 @@ void RtspThread::Run() { if ( ( lines[i].size() > 9 ) && ( lines[i].substr(0, 9) == "RTP-Info:" ) ) rtpInfo = TrimSpaces(lines[i].substr(9)); // Check for a timeout again. Some rtsp devices don't send a timeout until after the PLAY command is sent - if ( ( lines[i].size() > 8 ) && ( lines[i].substr(0, 8) == "Session:" ) && ( timeout == 0 ) ) { + if ((lines[i].size() > 8) && (lines[i].substr(0, 8) == "Session:") && (timeout == Seconds(0))) { StringVector sessionLine = Split(lines[i].substr(9), ";"); - if ( sessionLine.size() == 2 ) - sscanf(TrimSpaces(sessionLine[1]).c_str(), "timeout=%d", &timeout); - if ( timeout > 0 ) - Debug(2, "Got timeout %d secs from PLAY command response", timeout); + if ( sessionLine.size() == 2 ){ + int32 timeout_val = 0; + sscanf(TrimSpaces(sessionLine[1]).c_str(), "timeout=%d", &timeout_val); + timeout = Seconds(timeout_val); + } + + if ( timeout > Seconds(0) ) { + Debug(2, "Got timeout %" PRIi64 " secs from PLAY command response", + static_cast(Seconds(timeout).count())); + } } } @@ -574,8 +568,8 @@ void RtspThread::Run() { Debug( 2, "RTSP Seq is %d", seq ); Debug( 2, "RTSP Rtptime is %ld", rtpTime ); - time_t lastKeepalive = time(nullptr); - time_t now; + TimePoint lastKeepalive = std::chrono::steady_clock::now(); + TimePoint now; message = "GET_PARAMETER "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n"; switch( mMethod ) { @@ -587,20 +581,21 @@ void RtspThread::Run() { RtpCtrlThread rtpCtrlThread( *this, *source ); while (!mTerminate) { - now = time(nullptr); + now = std::chrono::steady_clock::now(); // Send a keepalive message if the server supports this feature and we are close to the timeout expiration - Debug(5, "sendkeepalive %d, timeout %d, now: %" PRIi64 " last: %" PRIi64 " since: %" PRIi64, + Debug(5, "sendkeepalive %d, timeout %" PRIi64 " s, now: %" PRIi64 " s last: %" PRIi64 " s since: %" PRIi64 "s ", sendKeepalive, - timeout, - static_cast(now), - static_cast(lastKeepalive), - static_cast(now - lastKeepalive)); - if ( sendKeepalive && (timeout > 0) && ((now-lastKeepalive) > (timeout-5)) ) { - if ( !sendCommand( message ) ) + static_cast(Seconds(timeout).count()), + static_cast(std::chrono::duration_cast(now.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(lastKeepalive.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast((now - lastKeepalive)).count())); + + if (sendKeepalive && (timeout > Seconds(0)) && ((now - lastKeepalive) > (timeout - Seconds(5)))) { + if (!sendCommand(message)) return; lastKeepalive = now; } - usleep( 100000 ); + std::this_thread::sleep_for(Microseconds(100)); } #if 0 message = "PAUSE "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n"; @@ -638,14 +633,14 @@ void RtspThread::Run() { RtpDataThread rtpDataThread( *this, *source ); RtpCtrlThread rtpCtrlThread( *this, *source ); - ZM::Select select( double(config.http_timeout)/1000.0 ); + zm::Select select(Milliseconds(config.http_timeout)); select.addReader( &mRtspSocket ); Buffer buffer( ZM_NETWORK_BUFSIZ ); std::string keepaliveMessage = "OPTIONS "+mUrl+" RTSP/1.0\r\n"; std::string keepaliveResponse = "RTSP/1.0 200 OK\r\n"; while (!mTerminate && select.wait() >= 0) { - ZM::Select::CommsList readable = select.getReadable(); + zm::Select::CommsList readable = select.getReadable(); if ( readable.size() == 0 ) { Error( "RTSP timed out" ); break; @@ -711,21 +706,23 @@ void RtspThread::Run() { } // Send a keepalive message if the server supports this feature and we are close to the timeout expiration // FIXME: Is this really necessary when using tcp ? - now = time(nullptr); + now = std::chrono::steady_clock::now(); // Send a keepalive message if the server supports this feature and we are close to the timeout expiration - Debug(5, "sendkeepalive %d, timeout %d, now: %" PRIi64 " last: %" PRIi64 " since: %" PRIi64, + Debug(5, "sendkeepalive %d, timeout %" PRIi64 " s, now: %" PRIi64 " s last: %" PRIi64 " s since: %" PRIi64 " s", sendKeepalive, - timeout, - static_cast(now), - static_cast(lastKeepalive), - static_cast(now - lastKeepalive)); - if ( sendKeepalive && (timeout > 0) && ((now-lastKeepalive) > (timeout-5)) ) - { - if ( !sendCommand( message ) ) + static_cast(Seconds(timeout).count()), + static_cast(std::chrono::duration_cast(now.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(lastKeepalive.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast((now - lastKeepalive)).count())); + + if (sendKeepalive && (timeout > Seconds(0)) && ((now - lastKeepalive) > (timeout - Seconds(5)))) { + if (!sendCommand(message)) { return; + } + lastKeepalive = now; } - buffer.tidy( 1 ); + buffer.tidy(true); } #if 0 message = "PAUSE "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n"; @@ -754,12 +751,14 @@ void RtspThread::Run() { while (!mTerminate) { // Send a keepalive message if the server supports this feature and we are close to the timeout expiration - if ( sendKeepalive && (timeout > 0) && ((time(nullptr)-lastKeepalive) > (timeout-5)) ) { - if ( !sendCommand( message ) ) + if (sendKeepalive && (timeout > Seconds(0)) + && ((std::chrono::steady_clock::now() - lastKeepalive) > (timeout - Seconds(5)))) { + if (!sendCommand(message)) { return; - lastKeepalive = time(nullptr); + } + lastKeepalive = std::chrono::steady_clock::now(); } - usleep(100000); + std::this_thread::sleep_for(Microseconds(100)); } #if 0 message = "PAUSE "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n"; @@ -790,5 +789,3 @@ void RtspThread::Run() { return; } - -#endif // HAVE_LIBAVFORMAT diff --git a/src/zm_rtsp.h b/src/zm_rtsp.h index 6cb88d091..96906e209 100644 --- a/src/zm_rtsp.h +++ b/src/zm_rtsp.h @@ -68,8 +68,8 @@ private: std::string mHttpSession; ///< Only for RTSP over HTTP sessions - ZM::TcpInetClient mRtspSocket; - ZM::TcpInetClient mRtspSocket2; + zm::TcpInetClient mRtspSocket; + zm::TcpInetClient mRtspSocket2; SourceMap mSources; diff --git a/src/zm_rtsp_auth.cpp b/src/zm_rtsp_auth.cpp index 4a1c6b2e2..cf2be15f4 100644 --- a/src/zm_rtsp_auth.cpp +++ b/src/zm_rtsp_auth.cpp @@ -18,29 +18,20 @@ #include "zm_rtsp_auth.h" +#include "zm_crypt.h" #include "zm_logger.h" #include "zm_utils.h" #include +#include namespace zm { -Authenticator::Authenticator( const std::string &username, const std::string &password) : - fCnonce("0a4f113b"), - fUsername(username), - fPassword(password) - { -#ifdef HAVE_GCRYPT_H - // Special initialisation for libgcrypt - if ( !gcry_check_version(GCRYPT_VERSION) ) { - Fatal("Unable to initialise libgcrypt"); - } - gcry_control( GCRYCTL_DISABLE_SECMEM, 0 ); - gcry_control( GCRYCTL_INITIALIZATION_FINISHED, 0 ); -#endif // HAVE_GCRYPT_H - - fAuthMethod = AUTH_UNDEFINED; - nc = 1; -} +Authenticator::Authenticator(std::string username, std::string password) + : fAuthMethod(AUTH_UNDEFINED), + fCnonce("0a4f113b"), + fUsername(std::move(username)), + fPassword(std::move(password)), + nc(1) {} Authenticator::~Authenticator() { reset(); @@ -95,7 +86,7 @@ std::string Authenticator::quote( const std::string &src ) { return ReplaceAll(ReplaceAll(src, "\\", "\\\\"), "\"", "\\\""); } -std::string Authenticator::getAuthHeader(std::string method, std::string uri) { +std::string Authenticator::getAuthHeader(const std::string &method, const std::string &uri) { std::string result = "Authorization: "; if ( fAuthMethod == AUTH_BASIC ) { result += "Basic " + Base64Encode(username() + ":" + password()); @@ -128,68 +119,36 @@ std::string Authenticator::getAuthHeader(std::string method, std::string uri) { } std::string Authenticator::computeDigestResponse(const std::string &method, const std::string &uri) { -#if HAVE_DECL_MD5 || HAVE_DECL_GNUTLS_FINGERPRINT // The "response" field is computed as: // md5(md5(::)::md5(:)) - size_t md5len = 16; - unsigned char md5buf[md5len]; - char md5HexBuf[md5len*2+1]; - + // Step 1: md5(::) std::string ha1Data = username() + ":" + realm() + ":" + password(); - Debug( 2, "HA1 pre-md5: %s", ha1Data.c_str() ); -#if HAVE_DECL_MD5 - MD5((unsigned char*)ha1Data.c_str(), ha1Data.length(), md5buf); -#elif HAVE_DECL_GNUTLS_FINGERPRINT - gnutls_datum_t md5dataha1 = { (unsigned char*)ha1Data.c_str(), (unsigned int)ha1Data.length() }; - gnutls_fingerprint( GNUTLS_DIG_MD5, &md5dataha1, md5buf, &md5len ); -#endif - for ( unsigned int j = 0; j < md5len; j++ ) { - sprintf(&md5HexBuf[2*j], "%02x", md5buf[j] ); - } - md5HexBuf[md5len*2]='\0'; - std::string ha1Hash = md5HexBuf; - + Debug(2, "HA1 pre-md5: %s", ha1Data.c_str()); + + zm::crypto::MD5::Digest md5_digest = zm::crypto::MD5::GetDigestOf(ha1Data); + std::string ha1Hash = ByteArrayToHexString(md5_digest); + // Step 2: md5(:) std::string ha2Data = method + ":" + uri; - Debug( 2, "HA2 pre-md5: %s", ha2Data.c_str() ); -#if HAVE_DECL_MD5 - MD5((unsigned char*)ha2Data.c_str(), ha2Data.length(), md5buf ); -#elif HAVE_DECL_GNUTLS_FINGERPRINT - gnutls_datum_t md5dataha2 = { (unsigned char*)ha2Data.c_str(), (unsigned int)ha2Data.length() }; - gnutls_fingerprint( GNUTLS_DIG_MD5, &md5dataha2, md5buf, &md5len ); -#endif - for ( unsigned int j = 0; j < md5len; j++ ) { - sprintf( &md5HexBuf[2*j], "%02x", md5buf[j] ); - } - md5HexBuf[md5len*2]='\0'; - std::string ha2Hash = md5HexBuf; + Debug(2, "HA2 pre-md5: %s", ha2Data.c_str()); + + md5_digest = zm::crypto::MD5::GetDigestOf(ha2Data); + std::string ha2Hash = ByteArrayToHexString(md5_digest); // Step 3: md5(ha1::ha2) std::string digestData = ha1Hash + ":" + nonce(); - if ( ! fQop.empty() ) { - digestData += ":" + stringtf("%08x", nc) + ":"+fCnonce + ":" + fQop; - nc ++; + if (!fQop.empty()) { + digestData += ":" + stringtf("%08x", nc) + ":" + fCnonce + ":" + fQop; + nc++; // if qop was specified, then we have to include t and a cnonce and an nccount } digestData += ":" + ha2Hash; - Debug( 2, "pre-md5: %s", digestData.c_str() ); -#if HAVE_DECL_MD5 - MD5((unsigned char*)digestData.c_str(), digestData.length(), md5buf); -#elif HAVE_DECL_GNUTLS_FINGERPRINT - gnutls_datum_t md5datadigest = { (unsigned char*)digestData.c_str(), (unsigned int)digestData.length() }; - gnutls_fingerprint( GNUTLS_DIG_MD5, &md5datadigest, md5buf, &md5len ); -#endif - for ( unsigned int j = 0; j < md5len; j++ ) { - sprintf( &md5HexBuf[2*j], "%02x", md5buf[j] ); - } - md5HexBuf[md5len*2]='\0'; - - return md5HexBuf; -#else // HAVE_DECL_MD5 - Error("You need to build with gnutls or openssl installed to use digest authentication"); - return 0; -#endif // HAVE_DECL_MD5 + Debug(2, "pre-md5: %s", digestData.c_str()); + + md5_digest = zm::crypto::MD5::GetDigestOf(digestData); + + return ByteArrayToHexString(md5_digest); } void Authenticator::checkAuthResponse(const std::string &response) { diff --git a/src/zm_rtsp_auth.h b/src/zm_rtsp_auth.h index 701b4b128..52d186d26 100644 --- a/src/zm_rtsp_auth.h +++ b/src/zm_rtsp_auth.h @@ -22,22 +22,12 @@ #include "zm_config.h" #include -#if HAVE_GNUTLS_GNUTLS_H -#include -#endif - -#if HAVE_GCRYPT_H -#include -#elif HAVE_LIBCRYPTO -#include -#endif // HAVE_GCRYPT_H || HAVE_LIBCRYPTO - -namespace zm { +namespace zm { enum AuthMethod { AUTH_UNDEFINED = 0, AUTH_BASIC = 1, AUTH_DIGEST = 2 }; class Authenticator { public: - Authenticator(const std::string &username, const std::string &password); + Authenticator(std::string username, std::string password); virtual ~Authenticator(); void reset(); @@ -47,8 +37,8 @@ public: AuthMethod auth_method() const { return fAuthMethod; } std::string computeDigestResponse(const std::string &cmd, const std::string &url); - void authHandleHeader( std::string headerData ); - std::string getAuthHeader( std::string method, std::string path ); + void authHandleHeader(std::string headerData); + std::string getAuthHeader(const std::string &method, const std::string &path); void checkAuthResponse(const std::string &response); private: diff --git a/src/zm_rtsp_server.cpp b/src/zm_rtsp_server.cpp index 194cd57f2..2276850cd 100644 --- a/src/zm_rtsp_server.cpp +++ b/src/zm_rtsp_server.cpp @@ -91,11 +91,9 @@ int main(int argc, char *argv[]) { while (1) { int option_index = 0; - int c = getopt_long(argc, argv, "m:h:v", long_options, &option_index); - if ( c == -1 ) { + if (c == -1) break; - } switch (c) { case 'm': @@ -162,27 +160,23 @@ int main(int argc, char *argv[]) { exit(-1); } - //xop::MediaSession **sessions = new xop::MediaSession *[monitors.size()]; - - std::map sessions; - std::map video_sources; - std::map audio_sources; - std::map> monitors; - //std::vector> monitors; - //= Monitor::LoadMonitors(where, Monitor::QUERY); + std::unordered_map sessions; + std::unordered_map video_sources; + std::unordered_map audio_sources; + std::unordered_map> monitors; while (!zm_terminate) { - std::map> old_monitors = monitors; + std::unordered_map> old_monitors = monitors; std::vector> new_monitors = Monitor::LoadMonitors(where, Monitor::QUERY); for (const auto &monitor : new_monitors) { - if ( - (old_monitors.find(monitor->Id()) != old_monitors.end()) + auto old_monitor_it = old_monitors.find(monitor->Id()); + if (old_monitor_it != old_monitors.end() and - (old_monitors[monitor->Id()]->GetRTSPStreamName() == monitor->GetRTSPStreamName()) + (old_monitor_it->second->GetRTSPStreamName() == monitor->GetRTSPStreamName()) ) { Debug(1, "Found monitor in oldmonitors, clearing it"); - old_monitors.erase(monitor->Id()); + old_monitors.erase(old_monitor_it); } else { Debug(1, "Adding monitor %d to monitors", monitor->Id()); monitors[monitor->Id()] = monitor; @@ -204,8 +198,6 @@ int main(int argc, char *argv[]) { audio_sources.erase(monitor->Id()); } rtspServer->RemoveSession(sessions[mid]->GetMediaSessionId()); - //Debug(1, "Deleting session"); - //delete sessions[mid]; sessions.erase(mid); } } @@ -214,7 +206,7 @@ int main(int argc, char *argv[]) { auto &monitor = it->second; if (!monitor->ShmValid()) { - Debug(1, "monitor %d !shmvalid", monitor->Id()); + Debug(1, "!ShmValid"); monitor->disconnect(); if (!monitor->connect()) { Warning("Couldn't connect to monitor %d", monitor->Id()); @@ -226,14 +218,12 @@ int main(int argc, char *argv[]) { audio_sources.erase(monitor->Id()); } rtspServer->RemoveSession(sessions[monitor->Id()]->GetMediaSessionId()); - delete sessions[monitor->Id()]; sessions.erase(monitor->Id()); } - + monitor->Reload(); // This is to pickup change of colours, width, height, etc continue; - } - } - + } // end if failed to connect + } // end if !ShmValid if (sessions.end() == sessions.find(monitor->Id())) { Debug(1, "Monitor not found in sessions, opening it"); @@ -319,8 +309,7 @@ int main(int argc, char *argv[]) { } // end if ! sessions[monitor->Id()] } // end foreach monitor - - sleep(5); + sleep(10); if (zm_reload) { logTerm(); @@ -328,6 +317,7 @@ int main(int argc, char *argv[]) { zm_reload = false; } // end if zm_reload } // end while !zm_terminate + Info("RTSP Server shutting down"); for (const std::pair> &mon_pair : monitors) { diff --git a/src/zm_rtsp_server_adts_source.cpp b/src/zm_rtsp_server_adts_source.cpp index f6d1d81c0..1c0c1a5a4 100644 --- a/src/zm_rtsp_server_adts_source.cpp +++ b/src/zm_rtsp_server_adts_source.cpp @@ -34,13 +34,7 @@ ADTS_ZoneMinderDeviceSource::ADTS_ZoneMinderDeviceSource( : ZoneMinderDeviceSource(env, std::move(monitor), stream, queueSize), samplingFrequencyIndex(0), - channels( -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - stream->codecpar->channels -#else - stream->codec->channels -#endif - ) + channels(stream->codecpar->channels) { std::ostringstream os; os << diff --git a/src/zm_rtsp_server_adts_source.h b/src/zm_rtsp_server_adts_source.h index b6f906a12..bae93b8bd 100644 --- a/src/zm_rtsp_server_adts_source.h +++ b/src/zm_rtsp_server_adts_source.h @@ -45,13 +45,7 @@ class ADTS_ZoneMinderDeviceSource : public ZoneMinderDeviceSource { virtual unsigned char* findMarker(unsigned char *frame, size_t size, size_t &length); */ public: - int samplingFrequency() { return -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - m_stream->codecpar->sample_rate; -#else - m_stream->codec->sample_rate; -#endif - }; + int samplingFrequency() { return m_stream->codecpar->sample_rate; }; const char *configStr() { return config.c_str(); }; int numChannels() { return channels; diff --git a/src/zm_rtsp_server_device_source.cpp b/src/zm_rtsp_server_device_source.cpp index abd1f8381..ba648b2f4 100644 --- a/src/zm_rtsp_server_device_source.cpp +++ b/src/zm_rtsp_server_device_source.cpp @@ -23,13 +23,13 @@ ZoneMinderDeviceSource::ZoneMinderDeviceSource( unsigned int queueSize ) : FramedSource(env), + m_eventTriggerId(envir().taskScheduler().createEventTrigger(ZoneMinderDeviceSource::deliverFrameStub)), m_stream(stream), m_monitor(std::move(monitor)), m_packetqueue(nullptr), m_packetqueue_it(nullptr), m_queueSize(queueSize) { - m_eventTriggerId = envir().taskScheduler().createEventTrigger(ZoneMinderDeviceSource::deliverFrameStub); memset(&m_thid, 0, sizeof(m_thid)); memset(&m_mutex, 0, sizeof(m_mutex)); if ( m_monitor ) { diff --git a/src/zm_rtsp_server_fifo_adts_source.cpp b/src/zm_rtsp_server_fifo_adts_source.cpp index 6c01371e5..bc979d0a6 100644 --- a/src/zm_rtsp_server_fifo_adts_source.cpp +++ b/src/zm_rtsp_server_fifo_adts_source.cpp @@ -22,7 +22,7 @@ ADTS_ZoneMinderFifoSource::ADTS_ZoneMinderFifoSource( std::shared_ptr& rtspServer, xop::MediaSessionId sessionId, xop::MediaChannelId channelId, - std::string fifo + const std::string &fifo ) : ZoneMinderFifoAudioSource(rtspServer, sessionId, channelId, fifo) diff --git a/src/zm_rtsp_server_fifo_adts_source.h b/src/zm_rtsp_server_fifo_adts_source.h index 0bbe0890c..4a8f86e85 100644 --- a/src/zm_rtsp_server_fifo_adts_source.h +++ b/src/zm_rtsp_server_fifo_adts_source.h @@ -26,7 +26,7 @@ class ADTS_ZoneMinderFifoSource : public ZoneMinderFifoAudioSource { std::shared_ptr& rtspServer, xop::MediaSessionId sessionId, xop::MediaChannelId channelId, - std::string fifo + const std::string &fifo ); virtual ~ADTS_ZoneMinderFifoSource() {} diff --git a/src/zm_rtsp_server_fifo_h264_source.cpp b/src/zm_rtsp_server_fifo_h264_source.cpp index 9963ea26c..b96f3da8b 100644 --- a/src/zm_rtsp_server_fifo_h264_source.cpp +++ b/src/zm_rtsp_server_fifo_h264_source.cpp @@ -23,7 +23,7 @@ H264_ZoneMinderFifoSource::H264_ZoneMinderFifoSource( std::shared_ptr& rtspServer, xop::MediaSessionId sessionId, xop::MediaChannelId channelId, - std::string fifo + const std::string &fifo ) : H26X_ZoneMinderFifoSource(rtspServer, sessionId, channelId, fifo) { @@ -96,7 +96,7 @@ H265_ZoneMinderFifoSource::H265_ZoneMinderFifoSource( std::shared_ptr& rtspServer, xop::MediaSessionId sessionId, xop::MediaChannelId channelId, - std::string fifo + const std::string &fifo ) : H26X_ZoneMinderFifoSource(rtspServer, sessionId, channelId, fifo) { @@ -199,12 +199,12 @@ unsigned char* H26X_ZoneMinderFifoSource::extractFrame(unsigned char* frame, si Debug(4, "ExtractFrame: %p %zu", frame, size); outsize = 0; size_t markerLength = 0; - size_t endMarkerLength = 0; m_frameType = 0; unsigned char *startFrame = nullptr; - if ( size >= 3 ) + if (size >= 3) startFrame = this->findMarker(frame, size, markerLength); - if ( startFrame != nullptr ) { + if (startFrame != nullptr) { + size_t endMarkerLength = 0; Debug(4, "startFrame: %p marker Length %zu", startFrame, markerLength); m_frameType = startFrame[markerLength]; diff --git a/src/zm_rtsp_server_fifo_h264_source.h b/src/zm_rtsp_server_fifo_h264_source.h index d771c9ac7..cf605f40a 100644 --- a/src/zm_rtsp_server_fifo_h264_source.h +++ b/src/zm_rtsp_server_fifo_h264_source.h @@ -50,7 +50,7 @@ class H264_ZoneMinderFifoSource : public H26X_ZoneMinderFifoSource { std::shared_ptr& rtspServer, xop::MediaSessionId sessionId, xop::MediaChannelId channelId, - std::string fifo + const std::string &fifo ); // overide ZoneMinderFifoSource @@ -63,7 +63,7 @@ class H265_ZoneMinderFifoSource : public H26X_ZoneMinderFifoSource { std::shared_ptr& rtspServer, xop::MediaSessionId sessionId, xop::MediaChannelId channelId, - std::string fifo + const std::string &fifo ); // overide ZoneMinderFifoSource diff --git a/src/zm_rtsp_server_fifo_source.cpp b/src/zm_rtsp_server_fifo_source.cpp index 6a6066bcf..40dbc14fc 100644 --- a/src/zm_rtsp_server_fifo_source.cpp +++ b/src/zm_rtsp_server_fifo_source.cpp @@ -24,7 +24,7 @@ ZoneMinderFifoSource::ZoneMinderFifoSource( std::shared_ptr& rtspServer, xop::MediaSessionId sessionId, xop::MediaChannelId channelId, - std::string fifo + const std::string &fifo ) : stop_(false), m_rtspServer(rtspServer), @@ -53,7 +53,7 @@ void ZoneMinderFifoSource::ReadRun() { if (stop_) Warning("bad value for stop_ in ReadRun"); while (!stop_) { if (getNextFrame() < 0) { - Debug(1, "Sleeping"); + Debug(1, "Sleeping because couldn't getNextFrame"); sleep(1); } } @@ -78,7 +78,7 @@ void ZoneMinderFifoSource::WriteRun() { if (nal) { if (1 and (nal->size() > maxNalSize)) { - Debug(1, "Splitting NAL %zu", nal->size()); + Debug(3, "Splitting NAL %zu", nal->size()); size_t nalRemaining = nal->size(); u_int8_t *nalSrc = nal->buffer(); @@ -123,7 +123,7 @@ void ZoneMinderFifoSource::WriteRun() { nalSrc += fuNalSize; nal_count += 1; } - Debug(1, "Sending %d NALs @ %zu and 1 @ %zu", nal_count, maxNalSize, fuNal.size()); + Debug(3, "Sending %d NALs @ %zu and 1 @ %zu", nal_count, maxNalSize, fuNal.size()); } else { Debug(3, "Pushing nal of size %zu at %" PRId64, nal->size(), nal->pts()); PushFrame(nal->buffer(), nal->size(), nal->pts()); @@ -151,7 +151,7 @@ int ZoneMinderFifoSource::getNextFrame() { } } - int bytes_read = m_buffer.read_into(m_fd, 32); + int bytes_read = m_buffer.read_into(m_fd, 4096); //int bytes_read = m_buffer.read_into(m_fd, 4096, {1,0}); if (bytes_read == 0) { Debug(3, "No bytes read"); @@ -209,7 +209,7 @@ int ZoneMinderFifoSource::getNextFrame() { Debug(4, "ZM Packet %s header_size %d packet size %u pts %s %" PRId64, header, header_size, data_size, pts_ptr, pts); delete[] header; } else { - Debug(1, "ZM header not found in %d of buffer:%s.", m_buffer.size(), m_buffer.head()); + Debug(1, "ZM header not found in %u of buffer.", m_buffer.size()); m_buffer.clear(); return 0; } @@ -225,7 +225,7 @@ int ZoneMinderFifoSource::getNextFrame() { if (bytes_needed > 0) { Debug(4, "Need another %d bytes. Trying to read them", bytes_needed); while (bytes_needed) { - int bytes_read = m_buffer.read_into(m_fd, bytes_needed); + 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); return -1; @@ -259,7 +259,6 @@ int ZoneMinderFifoSource::getNextFrame() { m_nalQueue.push(Nal); } } - Debug(1, "notifying"); condition_.notify_all(); } // end while m_buffer.size() return 1; diff --git a/src/zm_rtsp_server_fifo_source.h b/src/zm_rtsp_server_fifo_source.h index eb9fd4fe9..eea7c8230 100644 --- a/src/zm_rtsp_server_fifo_source.h +++ b/src/zm_rtsp_server_fifo_source.h @@ -35,7 +35,7 @@ class ZoneMinderFifoSource { std::shared_ptr& rtspServer, xop::MediaSessionId sessionId, xop::MediaChannelId channelId, - std::string fifo + const std::string &fifo ); virtual ~ZoneMinderFifoSource(); diff --git a/src/zm_rtsp_server_fifo_video_source.cpp b/src/zm_rtsp_server_fifo_video_source.cpp index df8c32955..4c5b382bd 100644 --- a/src/zm_rtsp_server_fifo_video_source.cpp +++ b/src/zm_rtsp_server_fifo_video_source.cpp @@ -15,9 +15,11 @@ ZoneMinderFifoVideoSource::ZoneMinderFifoVideoSource( std::shared_ptr& rtspServer, xop::MediaSessionId sessionId, xop::MediaChannelId channelId, - std::string fifo + const std::string &fifo ) : - ZoneMinderFifoSource(rtspServer, sessionId, channelId, fifo) + ZoneMinderFifoSource(rtspServer, sessionId, channelId, fifo), + m_width(0), + m_height(0) { m_timeBase = {1, 90000}; } diff --git a/src/zm_rtsp_server_fifo_video_source.h b/src/zm_rtsp_server_fifo_video_source.h index 765b79092..8bbef12f5 100644 --- a/src/zm_rtsp_server_fifo_video_source.h +++ b/src/zm_rtsp_server_fifo_video_source.h @@ -25,7 +25,7 @@ class ZoneMinderFifoVideoSource: public ZoneMinderFifoSource { std::shared_ptr& rtspServer, xop::MediaSessionId sessionId, xop::MediaChannelId channelId, - std::string fifo + const std::string &fifo ); protected: void PushFrame(const uint8_t *data, size_t size, int64_t pts) override; diff --git a/src/zm_rtsp_server_h264_device_source.cpp b/src/zm_rtsp_server_h264_device_source.cpp deleted file mode 100644 index 71b641021..000000000 --- a/src/zm_rtsp_server_h264_device_source.cpp +++ /dev/null @@ -1,241 +0,0 @@ -/* --------------------------------------------------------------------------- -** -** H264_DeviceSource.cpp -** -** H264 Live555 source -** -** -------------------------------------------------------------------------*/ - -#include "zm_rtsp_server_h264_device_source.h" - -#include "zm_config.h" -#include "zm_logger.h" -#include "zm_rtsp_server_frame.h" -#include -#include - -#if HAVE_RTSP_SERVER -// live555 -#include - -// --------------------------------- -// H264 ZoneMinder FramedSource -// --------------------------------- -// -H264_ZoneMinderDeviceSource::H264_ZoneMinderDeviceSource( - UsageEnvironment& env, - std::shared_ptr monitor, - AVStream *stream, - unsigned int queueSize, - bool repeatConfig, - bool keepMarker) - : H26X_ZoneMinderDeviceSource(env, std::move(monitor), stream, queueSize, repeatConfig, keepMarker) -{ - // extradata appears to simply be the SPS and PPS NAL's - this->splitFrames( -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - m_stream->codecpar->extradata, m_stream->codecpar->extradata_size -#else - m_stream->codec->extradata, m_stream->codec->extradata_size -#endif - ); -} - -// split packet into frames -std::list< std::pair > H264_ZoneMinderDeviceSource::splitFrames(unsigned char* frame, unsigned frameSize) { - std::list< std::pair > frameList; - - size_t bufSize = frameSize; - size_t size = 0; - unsigned char* buffer = this->extractFrame(frame, bufSize, size); - while ( buffer != nullptr ) { - switch ( m_frameType & 0x1F ) { - case 7: - Debug(1, "SPS_Size: %d bufSize %d", size, bufSize); - m_sps.assign((char*)buffer, size); - break; - case 8: - Debug(1, "PPS_Size: %d bufSize %d", size, bufSize); - m_pps.assign((char*)buffer, size); - break; - case 5: - Debug(1, "IDR_Size: %d bufSize %d", size, bufSize); - if ( m_repeatConfig && !m_sps.empty() && !m_pps.empty() ) { - frameList.push_back(std::pair((unsigned char*)m_sps.c_str(), m_sps.size())); - frameList.push_back(std::pair((unsigned char*)m_pps.c_str(), m_pps.size())); - } - break; - default: - Debug(1, "Unknown frametype!? %d %d", m_frameType, m_frameType & 0x1F); - break; - } - - if ( !m_sps.empty() && !m_pps.empty() ) { - u_int32_t profile_level_id = 0; - if ( m_sps.size() >= 4 ) profile_level_id = (m_sps[1]<<16)|(m_sps[2]<<8)|m_sps[3]; - - char* sps_base64 = base64Encode(m_sps.c_str(), m_sps.size()); - char* pps_base64 = base64Encode(m_pps.c_str(), m_pps.size()); - - std::ostringstream os; - os << "profile-level-id=" << std::hex << std::setw(6) << std::setfill('0') << profile_level_id; - os << ";sprop-parameter-sets=" << sps_base64 << "," << pps_base64; - m_auxLine.assign(os.str()); - Debug(1, "auxLine: %s", m_auxLine.c_str()); - - delete [] sps_base64; - delete [] pps_base64; - } - frameList.push_back(std::pair(buffer, size)); - - buffer = this->extractFrame(&buffer[size], bufSize, size); - } // end while buffer - return frameList; -} - -H265_ZoneMinderDeviceSource::H265_ZoneMinderDeviceSource( - UsageEnvironment& env, - std::shared_ptr monitor, - AVStream *stream, - unsigned int queueSize, - bool repeatConfig, - bool keepMarker) - : H26X_ZoneMinderDeviceSource(env, std::move(monitor), stream, queueSize, repeatConfig, keepMarker) -{ - // extradata appears to simply be the SPS and PPS NAL's - this->splitFrames( -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - m_stream->codecpar->extradata, m_stream->codecpar->extradata_size -#else - m_stream->codec->extradata, m_stream->codec->extradata_size -#endif - ); -} - -// split packet in frames -std::list< std::pair > -H265_ZoneMinderDeviceSource::splitFrames(unsigned char* frame, unsigned frameSize) { - std::list< std::pair > frameList; - - size_t bufSize = frameSize; - size_t size = 0; - unsigned char* buffer = this->extractFrame(frame, bufSize, size); - while ( buffer != nullptr ) { - switch ((m_frameType&0x7E)>>1) { - case 32: - Debug(1, "VPS_Size: %d bufSize %d", size, bufSize); - m_vps.assign((char*)buffer,size); - break; - case 33: - Debug(1, "SPS_Size: %d bufSize %d", size, bufSize); - m_sps.assign((char*)buffer,size); - break; - case 34: - Debug(1, "PPS_Size: %d bufSize %d", size, bufSize); - m_pps.assign((char*)buffer,size); - break; - case 19: - case 20: - Debug(1, "IDR_Size: %d bufSize %d", size, bufSize); - if ( m_repeatConfig && !m_vps.empty() && !m_sps.empty() && !m_pps.empty() ) { - frameList.push_back(std::pair((unsigned char*)m_vps.c_str(), m_vps.size())); - frameList.push_back(std::pair((unsigned char*)m_sps.c_str(), m_sps.size())); - frameList.push_back(std::pair((unsigned char*)m_pps.c_str(), m_pps.size())); - } - break; - default: - Debug(1, "Unknown frametype!? %d %d", m_frameType, ((m_frameType & 0x7E) >> 1)); - break; - } - - if ( !m_vps.empty() && !m_sps.empty() && !m_pps.empty() ) { - char* vps_base64 = base64Encode(m_vps.c_str(), m_vps.size()); - char* sps_base64 = base64Encode(m_sps.c_str(), m_sps.size()); - char* pps_base64 = base64Encode(m_pps.c_str(), m_pps.size()); - - std::ostringstream os; - os << "sprop-vps=" << vps_base64; - os << ";sprop-sps=" << sps_base64; - os << ";sprop-pps=" << pps_base64; - m_auxLine.assign(os.str()); - Debug(1, "Assigned auxLine to %s", m_auxLine.c_str()); - - delete [] vps_base64; - delete [] sps_base64; - delete [] pps_base64; - } - frameList.push_back(std::pair(buffer, size)); - - buffer = this->extractFrame(&buffer[size], bufSize, size); - } // end while buffer - if ( bufSize ) { - Debug(1, "%d bytes remaining", bufSize); - } - return frameList; -} // end H265_ZoneMinderDeviceSource::splitFrames(unsigned char* frame, unsigned frameSize) - -unsigned char * H26X_ZoneMinderDeviceSource::findMarker( - unsigned char *frame, size_t size, size_t &length - ) { - //Debug(1, "findMarker %p %d", frame, size); - unsigned char *start = nullptr; - for ( size_t i = 0; i < size-2; i += 1 ) { - //Debug(1, "%d: %d %d %d", i, frame[i], frame[i+1], frame[i+2]); - if ( (frame[i] == 0) and (frame[i+1]) == 0 and (frame[i+2] == 1) ) { - if ( i and (frame[i-1] == 0) ) { - start = frame + i - 1; - length = sizeof(H264marker); - } else { - start = frame + i; - length = sizeof(H264shortmarker); - } - break; - } - } - return start; -} - -// extract a frame -unsigned char* H26X_ZoneMinderDeviceSource::extractFrame(unsigned char* frame, size_t& size, size_t& outsize) { - unsigned char *outFrame = nullptr; - Debug(1, "ExtractFrame: %p %d", frame, size); - outsize = 0; - size_t markerLength = 0; - size_t endMarkerLength = 0; - m_frameType = 0; - unsigned char *startFrame = nullptr; - if ( size >= 3 ) - startFrame = this->findMarker(frame, size, markerLength); - if ( startFrame != nullptr ) { - Debug(1, "startFrame: %p marker Length %d", startFrame, markerLength); - m_frameType = startFrame[markerLength]; - - int remainingSize = size-(startFrame-frame+markerLength); - unsigned char *endFrame = nullptr; - if ( remainingSize > 3 ) { - endFrame = this->findMarker(startFrame+markerLength, remainingSize, endMarkerLength); - } - Debug(1, "endFrame: %p marker Length %d, remaining size %d", endFrame, endMarkerLength, remainingSize); - - if ( m_keepMarker ) { - size -= startFrame-frame; - outFrame = startFrame; - } else { - size -= startFrame-frame+markerLength; - outFrame = &startFrame[markerLength]; - } - - if ( endFrame != nullptr ) { - outsize = endFrame - outFrame; - } else { - outsize = size; - } - size -= outsize; - Debug(1, "Have frame type: %d size %d, keepmarker %d", m_frameType, outsize, m_keepMarker); - } else if ( size >= sizeof(H264shortmarker) ) { - Info("No marker found"); - } - - return outFrame; -} -#endif // HAVE_RTSP_SERVER diff --git a/src/zm_rtsp_server_h264_device_source.h b/src/zm_rtsp_server_h264_device_source.h deleted file mode 100644 index 5c01717c5..000000000 --- a/src/zm_rtsp_server_h264_device_source.h +++ /dev/null @@ -1,104 +0,0 @@ -/* --------------------------------------------------------------------------- -** This software is in the public domain, furnished "as is", without technical -** support, and with no warranty, express or implied, as to its usefulness for -** any purpose. -** -** H264_ZoneMinderDeviceSource.h -** -** H264 ZoneMinder live555 source -** -** -------------------------------------------------------------------------*/ - -#ifndef ZM_RTSP_H264_DEVICE_SOURCE_H -#define ZM_RTSP_H264_DEVICE_SOURCE_H - -#include "zm_config.h" -#include "zm_rtsp_server_device_source.h" - -// --------------------------------- -// H264 ZoneMinder FramedSource -// --------------------------------- -#if HAVE_RTSP_SERVER -class H26X_ZoneMinderDeviceSource : public ZoneMinderDeviceSource { - protected: - H26X_ZoneMinderDeviceSource( - UsageEnvironment& env, - std::shared_ptr monitor, - AVStream *stream, - unsigned int queueSize, - bool repeatConfig, - bool keepMarker) - : - ZoneMinderDeviceSource(env, std::move(monitor), stream, queueSize), - m_repeatConfig(repeatConfig), - m_keepMarker(keepMarker), - m_frameType(0) { } - - virtual ~H26X_ZoneMinderDeviceSource() {} - - virtual unsigned char* extractFrame(unsigned char* frame, size_t& size, size_t& outsize); - virtual unsigned char* findMarker(unsigned char *frame, size_t size, size_t &length); - - protected: - std::string m_sps; - std::string m_pps; - bool m_repeatConfig; - bool m_keepMarker; - int m_frameType; -}; - -class H264_ZoneMinderDeviceSource : public H26X_ZoneMinderDeviceSource { - public: - static H264_ZoneMinderDeviceSource* createNew( - UsageEnvironment& env, - std::shared_ptr monitor, - AVStream *stream, - unsigned int queueSize, - bool repeatConfig, - bool keepMarker) { - return new H264_ZoneMinderDeviceSource(env, std::move(monitor), stream, queueSize, repeatConfig, keepMarker); - } - - protected: - H264_ZoneMinderDeviceSource( - UsageEnvironment& env, - std::shared_ptr monitor, - AVStream *stream, - unsigned int queueSize, - bool repeatConfig, - bool keepMarker); - - // overide ZoneMinderDeviceSource - virtual std::list< std::pair > splitFrames(unsigned char* frame, unsigned frameSize); -}; - -class H265_ZoneMinderDeviceSource : public H26X_ZoneMinderDeviceSource { - public: - static H265_ZoneMinderDeviceSource* createNew( - UsageEnvironment& env, - std::shared_ptr monitor, - AVStream *stream, - unsigned int queueSize, - bool repeatConfig, - bool keepMarker) { - return new H265_ZoneMinderDeviceSource(env, std::move(monitor), stream, queueSize, repeatConfig, keepMarker); - } - - protected: - H265_ZoneMinderDeviceSource( - UsageEnvironment& env, - std::shared_ptr monitor, - AVStream *stream, - unsigned int queueSize, - bool repeatConfig, - bool keepMarker); - - // overide ZoneMinderDeviceSource - virtual std::list< std::pair > splitFrames(unsigned char* frame, unsigned frameSize); - - protected: - std::string m_vps; -}; -#endif // HAVE_RTSP_SERVER - -#endif // ZM_RTSP_H264_DEVICE_SOURCE_H diff --git a/src/zm_rtsp_server_server_media_subsession.cpp b/src/zm_rtsp_server_server_media_subsession.cpp index 141e8dad1..c847b471e 100644 --- a/src/zm_rtsp_server_server_media_subsession.cpp +++ b/src/zm_rtsp_server_server_media_subsession.cpp @@ -66,8 +66,9 @@ RTPSink* BaseServerMediaSubsession::createSink( sink = VP9VideoRTPSink::createNew(env, rtpGroupsock, rtpPayloadTypeIfDynamic); } else if (format == "video/H265") { sink = H265VideoRTPSink::createNew(env, rtpGroupsock, rtpPayloadTypeIfDynamic); + } #endif - } else if (format == "audio/AAC") { + else if (format == "audio/AAC") { ADTS_ZoneMinderFifoSource *adts_source = (ADTS_ZoneMinderFifoSource *)(m_replicator->inputSource()); sink = MPEG4GenericRTPSink::createNew(env, rtpGroupsock, rtpPayloadTypeIfDynamic, diff --git a/src/zm_sdp.cpp b/src/zm_sdp.cpp index 63bb8d9bd..bdbc9e1d1 100644 --- a/src/zm_sdp.cpp +++ b/src/zm_sdp.cpp @@ -23,9 +23,6 @@ #include "zm_exception.h" #include "zm_logger.h" -#if HAVE_LIBAVFORMAT - -#if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) SessionDescriptor::StaticPayloadDesc SessionDescriptor::smStaticPayloads[] = { { 0, "PCMU", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_PCM_MULAW, 8000, 1 }, { 3, "GSM", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_NONE, 8000, 1 }, @@ -63,45 +60,6 @@ SessionDescriptor::DynamicPayloadDesc SessionDescriptor::smDynamicPayloads[] = { { "AMR", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AMR_NB }, { "vnd.onvif.metadata", AVMEDIA_TYPE_DATA, AV_CODEC_ID_NONE } }; -#else -SessionDescriptor::StaticPayloadDesc SessionDescriptor::smStaticPayloads[] = { - { 0, "PCMU", CODEC_TYPE_AUDIO, CODEC_ID_PCM_MULAW, 8001, 1 }, - { 3, "GSM", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 8000, 1 }, - { 4, "G723", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 8000, 1 }, - { 5, "DVI4", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 8000, 1 }, - { 6, "DVI4", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 16000, 1 }, - { 7, "LPC", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 8000, 1 }, - { 8, "PCMA", CODEC_TYPE_AUDIO, CODEC_ID_PCM_ALAW, 8000, 1 }, - { 9, "G722", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 8000, 1 }, - { 10, "L16", CODEC_TYPE_AUDIO, CODEC_ID_PCM_S16BE, 44100, 2 }, - { 11, "L16", CODEC_TYPE_AUDIO, CODEC_ID_PCM_S16BE, 44100, 1 }, - { 12, "QCELP", CODEC_TYPE_AUDIO, CODEC_ID_QCELP, 8000, 1 }, - { 13, "CN", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 8000, 1 }, - { 14, "MPA", CODEC_TYPE_AUDIO, CODEC_ID_MP2, -1, -1 }, - { 14, "MPA", CODEC_TYPE_AUDIO, CODEC_ID_MP3, -1, -1 }, - { 15, "G728", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 8000, 1 }, - { 16, "DVI4", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 11025, 1 }, - { 17, "DVI4", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 22050, 1 }, - { 18, "G729", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 8000, 1 }, - { 25, "CelB", CODEC_TYPE_VIDEO, CODEC_ID_NONE, 90000, -1 }, - { 26, "JPEG", CODEC_TYPE_VIDEO, CODEC_ID_MJPEG, 90000, -1 }, - { 28, "nv", CODEC_TYPE_VIDEO, CODEC_ID_NONE, 90000, -1 }, - { 31, "H261", CODEC_TYPE_VIDEO, CODEC_ID_H261, 90000, -1 }, - { 32, "MPV", CODEC_TYPE_VIDEO, CODEC_ID_MPEG1VIDEO, 90000, -1 }, - { 32, "MPV", CODEC_TYPE_VIDEO, CODEC_ID_MPEG2VIDEO, 90000, -1 }, - { 33, "MP2T", CODEC_TYPE_DATA, CODEC_ID_MPEG2TS, 90000, -1 }, - { 34, "H263", CODEC_TYPE_VIDEO, CODEC_ID_H263, 90000, -1 }, - { -1, "", CODEC_TYPE_UNKNOWN, CODEC_ID_NONE, -1, -1 } -}; - -SessionDescriptor::DynamicPayloadDesc SessionDescriptor::smDynamicPayloads[] = { - { "MP4V-ES", CODEC_TYPE_VIDEO, CODEC_ID_MPEG4 }, - { "mpeg4-generic", CODEC_TYPE_AUDIO, CODEC_ID_AAC }, - { "H264", CODEC_TYPE_VIDEO, CODEC_ID_H264 }, - { "AMR", CODEC_TYPE_AUDIO, CODEC_ID_AMR_NB }, - { "vnd.onvif.metadata", CODEC_TYPE_DATA, CODEC_ID_NONE } -}; -#endif SessionDescriptor::ConnInfo::ConnInfo( const std::string &connInfo ) : mTtl( 16 ), @@ -343,53 +301,30 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const { //formatContext->nb_streams = mMediaList.size(); for ( unsigned int i = 0; i < mMediaList.size(); i++ ) { const MediaDescriptor *mediaDesc = mMediaList[i]; -#if !LIBAVFORMAT_VERSION_CHECK(53, 10, 0, 17, 0) - AVStream *stream = av_new_stream(formatContext, i); -#else AVStream *stream = avformat_new_stream(formatContext, nullptr); stream->id = i; -#endif -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) AVCodecContext *codec_context = avcodec_alloc_context3(nullptr); -#else - AVCodecContext *codec_context = stream->codec; -#endif std::string type = mediaDesc->getType(); Debug(1, "Looking for codec for %s payload type %d / %s", type.c_str(), mediaDesc->getPayloadType(), mediaDesc->getPayloadDesc().c_str()); -#if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) if ( type == "video" ) codec_context->codec_type = AVMEDIA_TYPE_VIDEO; else if ( type == "audio" ) codec_context->codec_type = AVMEDIA_TYPE_AUDIO; else if ( type == "application" ) codec_context->codec_type = AVMEDIA_TYPE_DATA; -#else - if ( type == "video" ) - codec_context->codec_type = CODEC_TYPE_VIDEO; - else if ( type == "audio" ) - codec_context->codec_type = CODEC_TYPE_AUDIO; - else if ( type == "application" ) - codec_context->codec_type = CODEC_TYPE_DATA; -#endif else Warning("Unknown media_type %s", type.c_str()); -#if LIBAVCODEC_VERSION_CHECK(55, 50, 3, 60, 103) std::string codec_name; -#endif if ( mediaDesc->getPayloadType() < PAYLOAD_TYPE_DYNAMIC ) { // Look in static table for ( unsigned int j = 0; j < (sizeof(smStaticPayloads)/sizeof(*smStaticPayloads)); j++ ) { if ( smStaticPayloads[j].payloadType == mediaDesc->getPayloadType() ) { Debug( 1, "Got static payload type %d, %s", smStaticPayloads[j].payloadType, smStaticPayloads[j].payloadName ); -#if LIBAVCODEC_VERSION_CHECK(55, 50, 3, 60, 103) codec_name = std::string(smStaticPayloads[j].payloadName); -#else - strncpy(codec_context->codec_name, smStaticPayloads[j].payloadName, sizeof(codec_context->codec_name)); -#endif codec_context->codec_type = smStaticPayloads[j].codecType; codec_context->codec_id = smStaticPayloads[j].codecId; codec_context->sample_rate = smStaticPayloads[j].clockRate; @@ -401,11 +336,7 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const { for ( unsigned int j = 0; j < (sizeof(smDynamicPayloads)/sizeof(*smDynamicPayloads)); j++ ) { if ( smDynamicPayloads[j].payloadName == mediaDesc->getPayloadDesc() ) { Debug(1, "Got dynamic payload type %d, %s", mediaDesc->getPayloadType(), smDynamicPayloads[j].payloadName); -#if LIBAVCODEC_VERSION_CHECK(55, 50, 3, 60, 103) codec_name = std::string(smStaticPayloads[j].payloadName); -#else - strncpy(codec_context->codec_name, smDynamicPayloads[j].payloadName, sizeof(codec_context->codec_name)); -#endif codec_context->codec_type = smDynamicPayloads[j].codecType; codec_context->codec_id = smDynamicPayloads[j].codecId; codec_context->sample_rate = mediaDesc->getClock(); @@ -415,12 +346,7 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const { } /// end if static or dynamic -#if LIBAVCODEC_VERSION_CHECK(55, 50, 3, 60, 103) - if ( codec_name.empty() ) -#else - if ( !stream->codec->codec_name[0] ) -#endif - { + if (codec_name.empty()) { Warning( "Can't find payload details for %s payload type %d, name %s", mediaDesc->getType().c_str(), mediaDesc->getPayloadType(), mediaDesc->getPayloadDesc().c_str() ); } @@ -454,15 +380,9 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const { packet_size = av_base64_decode(decoded_packet, (const char *)base64packet, (int)sizeof(decoded_packet)); Hexdump(4, (char *)decoded_packet, packet_size); if ( packet_size ) { - uint8_t *dest = - (uint8_t *)av_malloc(packet_size + sizeof(start_sequence) + - codec_context->extradata_size + -#if LIBAVCODEC_VERSION_CHECK(57, 0, 0, 0, 0) - AV_INPUT_BUFFER_PADDING_SIZE -#else - FF_INPUT_BUFFER_PADDING_SIZE -#endif -); + uint8_t *dest = + (uint8_t *) av_malloc( + packet_size + sizeof(start_sequence) + codec_context->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE); if ( dest ) { if ( codec_context->extradata_size ) { // av_realloc? @@ -474,11 +394,7 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const { memcpy(dest+codec_context->extradata_size+sizeof(start_sequence), decoded_packet, packet_size); memset(dest+codec_context->extradata_size+sizeof(start_sequence)+ packet_size, 0, -#if LIBAVCODEC_VERSION_CHECK(57, 0, 0, 0, 0) AV_INPUT_BUFFER_PADDING_SIZE -#else - FF_INPUT_BUFFER_PADDING_SIZE -#endif ); codec_context->extradata= dest; @@ -490,12 +406,8 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const { } } } -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) avcodec_parameters_from_context(stream->codecpar, codec_context); -#endif } // end foreach mediaList return formatContext; } - -#endif // HAVE_LIBAVFORMAT diff --git a/src/zm_sdp.h b/src/zm_sdp.h index e99c12920..68f7e30a0 100644 --- a/src/zm_sdp.h +++ b/src/zm_sdp.h @@ -32,11 +32,7 @@ protected: struct StaticPayloadDesc { int payloadType; const char payloadName[6]; -#if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) AVMediaType codecType; -#else - enum CodecType codecType; -#endif _AVCODECID codecId; int clockRate; int autoChannels; @@ -44,11 +40,7 @@ protected: struct DynamicPayloadDesc { const char payloadName[32]; -#if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) AVMediaType codecType; -#else - enum CodecType codecType; -#endif _AVCODECID codecId; //int clockRate; diff --git a/src/zm_sendfile.h b/src/zm_sendfile.h index 593a94833..fd72e2e61 100644 --- a/src/zm_sendfile.h +++ b/src/zm_sendfile.h @@ -1,10 +1,6 @@ #ifndef ZM_SENDFILE_H #define ZM_SENDFILE_H -#ifdef __cplusplus -extern "C" { -#endif - #ifdef HAVE_SENDFILE4_SUPPORT #include int zm_sendfile(int out_fd, int in_fd, off_t *offset, size_t size) { @@ -37,8 +33,4 @@ int zm_sendfile(int out_fd, int in_fd, off_t *offset, off_t size) { #error "Your platform does not support sendfile. Sorry." #endif -#ifdef __cplusplus -} -#endif - -#endif +#endif // ZM_SENDFILE_H diff --git a/src/zm_storage.cpp b/src/zm_storage.cpp index 5edc36e00..e53e79335 100644 --- a/src/zm_storage.cpp +++ b/src/zm_storage.cpp @@ -21,32 +21,33 @@ #include "zm_db.h" #include "zm_logger.h" +#include "zm_utils.h" #include Storage::Storage() : id(0) { - Warning("Instantiating default Storage Object. Should not happen."); - strcpy(name, "Default"); - if ( staticConfig.DIR_EVENTS[0] != '/' ) { + Warning("Instantiating default Storage Object. Should not happen."); + strcpy(name, "Default"); + if (staticConfig.DIR_EVENTS[0] != '/') { // not using an absolute path. Make it one by appending ZM_PATH_WEB snprintf(path, sizeof(path), "%s/%s", - staticConfig.PATH_WEB.c_str(), staticConfig.DIR_EVENTS.c_str()); + staticConfig.PATH_WEB.c_str(), staticConfig.DIR_EVENTS.c_str()); } else { - strncpy(path, staticConfig.DIR_EVENTS.c_str(), sizeof(path)-1); + strncpy(path, staticConfig.DIR_EVENTS.c_str(), sizeof(path) - 1); } scheme = MEDIUM; scheme_str = "Medium"; } Storage::Storage(MYSQL_ROW &dbrow) { - unsigned int index = 0; - id = atoi(dbrow[index++]); - strncpy(name, dbrow[index++], sizeof(name)-1); - strncpy(path, dbrow[index++], sizeof(path)-1); + unsigned int index = 0; + id = atoi(dbrow[index++]); + strncpy(name, dbrow[index++], sizeof(name) - 1); + strncpy(path, dbrow[index++], sizeof(path) - 1); type_str = std::string(dbrow[index++]); scheme_str = std::string(dbrow[index++]); - if ( scheme_str == "Deep" ) { + if (scheme_str == "Deep") { scheme = DEEP; - } else if ( scheme_str == "Medium" ) { + } else if (scheme_str == "Medium") { scheme = MEDIUM; } else { scheme = SHALLOW; @@ -55,44 +56,42 @@ Storage::Storage(MYSQL_ROW &dbrow) { /* If a zero or invalid p_id is passed, then the old default path will be assumed. */ Storage::Storage(unsigned int p_id) : id(p_id) { - - if ( id ) { - char sql[ZM_SQL_SML_BUFSIZ]; - snprintf(sql, sizeof(sql), "SELECT `Id`, `Name`, `Path`, `Type`, `Scheme` FROM `Storage` WHERE `Id`=%u", id); - Debug(2, "Loading Storage for %u using %s", id, sql); - zmDbRow dbrow; - if ( !dbrow.fetch(sql) ) { - Error("Unable to load storage area for id %d: %s", id, mysql_error(&dbconn)); - } else { - unsigned int index = 0; - id = atoi(dbrow[index++]); - strncpy(name, dbrow[index++], sizeof(name)-1); - strncpy(path, dbrow[index++], sizeof(path)-1); + if (id) { + std::string sql = stringtf("SELECT `Id`, `Name`, `Path`, `Type`, `Scheme` FROM `Storage` WHERE `Id`=%u", id); + Debug(2, "Loading Storage for %u using %s", id, sql.c_str()); + zmDbRow dbrow; + if (!dbrow.fetch(sql)) { + Error("Unable to load storage area for id %d: %s", id, mysql_error(&dbconn)); + } else { + unsigned int index = 0; + id = atoi(dbrow[index++]); + strncpy(name, dbrow[index++], sizeof(name) - 1); + strncpy(path, dbrow[index++], sizeof(path) - 1); type_str = std::string(dbrow[index++]); scheme_str = std::string(dbrow[index++]); - if ( scheme_str == "Deep" ) { + if (scheme_str == "Deep") { scheme = DEEP; - } else if ( scheme_str == "Medium" ) { + } else if (scheme_str == "Medium") { scheme = MEDIUM; } else { scheme = SHALLOW; } - Debug(1, "Loaded Storage area %d '%s'", id, name); - } - } - if ( !id ) { - if ( staticConfig.DIR_EVENTS[0] != '/' ) { + Debug(1, "Loaded Storage area %d '%s'", id, name); + } + } + if (!id) { + if (staticConfig.DIR_EVENTS[0] != '/') { // not using an absolute path. Make it one by appending ZM_PATH_WEB snprintf(path, sizeof(path), "%s/%s", - staticConfig.PATH_WEB.c_str(), staticConfig.DIR_EVENTS.c_str()); + staticConfig.PATH_WEB.c_str(), staticConfig.DIR_EVENTS.c_str()); } else { - strncpy(path, staticConfig.DIR_EVENTS.c_str(), sizeof(path)-1); + strncpy(path, staticConfig.DIR_EVENTS.c_str(), sizeof(path) - 1); } - Debug(1, "No id passed to Storage constructor. Using default path %s instead", path); - strcpy(name, "Default"); + Debug(1, "No id passed to Storage constructor. Using default path %s instead", path); + strcpy(name, "Default"); scheme = MEDIUM; scheme_str = "Medium"; - } + } } Storage::~Storage() { diff --git a/src/zm_stream.cpp b/src/zm_stream.cpp index 0968c9514..5255d3ab2 100644 --- a/src/zm_stream.cpp +++ b/src/zm_stream.cpp @@ -27,13 +27,14 @@ #include #include +constexpr Seconds StreamBase::MAX_STREAM_DELAY; +constexpr Milliseconds StreamBase::MAX_SLEEP; + StreamBase::~StreamBase() { -#if HAVE_LIBAVCODEC - if ( vid_stream ) { + if (vid_stream) { delete vid_stream; vid_stream = nullptr; } -#endif closeComms(); } @@ -204,10 +205,10 @@ Image *StreamBase::prepareImage(Image *image) { last_crop = Box(); // Recalculate crop parameters, as %ges - int click_x = (last_crop.LoX() * 100 ) / last_act_image_width; // Initial crop offset from last image - click_x += ( x * 100 ) / last_virt_image_width; - int click_y = (last_crop.LoY() * 100 ) / last_act_image_height; // Initial crop offset from last image - click_y += ( y * 100 ) / last_virt_image_height; + int click_x = (last_crop.Lo().x_ * 100) / last_act_image_width; // Initial crop offset from last image + click_x += (x * 100) / last_virt_image_width; + int click_y = (last_crop.Lo().y_ * 100) / last_act_image_height; // Initial crop offset from last image + click_y += (y * 100) / last_virt_image_height; Debug(3, "Got adjusted click at %d%%,%d%%", click_x, click_y); // Convert the click locations to the current image pixels @@ -231,10 +232,10 @@ Image *StreamBase::prepareImage(Image *image) { hi_y = act_image_height - 1; lo_y = hi_y - (send_image_height - 1); } - last_crop = Box( lo_x, lo_y, hi_x, hi_y ); + last_crop = Box({lo_x, lo_y}, {hi_x, hi_y}); } // end if ( mag != last_mag || x != last_x || y != last_y ) - Debug(3, "Cropping to %d,%d -> %d,%d", last_crop.LoX(), last_crop.LoY(), last_crop.HiX(), last_crop.HiY()); + Debug(3, "Cropping to %d,%d -> %d,%d", last_crop.Lo().x_, last_crop.Lo().y_, last_crop.Hi().x_, last_crop.Hi().y_); if ( !image_copied ) { static Image copy_image; copy_image.Assign(*image); @@ -253,42 +254,54 @@ Image *StreamBase::prepareImage(Image *image) { } // end Image *StreamBase::prepareImage(Image *image) bool StreamBase::sendTextFrame(const char *frame_text) { - Debug(2, "Sending %dx%d * %d text frame '%s'", - monitor->Width(), monitor->Height(), scale, frame_text); + int width = 640; + int height = 480; + int colours = ZM_COLOUR_RGB32; + int subpixelorder = ZM_SUBPIX_ORDER_RGBA; + int labelsize = 2; - Image image(monitor->Width(), monitor->Height(), monitor->Colours(), monitor->SubpixelOrder()); - image.Clear(); - image.Annotate(frame_text, image.centreCoord(frame_text, monitor->LabelSize()), monitor->LabelSize()); - - if ( scale != 100 ) { - image.Scale(scale); + if (monitor) { + width = monitor->Width(); + height = monitor->Height(); + colours = monitor->Colours(); + subpixelorder = monitor->SubpixelOrder(); + labelsize = monitor->LabelSize(); } -#if HAVE_LIBAVCODEC - if ( type == STREAM_MPEG ) { - if ( !vid_stream ) { + Debug(2, "Sending %dx%dx%dx%d * %d scale text frame '%s'", + width, height, colours, subpixelorder, scale, frame_text); + + Image image(width, height, colours, subpixelorder); + image.Clear(); + image.Annotate(frame_text, image.centreCoord(frame_text, labelsize), labelsize); + + if (scale != 100) { + image.Scale(scale); + Debug(2, "Scaled to %dx%d", image.Width(), image.Height()); + } + if (type == STREAM_MPEG) { + if (!vid_stream) { vid_stream = new VideoStream("pipe:", format, bitrate, effective_fps, image.Colours(), image.SubpixelOrder(), image.Width(), image.Height()); fprintf(stdout, "Content-type: %s\r\n\r\n", vid_stream->MimeType()); vid_stream->OpenStream(); } /* double pts = */ vid_stream->EncodeFrame(image.Buffer(), image.Size()); - } else -#endif // HAVE_LIBAVCODEC - { + } else { static unsigned char buffer[ZM_MAX_IMAGE_SIZE]; int n_bytes = 0; image.EncodeJpeg(buffer, &n_bytes); + Debug(4, "Encoded to %d bytes", n_bytes); fputs("--" BOUNDARY "\r\nContent-Type: image/jpeg\r\n", stdout); fprintf(stdout, "Content-Length: %d\r\n\r\n", n_bytes); - if ( fwrite(buffer, n_bytes, 1, stdout) != 1 ) { + if (fwrite(buffer, n_bytes, 1, stdout) != 1) { Error("Unable to send stream text frame: %s", strerror(errno)); return false; } fputs("\r\n\r\n", stdout); fflush(stdout); } - last_frame_sent = TV_2_FLOAT(now); + last_frame_sent = now; return true; } @@ -373,7 +386,7 @@ void StreamBase::openComms() { strncpy(rem_addr.sun_path, rem_sock_path, sizeof(rem_addr.sun_path)); rem_addr.sun_family = AF_UNIX; - gettimeofday(&last_comm_update, nullptr); + last_comm_update = std::chrono::system_clock::now(); } // end if connKey > 0 Debug(3, "comms open at %s", loc_sock_path); } // end void StreamBase::openComms() diff --git a/src/zm_stream.h b/src/zm_stream.h index 6d752689d..8650801ea 100644 --- a/src/zm_stream.h +++ b/src/zm_stream.h @@ -22,13 +22,13 @@ #include "zm_logger.h" #include "zm_mpeg.h" +#include "zm_time.h" #include #include class Image; class Monitor; -#define TV_2_FLOAT( tv ) ( double((tv).tv_sec) + (double((tv).tv_usec) / 1000000.0) ) #define BOUNDARY "ZoneMinderFrame" class StreamBase { @@ -42,8 +42,8 @@ public: } StreamType; protected: - static const int MAX_STREAM_DELAY = 5; // Seconds - static const int MAX_SLEEP_USEC = 500000; // .5 Seconds + static constexpr Seconds MAX_STREAM_DELAY = Seconds(5); + static constexpr Milliseconds MAX_SLEEP = Milliseconds(500); static const StreamType DEFAULT_TYPE = STREAM_JPEG; enum { DEFAULT_RATE=ZM_RATE_BASE }; @@ -102,7 +102,6 @@ protected: int last_scale; int zoom; int last_zoom; - double maxfps; int bitrate; unsigned short last_x, last_y; unsigned short x, y; @@ -119,19 +118,23 @@ protected: bool paused; int step; - struct timeval now; - struct timeval last_comm_update; + SystemTimePoint now; + SystemTimePoint 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; + int frame_count; // Count of frames sent + int last_frame_count; // Used in calculating actual_fps from frame_count - last_frame_count - double base_fps; - double effective_fps; int frame_mod; - double last_frame_sent; - struct timeval last_frame_timestamp; + SystemTimePoint last_frame_sent; + SystemTimePoint last_frame_timestamp; -#if HAVE_LIBAVCODEC VideoStream *vid_stream; -#endif // HAVE_LIBAVCODEC CmdMsg msg; @@ -154,7 +157,6 @@ public: last_scale(DEFAULT_SCALE), zoom(DEFAULT_ZOOM), last_zoom(DEFAULT_ZOOM), - maxfps(DEFAULT_MAXFPS), bitrate(DEFAULT_BITRATE), last_x(0), last_y(0), @@ -166,7 +168,14 @@ public: sd(-1), lock_fd(0), paused(false), - step(0) + step(0), + maxfps(DEFAULT_MAXFPS), + base_fps(0.0), + effective_fps(0.0), + actual_fps(0.0), + frame_count(0), + last_frame_count(0), + frame_mod(1) { memset(&loc_sock_path, 0, sizeof(loc_sock_path)); memset(&loc_addr, 0, sizeof(loc_addr)); @@ -174,15 +183,7 @@ public: memset(&rem_addr, 0, sizeof(rem_addr)); memset(&sock_path_lock, 0, sizeof(sock_path_lock)); - base_fps = 0.0; - effective_fps = 0.0; - frame_mod = 1; - -#if HAVE_LIBAVCODEC - vid_stream = 0; -#endif // HAVE_LIBAVCODEC - last_frame_sent = 0.0; - last_frame_timestamp = {}; + vid_stream = nullptr; msg = { 0, { 0 } }; } virtual ~StreamBase(); diff --git a/src/zm_swscale.cpp b/src/zm_swscale.cpp index b5cf6eded..20b322ab9 100644 --- a/src/zm_swscale.cpp +++ b/src/zm_swscale.cpp @@ -22,30 +22,19 @@ #include "zm_image.h" #include "zm_logger.h" -#if HAVE_LIBSWSCALE && HAVE_LIBAVUTIL SWScale::SWScale() : gotdefaults(false), swscale_ctx(nullptr), input_avframe(nullptr), output_avframe(nullptr) { Debug(4, "SWScale object created"); } bool SWScale::init() { - /* Allocate AVFrame for the input */ -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) input_avframe = av_frame_alloc(); -#else - input_avframe = avcodec_alloc_frame(); -#endif - if ( input_avframe == nullptr ) { + if (!input_avframe) { Error("Failed allocating AVFrame for the input"); return false; } - /* Allocate AVFrame for the output */ -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) output_avframe = av_frame_alloc(); -#else - output_avframe = avcodec_alloc_frame(); -#endif - if ( output_avframe == nullptr ) { + if (!output_avframe) { Error("Failed allocating AVFrame for the output"); return false; } @@ -128,11 +117,11 @@ int SWScale::Convert( in_buffer, in_buffer_size, out_buffer, out_buffer_size, width, height, new_width, new_height, in_pf, out_pf); /* Parameter checking */ - if ( in_buffer == nullptr ) { + if (in_buffer == nullptr) { Error("NULL Input buffer"); return -1; } - if ( out_buffer == nullptr ) { + if (out_buffer == nullptr) { Error("NULL output buffer"); return -1; } @@ -140,30 +129,28 @@ int SWScale::Convert( // Error("Invalid input or output pixel formats"); // return -2; // } - if ( !width || !height || !new_height || !new_width ) { + if (!width || !height || !new_height || !new_width) { Error("Invalid width or height"); return -3; } in_pf = fix_deprecated_pix_fmt(in_pf); -#if LIBSWSCALE_VERSION_CHECK(0, 8, 0, 8, 0) /* Warn if the input or output pixelformat is not supported */ - if ( !sws_isSupportedInput(in_pf) ) { + if (!sws_isSupportedInput(in_pf)) { Warning("swscale does not support the input format: %c%c%c%c", (in_pf)&0xff,((in_pf)&0xff),((in_pf>>16)&0xff),((in_pf>>24)&0xff)); } - if ( !sws_isSupportedOutput(out_pf) ) { + if (!sws_isSupportedOutput(out_pf)) { Warning("swscale does not support the output format: %c%c%c%c", (out_pf)&0xff,((out_pf>>8)&0xff),((out_pf>>16)&0xff),((out_pf>>24)&0xff)); } -#endif - int alignment = 1; + int alignment = width % 32 ? 1 : 32; /* Check the buffer sizes */ size_t needed_insize = GetBufferSize(in_pf, width, height); - if ( needed_insize > in_buffer_size ) { - Debug(1, + if (needed_insize > in_buffer_size) { + Warning( "The input buffer size does not match the expected size for the input format. Required: %zu for %dx%d %d Available: %zu", needed_insize, width, @@ -172,7 +159,7 @@ int SWScale::Convert( in_buffer_size); } size_t needed_outsize = GetBufferSize(out_pf, new_width, new_height); - if ( needed_outsize > out_buffer_size ) { + if (needed_outsize > out_buffer_size) { Error("The output buffer is undersized for the output format. Required: %zu Available: %zu", needed_outsize, out_buffer_size); @@ -184,29 +171,27 @@ int SWScale::Convert( width, height, in_pf, new_width, new_height, out_pf, SWS_FAST_BILINEAR, nullptr, nullptr, nullptr); - if ( swscale_ctx == nullptr ) { + if (swscale_ctx == nullptr) { Error("Failed getting swscale context"); return -6; } + /* + input_avframe->format = in_pf; + input_avframe->width = width; + input_avframe->height = height; + output_avframe->format = out_pf; + output_avframe->width = new_width; + output_avframe->height = new_height; + */ /* Fill in the buffers */ -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) if (av_image_fill_arrays(input_avframe->data, input_avframe->linesize, (uint8_t*) in_buffer, in_pf, width, height, alignment) <= 0) { -#else - if (avpicture_fill((AVPicture*) input_avframe, (uint8_t*) in_buffer, - in_pf, width, height) <= 0) { -#endif Error("Failed filling input frame with input buffer"); return -7; } -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) if (av_image_fill_arrays(output_avframe->data, output_avframe->linesize, out_buffer, out_pf, new_width, new_height, alignment) <= 0) { -#else - if (avpicture_fill((AVPicture*) output_avframe, out_buffer, out_pf, new_width, - new_height) <= 0) { -#endif Error("Failed filling output frame with output buffer"); return -8; } @@ -277,10 +262,5 @@ int SWScale::ConvertDefaults(const uint8_t* in_buffer, const size_t in_buffer_si } size_t SWScale::GetBufferSize(enum _AVPIXELFORMAT pf, unsigned int width, unsigned int height) { -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) return av_image_get_buffer_size(pf, width, height, 1); -#else - return outsize = avpicture_get_size(pf, width,height); -#endif } -#endif // HAVE_LIBSWSCALE && HAVE_LIBAVUTIL diff --git a/src/zm_swscale.h b/src/zm_swscale.h index 8e7e1fa19..c0d1e7120 100644 --- a/src/zm_swscale.h +++ b/src/zm_swscale.h @@ -7,7 +7,6 @@ class Image; /* SWScale wrapper class to make our life easier and reduce code reuse */ -#if HAVE_LIBSWSCALE && HAVE_LIBAVUTIL class SWScale { public: SWScale(); @@ -32,6 +31,5 @@ class SWScale { unsigned int default_width; unsigned int default_height; }; -#endif // HAVE_LIBSWSCALE && HAVE_LIBAVUTIL -#endif +#endif // ZM_SWSCALE_H diff --git a/src/zm_time.cpp b/src/zm_time.cpp deleted file mode 100644 index 417ee2b2b..000000000 --- a/src/zm_time.cpp +++ /dev/null @@ -1,22 +0,0 @@ -// -// ZoneMinder Time Functions & Definitions 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_time.h" - -// Blank diff --git a/src/zm_time.h b/src/zm_time.h index 5a54bea46..f7574bbf7 100644 --- a/src/zm_time.h +++ b/src/zm_time.h @@ -20,189 +20,67 @@ #ifndef ZM_TIME_H #define ZM_TIME_H -#include +#include #include -// Structure used for storing the results of the subtraction -// of one struct timeval from another +typedef std::chrono::microseconds Microseconds; +typedef std::chrono::milliseconds Milliseconds; +typedef std::chrono::seconds Seconds; +typedef std::chrono::minutes Minutes; +typedef std::chrono::hours Hours; -struct DeltaTimeval -{ - bool positive; - unsigned long delta; - unsigned long sec; - unsigned long fsec; - unsigned long prec; +// floating point seconds +typedef std::chrono::duration FPSeconds; + +typedef std::chrono::steady_clock::time_point TimePoint; +typedef std::chrono::system_clock::time_point SystemTimePoint; + +namespace zm { +namespace chrono { +namespace impl { + +template +struct posix_duration_cast; + +// chrono -> timeval caster +template +struct posix_duration_cast, timeval> { + static timeval cast(std::chrono::duration const &d) { + timeval tv = {}; + + Seconds const sec = std::chrono::duration_cast(d); + + tv.tv_sec = sec.count(); + tv.tv_usec = std::chrono::duration_cast(d - sec).count(); + + return tv; + } }; -#define DT_GRAN_1000000 1000000 -#define DT_PREC_6 DT_GRAN_1000000 -#define DT_GRAN_100000 100000 -#define DT_PREC_5 DT_GRAN_100000 -#define DT_GRAN_10000 10000 -#define DT_PREC_4 DT_GRAN_10000 -#define DT_GRAN_1000 1000 -#define DT_PREC_3 DT_GRAN_1000 -#define DT_GRAN_100 100 -#define DT_PREC_2 DT_GRAN_100 -#define DT_GRAN_10 10 -#define DT_PREC_1 DT_GRAN_10 - -#define DT_MAXGRAN DT_GRAN_1000000 - -// This obviously wouldn't work for massive deltas but as it's mostly -// for frames it will only usually be a fraction of a second or so -#define DELTA_TIMEVAL( result, time1, time2, precision ) \ -{ \ - int delta = (((time1).tv_sec-(time2).tv_sec)*(precision))+(((time1).tv_usec-(time2).tv_usec)/(DT_MAXGRAN/(precision))); \ - result.positive = (delta>=0); \ - result.delta = abs(delta); \ - result.sec = result.delta/(precision); \ - result.fsec = result.delta%(precision); \ - result.prec = (precision); \ -} - -#define TIMEVAL_INTERVAL( result, time1, time2, precision ) \ -{ \ - int delta = (((time1).tv_sec-(time2).tv_sec)*(precision))+(((time1).tv_usec-(time2).tv_usec)/(DT_MAXGRAN/(precision))); \ - result.positive = (delta>=0); \ - result.delta = abs(delta); \ - result.sec = result.delta/(precision); \ - result.fsec = result.delta%(precision); \ - result.prec = (precision); \ -} - -#define USEC_PER_SEC 1000000 -#define MSEC_PER_SEC 1000 - -/* -extern struct timeval tv; -*/ - -inline int tvDiffUsec( struct timeval first, struct timeval last ) -{ - return( (last.tv_sec - first.tv_sec) * USEC_PER_SEC) + ((USEC_PER_SEC + last.tv_usec - first.tv_usec) - USEC_PER_SEC ); -} - -inline int tvDiffUsec( struct timeval first ) -{ - struct timeval now; - gettimeofday( &now, nullptr ); - return( tvDiffUsec( first, now ) ); -} - -inline int tvDiffMsec( struct timeval first, struct timeval last ) -{ - return( (last.tv_sec - first.tv_sec) * MSEC_PER_SEC) + (((MSEC_PER_SEC + last.tv_usec - first.tv_usec) / MSEC_PER_SEC) - MSEC_PER_SEC ); -} - -inline int tvDiffMsec( struct timeval first ) -{ - struct timeval now; - gettimeofday( &now, nullptr ); - return( tvDiffMsec( first, now ) ); -} - -inline double tvDiffSec( struct timeval first, struct timeval last ) -{ - return( double(last.tv_sec - first.tv_sec) + double(((USEC_PER_SEC + last.tv_usec - first.tv_usec) - USEC_PER_SEC) / (1.0*USEC_PER_SEC) ) ); -} - -inline double tvDiffSec( struct timeval first ) -{ - struct timeval now; - gettimeofday( &now, nullptr ); - return( tvDiffSec( first, now ) ); -} - -inline struct timeval tvZero() -{ - struct timeval t = { 0, 0 }; - return( t ); -} - -inline int tvIsZero( const struct timeval t ) -{ - return( t.tv_sec == 0 && t.tv_usec == 0 ); -} - -inline int tvCmp( struct timeval t1, struct timeval t2 ) -{ - if ( t1.tv_sec < t2.tv_sec ) - return( -1 ); - if ( t1.tv_sec > t2.tv_sec ) - return( 1 ); - if ( t1.tv_usec < t2.tv_usec ) - return( -1 ); - if ( t1.tv_usec > t2.tv_usec ) - return( 1 ); - return( 0 ); -} - -inline int tvEq( struct timeval t1, struct timeval t2 ) -{ - return( t1.tv_sec == t2.tv_sec && t1.tv_usec == t2.tv_usec ); -} - -inline struct timeval tvNow( void ) -{ - struct timeval t; - gettimeofday( &t, nullptr ); - return( t ); -} - -inline struct timeval tvCheck( struct timeval &t ) -{ - if ( t.tv_usec >= USEC_PER_SEC ) - { - Warning( "Timestamp too large %ld.%ld\n", t.tv_sec, (long int) t.tv_usec ); - t.tv_sec += t.tv_usec / USEC_PER_SEC; - t.tv_usec %= USEC_PER_SEC; +// timeval -> chrono caster +template +struct posix_duration_cast> { + static std::chrono::duration cast(timeval const &tv) { + return std::chrono::duration_cast>( + Seconds(tv.tv_sec) + Microseconds(tv.tv_usec) + ); } - else if ( t.tv_usec < 0 ) - { - Warning( "Got negative timestamp %ld.%ld\n", t.tv_sec, (long int)t.tv_usec ); - t.tv_usec = 0; - } - return( t ); +}; } -// Add t2 to t1 -inline struct timeval tvAdd( struct timeval t1, struct timeval t2 ) -{ - tvCheck(t1); - tvCheck(t2); - t1.tv_sec += t2.tv_sec; - t1.tv_usec += t2.tv_usec; - if ( t1.tv_usec >= USEC_PER_SEC ) - { - t1.tv_sec++; - t1.tv_usec -= USEC_PER_SEC; - } - return( t1 ); +// chrono -> timeval +template +auto duration_cast(std::chrono::duration const &d) +-> typename std::enable_if::value, timeval>::type { + return impl::posix_duration_cast, timeval>::cast(d); } -// Subtract t2 from t1 -inline struct timeval tvSub( struct timeval t1, struct timeval t2 ) -{ - tvCheck(t1); - tvCheck(t2); - t1.tv_sec -= t2.tv_sec; - t1.tv_usec -= t2.tv_usec; - if ( t1.tv_usec < 0 ) - { - t1.tv_sec--; - t1.tv_usec += USEC_PER_SEC; - } - return( t1 ) ; +// timeval -> chrono +template +Duration duration_cast(timeval const &tv) { + return impl::posix_duration_cast::cast(tv); +} } - -inline struct timeval tvMake( time_t sec, suseconds_t usec ) -{ - struct timeval t; - t.tv_sec = sec; - t.tv_usec = usec; - return( t ); } #endif // ZM_TIME_H diff --git a/src/zm_user.cpp b/src/zm_user.cpp index 254663a04..44a22df38 100644 --- a/src/zm_user.cpp +++ b/src/zm_user.cpp @@ -21,19 +21,10 @@ #include "zm_crypt.h" #include "zm_logger.h" +#include "zm_time.h" #include "zm_utils.h" #include -#if HAVE_GNUTLS_GNUTLS_H -#include -#endif - -#if HAVE_GCRYPT_H -#include -#elif HAVE_LIBCRYPTO -#include -#endif // HAVE_GCRYPT_H || HAVE_LIBCRYPTO - User::User() { id = 0; username[0] = password[0] = 0; @@ -94,15 +85,7 @@ bool User::canAccess(int monitor_id) { // Function to load a user from username and password // Please note that in auth relay mode = none, password is NULL User *zmLoadUser(const char *username, const char *password) { - int username_length = strlen(username); - - // According to docs, size of safer_whatever must be 2*length+1 - // due to unicode conversions + null terminator. - std::string escaped_username((username_length * 2) + 1, '\0'); - - - size_t escaped_len = mysql_real_escape_string(&dbconn, &escaped_username[0], username, username_length); - escaped_username.resize(escaped_len); + std::string escaped_username = zmDbEscapeString(username); std::string sql = stringtf("SELECT `Id`, `Username`, `Password`, `Enabled`," " `Stream`+0, `Events`+0, `Control`+0, `Monitors`+0, `System`+0," @@ -110,7 +93,7 @@ User *zmLoadUser(const char *username, const char *password) { " FROM `Users` WHERE `Username` = '%s' AND `Enabled` = 1", escaped_username.c_str()); - MYSQL_RES *result = zmDbFetch(sql.c_str()); + MYSQL_RES *result = zmDbFetch(sql); if (!result) return nullptr; @@ -161,7 +144,7 @@ User *zmLoadTokenUser(const std::string &jwt_token_str, bool use_remote_addr) { " `Control`+0, `Monitors`+0, `System`+0, `MonitorIds`, `TokenMinExpiry`" " FROM `Users` WHERE `Username` = '%s' AND `Enabled` = 1", username.c_str()); - MYSQL_RES *result = zmDbFetch(sql.c_str()); + MYSQL_RES *result = zmDbFetch(sql); if (!result) return nullptr; @@ -190,20 +173,10 @@ User *zmLoadTokenUser(const std::string &jwt_token_str, bool use_remote_addr) { // Function to validate an authentication string User *zmLoadAuthUser(const char *auth, bool use_remote_addr) { -#if HAVE_DECL_MD5 || HAVE_DECL_GNUTLS_FINGERPRINT -#ifdef HAVE_GCRYPT_H - // Special initialisation for libgcrypt - if ( !gcry_check_version(GCRYPT_VERSION) ) { - Fatal("Unable to initialise libgcrypt"); - } - gcry_control(GCRYCTL_DISABLE_SECMEM, 0); - gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); -#endif // HAVE_GCRYPT_H - const char *remote_addr = ""; - if ( use_remote_addr ) { + if (use_remote_addr) { remote_addr = getenv("REMOTE_ADDR"); - if ( !remote_addr ) { + if (!remote_addr) { Warning("Can't determine remote address, using null"); remote_addr = ""; } @@ -214,70 +187,55 @@ User *zmLoadAuthUser(const char *auth, bool use_remote_addr) { " `Stream`+0, `Events`+0, `Control`+0, `Monitors`+0, `System`+0," " `MonitorIds` FROM `Users` WHERE `Enabled` = 1"; - MYSQL_RES *result = zmDbFetch(sql.c_str()); + MYSQL_RES *result = zmDbFetch(sql); if (!result) return nullptr; int n_users = mysql_num_rows(result); - if ( n_users < 1 ) { + if (n_users < 1) { mysql_free_result(result); Warning("Unable to authenticate user"); return nullptr; } - // getting the time is expensive, so only do it once. - time_t now = time(nullptr); - unsigned int hours = config.auth_hash_ttl; - if ( !hours ) { - Warning("No value set for ZM_AUTH_HASH_TTL. Defaulting to 2."); - hours = 2; - } else { - Debug(1, "AUTH_HASH_TTL is %d, time is %" PRIi64, hours, static_cast(now)); - } - char auth_key[512] = ""; - char auth_md5[32+1] = ""; - size_t md5len = 16; - unsigned char md5sum[md5len]; + SystemTimePoint now = std::chrono::system_clock::now(); + Hours hours = Hours(config.auth_hash_ttl); - const char * hex = "0123456789abcdef"; - while ( MYSQL_ROW dbrow = mysql_fetch_row(result) ) { + if (hours == Hours(0)) { + Warning("No value set for ZM_AUTH_HASH_TTL. Defaulting to 2."); + hours = Hours(2); + } else { + Debug(1, "AUTH_HASH_TTL is %" PRIi64 " h, time is %" PRIi64 " s", + static_cast(Hours(hours).count()), + static_cast(std::chrono::duration_cast(now.time_since_epoch()).count())); + } + + while (MYSQL_ROW dbrow = mysql_fetch_row(result)) { const char *username = dbrow[1]; const char *password = dbrow[2]; - time_t our_now = now; + SystemTimePoint our_now = now; tm now_tm = {}; - for ( unsigned int i = 0; i < hours; i++, our_now -= 3600 ) { - localtime_r(&our_now, &now_tm); + for (Hours i = Hours(0); i < hours; i++, our_now -= Hours(1)) { + time_t our_now_t = std::chrono::system_clock::to_time_t(our_now); + localtime_r(&our_now_t, &now_tm); - snprintf(auth_key, sizeof(auth_key)-1, "%s%s%s%s%d%d%d%d", - config.auth_hash_secret, - username, - password, - remote_addr, - now_tm.tm_hour, - now_tm.tm_mday, - now_tm.tm_mon, - now_tm.tm_year); + std::string auth_key = stringtf("%s%s%s%s%d%d%d%d", + config.auth_hash_secret, + username, + password, + remote_addr, + now_tm.tm_hour, + now_tm.tm_mday, + now_tm.tm_mon, + now_tm.tm_year); -#if HAVE_DECL_MD5 - MD5((unsigned char *)auth_key, strlen(auth_key), md5sum); -#elif HAVE_DECL_GNUTLS_FINGERPRINT - gnutls_datum_t md5data = { (unsigned char *)auth_key, (unsigned int)strlen(auth_key) }; - gnutls_fingerprint(GNUTLS_DIG_MD5, &md5data, md5sum, &md5len); -#endif - unsigned char *md5sum_ptr = md5sum; - char *auth_md5_ptr = auth_md5; + zm::crypto::MD5::Digest md5_digest = zm::crypto::MD5::GetDigestOf(auth_key); + std::string auth_md5 = ByteArrayToHexString(md5_digest); - for ( unsigned int j = 0; j < md5len; j++ ) { - *auth_md5_ptr++ = hex[(*md5sum_ptr>>4)&0xf]; - *auth_md5_ptr++ = hex[(*md5sum_ptr++)&0xf]; - } - *auth_md5_ptr = 0; + Debug(1, "Checking auth_key '%s' -> auth_md5 '%s' == '%s'", auth_key.c_str(), auth_md5.c_str(), auth); - Debug(1, "Checking auth_key '%s' -> auth_md5 '%s' == '%s'", - auth_key, auth_md5, auth); - - if ( !strcmp(auth, auth_md5) ) { + if (!strcmp(auth, auth_md5.c_str())) { // We have a match User *user = new User(dbrow); Debug(1, "Authenticated user '%s'", user->getUsername()); @@ -289,9 +247,7 @@ User *zmLoadAuthUser(const char *auth, bool use_remote_addr) { } // end foreach hour } // end foreach user mysql_free_result(result); -#else // HAVE_DECL_MD5 || HAVE_DECL_GNUTLS_FINGERPRINT - Error("You need to build with gnutls or openssl to use hash based auth"); -#endif // HAVE_DECL_MD5 || HAVE_DECL_GNUTLS_FINGERPRINT + Debug(1, "No user found for auth_key %s", auth); return nullptr; } // end User *zmLoadAuthUser( const char *auth, bool use_remote_addr ) diff --git a/src/zm_utils.cpp b/src/zm_utils.cpp index 7d2bbc888..5da5509ff 100644 --- a/src/zm_utils.cpp +++ b/src/zm_utils.cpp @@ -22,6 +22,7 @@ #include "zm_config.h" #include "zm_logger.h" #include +#include #include #include /* Definition of AT_* constants */ #include @@ -116,6 +117,43 @@ std::string Join(const StringVector &values, const std::string &delim) { return ss.str(); } +std::string stringtf(const char* format, ...) { + va_list args; + va_start(args, format); + va_list args2; + va_copy(args2, args); + + int size = vsnprintf(nullptr, 0, format, args) + 1; // Extra space for '\0' + va_end(args); + + if (size <= 0) { + throw std::runtime_error("Error during formatting."); + } + + std::unique_ptr buf(new char[size]); + vsnprintf(buf.get(), size, format, args2); + va_end(args2); + + return std::string(buf.get(), buf.get() + size - 1); // We don't want the '\0' inside +} + +std::string ByteArrayToHexString(nonstd::span bytes) { + static constexpr char lowercase_table[] = "0123456789abcdef"; + std::string buf; + buf.resize(2 * bytes.size()); + + const uint8 *srcPtr = bytes.data(); + char *dstPtr = &buf[0]; + + for (size_t i = 0; i < bytes.size(); ++i) { + uint8 c = *srcPtr++; + *dstPtr++ = lowercase_table[c >> 4]; + *dstPtr++ = lowercase_table[c & 0x0f]; + } + + return buf; +} + std::string Base64Encode(const std::string &str) { static char base64_table[64] = {'\0'}; @@ -163,16 +201,6 @@ std::string Base64Encode(const std::string &str) { return outString; } -void TimespecDiff(struct timespec *start, struct timespec *end, struct timespec *diff) { - if (((end->tv_nsec) - (start->tv_nsec)) < 0) { - diff->tv_sec = end->tv_sec - start->tv_sec - 1; - diff->tv_nsec = 1000000000 + end->tv_nsec - start->tv_nsec; - } else { - diff->tv_sec = end->tv_sec - start->tv_sec; - diff->tv_nsec = end->tv_nsec - start->tv_nsec; - } -} - std::string TimevalToString(timeval tv) { tm now = {}; std::array tm_buf = {}; @@ -350,7 +378,7 @@ QueryString::QueryString(std::istream &input) { auto foundItr = parameters_.find(name); if (foundItr == parameters_.end()) { - std::unique_ptr newParam = ZM::make_unique(name); + std::unique_ptr newParam = zm::make_unique(name); if (!value.empty()) { newParam->addValue(value); } diff --git a/src/zm_utils.h b/src/zm_utils.h index 88e00a785..d5ee88202 100644 --- a/src/zm_utils.h +++ b/src/zm_utils.h @@ -20,23 +20,36 @@ #ifndef ZM_UTILS_H #define ZM_UTILS_H +#include "zm_define.h" #include #include #include #include #include #include +#include "span.hpp" #include #include #include #include + +#ifdef NDEBUG +#define ASSERT(x) do { (void) sizeof(x); } while (0) +#else +#include +#define ASSERT(x) assert(x) +#endif + typedef std::vector StringVector; std::string Trim(const std::string &str, const std::string &char_set); inline std::string TrimSpaces(const std::string &str) { return Trim(str, " \t"); } -std::string ReplaceAll(std::string str, const std::string& old_value, const std::string& new_value); -inline void StringToUpper(std::string &str) { std::transform(str.begin(), str.end(), str.begin(), ::toupper); } +std::string ReplaceAll(std::string str, const std::string &old_value, const std::string &new_value); +inline std::string StringToUpper(std::string str) { + std::transform(str.begin(), str.end(), str.begin(), ::toupper); + return str; +} StringVector Split(const std::string &str, char delim); StringVector Split(const std::string &str, const std::string &delim, size_t limit = 0); @@ -48,20 +61,13 @@ inline bool StartsWith(const std::string &haystack, const std::string &needle) { return (haystack.substr(0, needle.length()) == needle); } -template -std::string stringtf(const std::string &format, Args... args) { - int size = snprintf(nullptr, 0, format.c_str(), args...) + 1; // Extra space for '\0' - if (size <= 0) { - throw std::runtime_error("Error during formatting."); - } - std::unique_ptr buf(new char[size]); - snprintf(buf.get(), size, format.c_str(), args...); - return std::string(buf.get(), buf.get() + size - 1); // We don't want the '\0' inside -} +__attribute__((format(printf, 1, 2))) +std::string stringtf(const char* format, ...); + +std::string ByteArrayToHexString(nonstd::span bytes); std::string Base64Encode(const std::string &str); -void TimespecDiff(timespec *start, timespec *end, timespec *diff); std::string TimevalToString(timeval tv); extern unsigned int sse_version; @@ -71,43 +77,55 @@ void *sse2_aligned_memcpy(void *dest, const void *src, size_t bytes); void touch(const char *pathname); -namespace ZM { -//! std::make_unique implementation (TODO: remove this once C++14 is supported) +namespace zm { +// C++14 std::make_unique (TODO: remove this once C++14 is supported) template inline auto make_unique(Args &&...args) -> typename std::enable_if::value, std::unique_ptr>::type { return std::unique_ptr(new T(std::forward(args)...)); } - template inline auto make_unique(std::size_t size) -> typename std::enable_if::value && std::extent::value == 0, std::unique_ptr>::type { return std::unique_ptr(new typename std::remove_extent::type[size]()); } - template inline auto make_unique(Args &&...) -> typename std::enable_if::value != 0, void>::type = delete; +// C++17 std::clamp (TODO: remove this once C++17 is supported) template constexpr const T &clamp(const T &v, const T &lo, const T &hi, Compare comp) { return comp(v, lo) ? lo : comp(hi, v) ? hi : v; } - template constexpr const T &clamp(const T &v, const T &lo, const T &hi) { - return clamp(v, lo, hi, std::less{}); -} + return zm::clamp(v, lo, hi, std::less{}); } -typedef std::chrono::microseconds Microseconds; -typedef std::chrono::milliseconds Milliseconds; -typedef std::chrono::seconds Seconds; -typedef std::chrono::minutes Minutes; -typedef std::chrono::hours Hours; +// C++17 std::data (TODO: remove this once C++17 is supported) +template +constexpr auto data(C &c) -> decltype(c.data()) { return c.data(); } -typedef std::chrono::steady_clock::time_point TimePoint; -typedef std::chrono::system_clock::time_point SystemTimePoint; +template +constexpr auto data(C const &c) -> decltype(c.data()) { return c.data(); } + +template +constexpr T *data(T(&a)[N]) noexcept { return a; } + +template +constexpr T const *data(const T(&a)[N]) noexcept { return a; } + +template +constexpr T const *data(std::initializer_list l) noexcept { return l.begin(); } + +// C++17 std::size (TODO: remove this once C++17 is supported) +template +constexpr auto size(const C &c) -> decltype(c.size()) { return c.size(); } + +template +constexpr std::size_t size(const T(&)[N]) noexcept { return N; } +} std::string UriDecode(const std::string &encoded); @@ -146,4 +164,5 @@ class QueryString { std::map> parameters_; }; + #endif // ZM_UTILS_H diff --git a/src/zm_vector2.h b/src/zm_vector2.h new file mode 100644 index 000000000..c7630ad36 --- /dev/null +++ b/src/zm_vector2.h @@ -0,0 +1,80 @@ +// +// ZoneMinder Coordinate Class Interface, $Date$, $Revision$ +// Copyright (C) 2001-2008 Philip Coombes +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// + +#ifndef ZM_VECTOR2_H +#define ZM_VECTOR2_H + +#include "zm_define.h" +#include +#include + +// +// Class used for storing an x,y pair, i.e. a coordinate/vector +// +class Vector2 { + public: + Vector2() : x_(0), y_(0) {} + Vector2(int32 x, int32 y) : x_(x), y_(y) {} + + static Vector2 Inf() { + static const Vector2 inf = {std::numeric_limits::max(), std::numeric_limits::max()}; + return inf; + } + + bool operator==(const Vector2 &rhs) const { return (x_ == rhs.x_ && y_ == rhs.y_); } + bool operator!=(const Vector2 &rhs) const { return (x_ != rhs.x_ || y_ != rhs.y_); } + + // These operators are not idiomatic. If lexicographic comparison is needed, it should be implemented separately. + bool operator>(const Vector2 &rhs) const = delete; + bool operator>=(const Vector2 &rhs) const = delete; + bool operator<(const Vector2 &rhs) const = delete; + bool operator<=(const Vector2 &rhs) const = delete; + + Vector2 operator+(const Vector2 &rhs) const { + return {x_ + rhs.x_, y_ + rhs.y_}; + } + Vector2 operator-(const Vector2 &rhs) const { + return {x_ - rhs.x_, y_ - rhs.y_}; + } + Vector2 operator*(double rhs) const { + return {static_cast(std::lround(x_ * rhs)), static_cast(std::lround(y_ * rhs))}; + } + + Vector2 &operator+=(const Vector2 &rhs) { + x_ += rhs.x_; + y_ += rhs.y_; + return *this; + } + Vector2 &operator-=(const Vector2 &rhs) { + x_ -= rhs.x_; + y_ -= rhs.y_; + return *this; + } + + // Calculated the determinant of the 2x2 matrix as given by [[x_, y_], [v.x_y, v.y_]] + int32 Determinant(Vector2 const &v) const { + return (x_ * v.y_) - (y_ * v.x_); + } + + public: + int32 x_; + int32 y_; +}; + +#endif // ZM_VECTOR2_H diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 843558de3..67f31cabc 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -22,9 +22,10 @@ #include "zm_logger.h" #include "zm_monitor.h" +#include "zm_time.h" extern "C" { -#include "libavutil/time.h" +#include } /* @@ -37,7 +38,7 @@ extern "C" { */ VideoStore::CodecData VideoStore::codec_data[] = { -#if HAVE_LIBAVUTIL_HWCONTEXT_H +#if HAVE_LIBAVUTIL_HWCONTEXT_H && LIBAVCODEC_VERSION_CHECK(57, 107, 0, 107, 0) { AV_CODEC_ID_H265, "h265", "hevc_vaapi", AV_PIX_FMT_NV12, AV_PIX_FMT_VAAPI, AV_HWDEVICE_TYPE_VAAPI }, { AV_CODEC_ID_H265, "h265", "hevc_nvenc", AV_PIX_FMT_NV12, AV_PIX_FMT_NV12, AV_HWDEVICE_TYPE_NONE }, { AV_CODEC_ID_H265, "h265", "libx265", AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P, AV_HWDEVICE_TYPE_NONE }, @@ -72,7 +73,6 @@ VideoStore::VideoStore( oc(nullptr), video_out_stream(nullptr), audio_out_stream(nullptr), - video_out_codec(nullptr), video_in_ctx(p_video_in_ctx), video_out_ctx(nullptr), video_in_stream(p_video_in_stream), @@ -88,16 +88,11 @@ VideoStore::VideoStore( packets_written(0), frame_count(0), hw_device_ctx(nullptr), -#if defined(HAVE_LIBSWRESAMPLE) || defined(HAVE_LIBAVRESAMPLE) resample_ctx(nullptr), -#if defined(HAVE_LIBSWRESAMPLE) fifo(nullptr), -#endif -#endif converted_in_samples(nullptr), filename(filename_in), format(format_in), - video_first_pts(0), /* starting pts of first in frame/packet */ video_first_dts(0), audio_first_pts(0), audio_first_dts(0), @@ -115,7 +110,7 @@ bool VideoStore::open() { Debug(1, "Opening video storage stream %s format: %s", filename, format); int ret = avformat_alloc_output_context2(&oc, nullptr, nullptr, filename); - if ( ret < 0 ) { + if (ret < 0) { Warning( "Could not create video storage stream %s as no out ctx" " could be assigned based on filename: %s", @@ -123,9 +118,9 @@ bool VideoStore::open() { } // Couldn't deduce format from filename, trying from format name - if ( !oc ) { + if (!oc) { avformat_alloc_output_context2(&oc, nullptr, format, filename); - if ( !oc ) { + if (!oc) { Error( "Could not create video storage stream %s as no out ctx" " could not be assigned based on filename or format %s", @@ -136,51 +131,115 @@ bool VideoStore::open() { AVDictionary *pmetadata = nullptr; ret = av_dict_set(&pmetadata, "title", "Zoneminder Security Recording", 0); - if ( ret < 0 ) Warning("%s:%d: title set failed", __FILE__, __LINE__); + if (ret < 0) Warning("%s:%d: title set failed", __FILE__, __LINE__); oc->metadata = pmetadata; out_format = oc->oformat; out_format->flags |= AVFMT_TS_NONSTRICT; // allow non increasing dts + AVCodec *video_out_codec = nullptr; - if ( video_in_stream ) { -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + AVDictionary *opts = nullptr; + std::string Options = monitor->GetEncoderOptions(); + Debug(2, "Options? %s", Options.c_str()); + ret = av_dict_parse_string(&opts, Options.c_str(), "=", ",#\n", 0); + if (ret < 0) { + Warning("Could not parse ffmpeg encoder options list '%s'", Options.c_str()); + } else { + AVDictionaryEntry *e = nullptr; + while ((e = av_dict_get(opts, "", e, AV_DICT_IGNORE_SUFFIX)) != nullptr) { + Debug(3, "Encoder Option %s=%s", e->key, e->value); + } + } + + if (video_in_stream) { zm_dump_codecpar(video_in_stream->codecpar); -#endif - if ( monitor->GetOptVideoWriter() == Monitor::PASSTHROUGH ) { - // Don't care what codec, just copy parameters - video_out_ctx = avcodec_alloc_context3(nullptr); - // There might not be a useful video_in_stream. v4l in might not populate this very -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - ret = avcodec_parameters_to_context(video_out_ctx, video_in_stream->codecpar); -#else - ret = avcodec_copy_context(video_out_ctx, video_in_ctx); -#endif - if ( ret < 0 ) { - Error("Could not initialize ctx parameters"); + + if (monitor->GetOptVideoWriter() == Monitor::PASSTHROUGH) { + video_out_stream = avformat_new_stream(oc, nullptr); + if (!video_out_stream) { + Error("Unable to create video out stream"); return false; } - video_out_ctx->pix_fmt = fix_deprecated_pix_fmt(video_out_ctx->pix_fmt); - if ( oc->oformat->flags & AVFMT_GLOBALHEADER ) { -#if LIBAVCODEC_VERSION_CHECK(56, 35, 0, 64, 0) - video_out_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; -#else - video_out_ctx->flags |= CODEC_FLAG_GLOBAL_HEADER; -#endif - } - video_out_ctx->time_base = video_in_ctx->time_base; - if ( ! (video_out_ctx->time_base.num && video_out_ctx->time_base.den) ) { - Debug(2,"No timebase found in video in context, defaulting to Q"); - video_out_ctx->time_base = AV_TIME_BASE_Q; - } - } else if ( monitor->GetOptVideoWriter() == Monitor::ENCODE ) { + avcodec_parameters_copy(video_out_stream->codecpar, video_in_stream->codecpar); + zm_dump_codecpar(video_out_stream->codecpar); + + video_out_stream->avg_frame_rate = video_in_stream->avg_frame_rate; + // Only set orientation if doing passthrough, otherwise the frame image will be rotated + Monitor::Orientation orientation = monitor->getOrientation(); + if (orientation) { + Debug(3, "Have orientation %d", orientation); + if (orientation == Monitor::ROTATE_0) { + } else if (orientation == Monitor::ROTATE_90) { + ret = av_dict_set(&video_out_stream->metadata, "rotate", "90", 0); + if (ret < 0) Warning("%s:%d: title set failed", __FILE__, __LINE__); + } else if (orientation == Monitor::ROTATE_180) { + ret = av_dict_set(&video_out_stream->metadata, "rotate", "180", 0); + if (ret < 0) Warning("%s:%d: title set failed", __FILE__, __LINE__); + } else if (orientation == Monitor::ROTATE_270) { + ret = av_dict_set(&video_out_stream->metadata, "rotate", "270", 0); + if (ret < 0) Warning("%s:%d: title set failed", __FILE__, __LINE__); + } else { + Warning("Unsupported Orientation(%d)", orientation); + } + } // end if orientation + + if (av_dict_get(opts, "new_extradata", nullptr, AV_DICT_MATCH_CASE)) { + av_dict_set(&opts, "new_extradata", nullptr, 0); + // Special flag to tell us to open a codec to get new extraflags to fix weird h265 + video_out_codec = avcodec_find_encoder(video_in_stream->codecpar->codec_id); + if (video_out_codec) { + video_out_ctx = avcodec_alloc_context3(video_out_codec); + ret = avcodec_parameters_to_context(video_out_ctx, video_in_stream->codecpar); + + if (ret < 0) { + Error("Could not initialize ctx parameters"); + return false; + } + //video_out_ctx->pix_fmt = fix_deprecated_pix_fmt(video_out_ctx->pix_fmt); + if (oc->oformat->flags & AVFMT_GLOBALHEADER) { + video_out_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + } + video_out_ctx->time_base = video_in_ctx->time_base; + if (!(video_out_ctx->time_base.num && video_out_ctx->time_base.den)) { + Debug(2,"No timebase found in video in context, defaulting to Q"); + video_out_ctx->time_base = AV_TIME_BASE_Q; + } + video_out_ctx->bit_rate = video_in_ctx->bit_rate; + video_out_ctx->gop_size = video_in_ctx->gop_size; + video_out_ctx->has_b_frames = video_in_ctx->has_b_frames; + video_out_ctx->max_b_frames = video_in_ctx->max_b_frames; + video_out_ctx->qmin = video_in_ctx->qmin; + video_out_ctx->qmax = video_in_ctx->qmax; + + if (!av_dict_get(opts, "crf", nullptr, AV_DICT_MATCH_CASE)) { + if (av_dict_set(&opts, "crf", "23", 0)<0) + Warning("Can't set crf to 23"); + } + + if ((ret = avcodec_open2(video_out_ctx, video_out_codec, &opts)) < 0) { + Warning("Can't open video codec (%s) %s", + video_out_codec->name, + av_make_error_string(ret).c_str() + ); + video_out_codec = nullptr; + } + } // end if video_out_codec + + ret = avcodec_parameters_from_context(video_out_stream->codecpar, video_out_ctx); + if (ret < 0) { + Error("Could not initialize stream parameteres"); + } + } // end if extradata_entry + av_dict_free(&opts); + } else if (monitor->GetOptVideoWriter() == Monitor::ENCODE) { int wanted_codec = monitor->OutputCodec(); - if ( !wanted_codec ) { + if (!wanted_codec) { // default to h264 //Debug(2, "Defaulting to H264"); //wanted_codec = AV_CODEC_ID_H264; // FIXME what is the optimal codec? Probably low latency h264 which is effectively mjpeg } else { - if ( AV_CODEC_ID_H264 != 27 and wanted_codec > 3 ) { + if (AV_CODEC_ID_H264 != 27 and wanted_codec > 3) { // Older ffmpeg had AV_CODEC_ID_MPEG2VIDEO_XVMC at position 3 has been deprecated wanted_codec += 1; } @@ -214,11 +273,7 @@ bool VideoStore::open() { Debug(1, "Found video codec for %s", codec_data[i].codec_name); video_out_ctx = avcodec_alloc_context3(video_out_codec); if (oc->oformat->flags & AVFMT_GLOBALHEADER) { -#if LIBAVCODEC_VERSION_CHECK(56, 35, 0, 64, 0) video_out_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; -#else - video_out_ctx->flags |= CODEC_FLAG_GLOBAL_HEADER; -#endif } // When encoding, we are going to use the timestamp values instead of packet pts/dts @@ -233,32 +288,36 @@ bool VideoStore::open() { video_out_ctx->height = monitor->Height(); video_out_ctx->codec_type = AVMEDIA_TYPE_VIDEO; - if ( video_out_ctx->codec_id == AV_CODEC_ID_H264 ) { + if (video_out_ctx->codec_id == AV_CODEC_ID_H264) { video_out_ctx->bit_rate = 2000000; video_out_ctx->gop_size = 12; video_out_ctx->max_b_frames = 1; - } else if ( video_out_ctx->codec_id == AV_CODEC_ID_MPEG2VIDEO ) { + } else if (video_out_ctx->codec_id == AV_CODEC_ID_MPEG2VIDEO) { /* just for testing, we also add B frames */ video_out_ctx->max_b_frames = 2; - } else if ( video_out_ctx->codec_id == AV_CODEC_ID_MPEG1VIDEO ) { + } else if (video_out_ctx->codec_id == AV_CODEC_ID_MPEG1VIDEO) { /* Needed to avoid using macroblocks in which some coeffs overflow. * This does not happen with normal video, it just happens here as * the motion of the chroma plane does not match the luma plane. */ video_out_ctx->mb_decision = 2; } -#if HAVE_LIBAVUTIL_HWCONTEXT_H +#if HAVE_LIBAVUTIL_HWCONTEXT_H && LIBAVCODEC_VERSION_CHECK(57, 107, 0, 107, 0) if (codec_data[i].hwdevice_type != AV_HWDEVICE_TYPE_NONE) { Debug(1, "Setting up hwdevice"); ret = av_hwdevice_ctx_create(&hw_device_ctx, codec_data[i].hwdevice_type, nullptr, nullptr, 0); + if (0>ret) { + Error("Failed to create hwdevice_ctx"); + continue; + } AVBufferRef *hw_frames_ref; AVHWFramesContext *frames_ctx = nullptr; if (!(hw_frames_ref = av_hwframe_ctx_alloc(hw_device_ctx))) { Error("Failed to create hwaccel frame context."); - return -1; + continue; } frames_ctx = (AVHWFramesContext *)(hw_frames_ref->data); frames_ctx->format = codec_data[i].hw_pix_fmt; @@ -268,7 +327,7 @@ bool VideoStore::open() { frames_ctx->initial_pool_size = 20; if ((ret = av_hwframe_ctx_init(hw_frames_ref)) < 0) { Error("Failed to initialize hwaccel frame context." - "Error code: %s",av_err2str(ret)); + "Error code: %s", av_err2str(ret)); av_buffer_unref(&hw_frames_ref); } else { video_out_ctx->hw_frames_ctx = av_buffer_ref(hw_frames_ref); @@ -277,22 +336,10 @@ bool VideoStore::open() { } } av_buffer_unref(&hw_frames_ref); - } + av_buffer_unref(&hw_device_ctx); + } // end if hwdevice_type != NONE #endif - - AVDictionary *opts = 0; - std::string Options = monitor->GetEncoderOptions(); - Debug(2, "Options? %s", Options.c_str()); - ret = av_dict_parse_string(&opts, Options.c_str(), "=", ",#\n", 0); - if (ret < 0) { - Warning("Could not parse ffmpeg encoder options list '%s'\n", Options.c_str()); - } else { - AVDictionaryEntry *e = nullptr; - while ((e = av_dict_get(opts, "", e, AV_DICT_IGNORE_SUFFIX)) != nullptr) { - Debug(3, "Encoder Option %s=%s", e->key, e->value); - } - } - + av_dict_parse_string(&opts, Options.c_str(), "=", ",#\n", 0); if ((ret = avcodec_open2(video_out_ctx, video_out_codec, &opts)) < 0) { if (wanted_encoder != "" and wanted_encoder != "auto") { Warning("Can't open video codec (%s) %s", @@ -308,102 +355,61 @@ bool VideoStore::open() { video_out_codec = nullptr; } - Debug(1, "Success"); AVDictionaryEntry *e = nullptr; while ((e = av_dict_get(opts, "", e, AV_DICT_IGNORE_SUFFIX)) != nullptr) { Warning("Encoder Option %s not recognized by ffmpeg codec", e->key); } - if (video_out_codec) break; + av_dict_free(&opts); + if (video_out_codec) { + break; + } + // We allocate and copy in newer ffmpeg, so need to free it avcodec_free_context(&video_out_ctx); - if (hw_device_ctx) av_buffer_unref(&hw_device_ctx); + if (hw_device_ctx) { + av_buffer_unref(&hw_device_ctx); + } } // end foreach codec if (!video_out_codec) { Error("Can't open video codec!"); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - // We allocate and copy in newer ffmpeg, so need to free it - avcodec_free_context(&video_out_ctx); -#endif return false; } // end if can't open codec Debug(2, "Success opening codec"); + + video_out_stream = avformat_new_stream(oc, nullptr); + ret = avcodec_parameters_from_context(video_out_stream->codecpar, video_out_ctx); + if (ret < 0) { + Error("Could not initialize stream parameteres"); + return false; + } } // end if copying or transcoding - zm_dump_codec(video_out_ctx); } // end if video_in_stream - video_out_stream = avformat_new_stream(oc, video_out_codec); - if ( !video_out_stream ) { - Error("Unable to create video out stream"); - return false; - } max_stream_index = video_out_stream->index; -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - ret = avcodec_parameters_from_context(video_out_stream->codecpar, video_out_ctx); - if ( ret < 0 ) { - Error("Could not initialize stream parameteres"); - return false; - } -#else - avcodec_copy_context(video_out_stream->codec, video_out_ctx); -#endif - // Only set orientation if doing passthrough, otherwise the frame image will be rotated - Monitor::Orientation orientation = monitor->getOrientation(); - if ( orientation ) { - Debug(3, "Have orientation %d", orientation); - if ( orientation == Monitor::ROTATE_0 ) { - } else if ( orientation == Monitor::ROTATE_90 ) { - ret = av_dict_set(&video_out_stream->metadata, "rotate", "90", 0); - if ( ret < 0 ) Warning("%s:%d: title set failed", __FILE__, __LINE__); - } else if ( orientation == Monitor::ROTATE_180 ) { - ret = av_dict_set(&video_out_stream->metadata, "rotate", "180", 0); - if ( ret < 0 ) Warning("%s:%d: title set failed", __FILE__, __LINE__); - } else if ( orientation == Monitor::ROTATE_270 ) { - ret = av_dict_set(&video_out_stream->metadata, "rotate", "270", 0); - if ( ret < 0 ) Warning("%s:%d: title set failed", __FILE__, __LINE__); - } else { - Warning("Unsupported Orientation(%d)", orientation); - } - } // end if orientation video_out_stream->time_base = video_in_stream ? video_in_stream->time_base : AV_TIME_BASE_Q; - if ( audio_in_stream and audio_in_ctx ) { + if (audio_in_stream and audio_in_ctx) { Debug(2, "Have audio_in_stream %p", audio_in_stream); - if ( -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - audio_in_stream->codecpar->codec_id -#else - audio_in_stream->codec->codec_id -#endif - != AV_CODEC_ID_AAC - ) { - + if (CODEC(audio_in_stream)->codec_id != AV_CODEC_ID_AAC) { audio_out_codec = avcodec_find_encoder(AV_CODEC_ID_AAC); - if ( !audio_out_codec ) { + if (!audio_out_codec) { Error("Could not find codec for AAC"); } else { -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) audio_in_ctx = avcodec_alloc_context3(audio_out_codec); - ret = avcodec_parameters_to_context(audio_in_ctx, - audio_in_stream->codecpar); + ret = avcodec_parameters_to_context(audio_in_ctx, audio_in_stream->codecpar); audio_in_ctx->time_base = audio_in_stream->time_base; -#else - audio_in_ctx = audio_in_stream->codec; -#endif -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) audio_out_ctx = avcodec_alloc_context3(audio_out_codec); - if ( !audio_out_ctx ) { + if (!audio_out_ctx) { Error("could not allocate codec ctx for AAC"); return false; } -#else - audio_out_ctx = audio_out_stream->codec; -#endif + audio_out_stream = avformat_new_stream(oc, audio_out_codec); audio_out_stream->time_base = audio_in_stream->time_base; - if ( !setup_resampler() ) { + if (!setup_resampler()) { return false; } } // end if found AAC codec @@ -413,16 +419,15 @@ bool VideoStore::open() { // normally we want to pass params from codec in here // but since we are doing audio passthrough we don't care audio_out_stream = avformat_new_stream(oc, audio_out_codec); - if ( !audio_out_stream ) { + if (!audio_out_stream) { Error("Could not allocate new stream"); return false; } audio_out_stream->time_base = audio_in_stream->time_base; -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) // Just use the ctx to copy the parameters over audio_out_ctx = avcodec_alloc_context3(audio_out_codec); - if ( !audio_out_ctx ) { + if (!audio_out_ctx) { Error("Could not allocate new output_context"); return false; } @@ -433,29 +438,18 @@ bool VideoStore::open() { // Copy params from instream to ctx ret = avcodec_parameters_to_context( audio_out_ctx, audio_in_stream->codecpar); - if ( ret < 0 ) { + if (ret < 0) { Error("Unable to copy audio params to ctx %s", av_make_error_string(ret).c_str()); } ret = avcodec_parameters_from_context( audio_out_stream->codecpar, audio_out_ctx); - if ( ret < 0 ) { + if (ret < 0) { Error("Unable to copy audio params to stream %s", av_make_error_string(ret).c_str()); } -#else - audio_out_ctx = audio_out_stream->codec; - ret = avcodec_copy_context(audio_out_ctx, audio_in_stream->codec); - if ( ret < 0 ) { - Error("Unable to copy audio ctx %s", - av_make_error_string(ret).c_str()); - audio_out_stream = nullptr; - return false; - } // end if - audio_out_ctx->codec_tag = 0; -#endif - if ( audio_out_ctx->channels > 1 ) { + if (audio_out_ctx->channels > 1) { Warning("Audio isn't mono, changing it."); audio_out_ctx->channels = 1; } else { @@ -463,12 +457,8 @@ bool VideoStore::open() { } } // end if is AAC - if ( oc->oformat->flags & AVFMT_GLOBALHEADER ) { -#if LIBAVCODEC_VERSION_CHECK(56, 35, 0, 64, 0) + if (oc->oformat->flags & AVFMT_GLOBALHEADER) { audio_out_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; -#else - audio_out_ctx->flags |= CODEC_FLAG_GLOBAL_HEADER; -#endif } // We will assume that subsequent stream allocations will increase the index @@ -477,14 +467,14 @@ bool VideoStore::open() { //max_stream_index is 0-based, so add 1 next_dts = new int64_t[max_stream_index+1]; - for ( int i = 0; i <= max_stream_index; i++ ) { + for (int i = 0; i <= max_stream_index; i++) { next_dts[i] = 0; } /* open the out file, if needed */ - if ( !(out_format->flags & AVFMT_NOFILE) ) { + if (!(out_format->flags & AVFMT_NOFILE)) { ret = avio_open2(&oc->pb, filename, AVIO_FLAG_WRITE, nullptr, nullptr); - if ( ret < 0 ) { + if (ret < 0) { Error("Could not open out file '%s': %s", filename, av_make_error_string(ret).c_str()); return false; @@ -492,39 +482,31 @@ bool VideoStore::open() { } zm_dump_stream_format(oc, 0, 0, 1); - if ( audio_out_stream ) zm_dump_stream_format(oc, 1, 0, 1); - - AVDictionary *opts = nullptr; - - std::string option_string = monitor->GetEncoderOptions(); - ret = av_dict_parse_string(&opts, option_string.c_str(), "=", ",\n", 0); - if ( ret < 0 ) { - Warning("Could not parse ffmpeg output options '%s'", option_string.c_str()); - } + if (audio_out_stream) zm_dump_stream_format(oc, 1, 0, 1); const AVDictionaryEntry *movflags_entry = av_dict_get(opts, "movflags", nullptr, AV_DICT_MATCH_CASE); - if ( !movflags_entry ) { + if (!movflags_entry) { Debug(1, "setting movflags to frag_keyframe+empty_moov"); // Shiboleth reports that this may break seeking in mp4 before it downloads av_dict_set(&opts, "movflags", "frag_keyframe+empty_moov", 0); } else { Debug(1, "using movflags %s", movflags_entry->value); } - if ( (ret = avformat_write_header(oc, &opts)) < 0 ) { + if ((ret = avformat_write_header(oc, &opts)) < 0) { Warning("Unable to set movflags trying with defaults."); ret = avformat_write_header(oc, nullptr); - } else if ( av_dict_count(opts) != 0 ) { + } else if (av_dict_count(opts) != 0) { Info("some options not used, turn on debugging for a list."); AVDictionaryEntry *e = nullptr; - while ( (e = av_dict_get(opts, "", e, AV_DICT_IGNORE_SUFFIX)) != nullptr ) { + while ((e = av_dict_get(opts, "", e, AV_DICT_IGNORE_SUFFIX)) != nullptr) { Debug(1, "Encoder Option %s=>%s", e->key, e->value); - if ( !e->value ) { + if (!e->value) { av_dict_set(&opts, e->key, nullptr, 0); } } } - if ( opts ) av_dict_free(&opts); - if ( ret < 0 ) { + if (opts) av_dict_free(&opts); + if (ret < 0) { Error("Error occurred when writing out file header to %s: %s", filename, av_make_error_string(ret).c_str()); avio_closep(&oc->pb); @@ -537,7 +519,6 @@ bool VideoStore::open() { } // end bool VideoStore::open() void VideoStore::flush_codecs() { - int ret; // The codec queues data. We need to send a flush command and out // whatever we get. Failures are not fatal. AVPacket pkt; @@ -547,15 +528,9 @@ void VideoStore::flush_codecs() { av_init_packet(&pkt); // I got crashes if the codec didn't do DELAY, so let's test for it. - if ( video_out_ctx->codec && ( video_out_ctx->codec->capabilities & -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - AV_CODEC_CAP_DELAY -#else - CODEC_CAP_DELAY -#endif - ) ) { + if (video_out_ctx && video_out_ctx->codec && (video_out_ctx->codec->capabilities & AV_CODEC_CAP_DELAY)) { // Put encoder into flushing mode - while ( (ret = zm_send_frame_receive_packet(video_out_ctx, nullptr, pkt) ) > 0 ) { + while ((zm_send_frame_receive_packet(video_out_ctx, nullptr, pkt)) > 0) { av_packet_rescale_ts(&pkt, video_out_ctx->time_base, video_out_stream->time_base); @@ -565,7 +540,7 @@ void VideoStore::flush_codecs() { Debug(1, "Done writing buffered video."); } // end if have delay capability - if ( audio_out_codec ) { + if (audio_out_codec) { // The codec queues data. We need to send a flush command and out // whatever we get. Failures are not fatal. @@ -573,13 +548,13 @@ void VideoStore::flush_codecs() { /* * At the end of the file, we pass the remaining samples to * the encoder. */ - while ( zm_resample_get_delay(resample_ctx, audio_out_ctx->sample_rate) ) { + while (zm_resample_get_delay(resample_ctx, audio_out_ctx->sample_rate)) { zm_resample_audio(resample_ctx, nullptr, out_frame); - if ( zm_add_samples_to_fifo(fifo, out_frame) ) { + if (zm_add_samples_to_fifo(fifo, out_frame)) { // Should probably set the frame size to what is reported FIXME - if ( zm_get_samples_from_fifo(fifo, out_frame) ) { - if ( zm_send_frame_receive_packet(audio_out_ctx, out_frame, pkt) > 0 ) { + if (zm_get_samples_from_fifo(fifo, out_frame)) { + if (zm_send_frame_receive_packet(audio_out_ctx, out_frame, pkt) > 0) { av_packet_rescale_ts(&pkt, audio_out_ctx->time_base, audio_out_stream->time_base); @@ -588,11 +563,10 @@ void VideoStore::flush_codecs() { } } // end if data returned from fifo } - } // end while have buffered samples in the resampler Debug(2, "av_audio_fifo_size = %d", av_audio_fifo_size(fifo)); - while ( av_audio_fifo_size(fifo) > 0 ) { + while (av_audio_fifo_size(fifo) > 0) { /* Take one frame worth of audio samples from the FIFO buffer, * encode it and write it to the output file. */ @@ -600,8 +574,8 @@ void VideoStore::flush_codecs() { frame_size, av_audio_fifo_size(fifo)); // SHould probably set the frame size to what is reported FIXME - if ( av_audio_fifo_read(fifo, (void **)out_frame->data, frame_size) ) { - if ( zm_send_frame_receive_packet(audio_out_ctx, out_frame, pkt) ) { + if (av_audio_fifo_read(fifo, (void **)out_frame->data, frame_size)) { + if (zm_send_frame_receive_packet(audio_out_ctx, out_frame, pkt)) { pkt.stream_index = audio_out_stream->index; av_packet_rescale_ts(&pkt, @@ -613,13 +587,11 @@ void VideoStore::flush_codecs() { } // end if data returned from fifo } // end while still data in the fifo -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) // Put encoder into flushing mode avcodec_send_frame(audio_out_ctx, nullptr); -#endif - while (1) { - if ( 0 >= zm_receive_packet(audio_out_ctx, pkt) ) { + while (true) { + if (0 >= zm_receive_packet(audio_out_ctx, pkt)) { Debug(1, "No more packets"); break; } @@ -634,33 +606,33 @@ void VideoStore::flush_codecs() { } // end flush_codecs VideoStore::~VideoStore() { - if ( oc->pb ) { + if (oc->pb) { flush_codecs(); // Flush Queues - Debug(1, "Flushing interleaved queues"); + Debug(4, "Flushing interleaved queues"); av_interleaved_write_frame(oc, nullptr); Debug(1, "Writing trailer"); /* Write the trailer before close */ - if ( int rc = av_write_trailer(oc) ) { + if (int rc = av_write_trailer(oc)) { Error("Error writing trailer %s", av_err2str(rc)); } else { Debug(3, "Success Writing trailer"); } // When will we not be using a file ? - if ( !(out_format->flags & AVFMT_NOFILE) ) { + if (!(out_format->flags & AVFMT_NOFILE)) { /* Close the out file. */ - Debug(2, "Closing"); - if ( int rc = avio_close(oc->pb) ) { + Debug(4, "Closing"); + if (int rc = avio_close(oc->pb)) { Error("Error closing avio %s", av_err2str(rc)); } } else { Debug(3, "Not closing avio because we are not writing to a file."); } oc->pb = nullptr; - } // end if oc->pb + } // end if oc->pb // I wonder if we should be closing the file first. // I also wonder if we really need to be doing all the ctx @@ -668,56 +640,46 @@ VideoStore::~VideoStore() { // Just do a file open/close/writeheader/etc. // What if we were only doing audio recording? - if ( video_out_stream ) { - video_in_ctx = nullptr; + video_in_ctx = nullptr; - Debug(4, "Freeing video_out_ctx"); + if (video_out_ctx) { + avcodec_close(video_out_ctx); + Debug(3, "Freeing video_out_ctx"); avcodec_free_context(&video_out_ctx); - } // end if video_out_stream + if (hw_device_ctx) { + Debug(3, "Freeing hw_device_ctx"); + av_buffer_unref(&hw_device_ctx); + } + } - if ( audio_out_stream ) { -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - // We allocate and copy in newer ffmpeg, so need to free it - //avcodec_free_context(&audio_in_ctx); -#endif - //Debug(4, "Success freeing audio_in_ctx"); + if (audio_out_stream) { audio_in_codec = nullptr; - if ( audio_out_ctx ) { + if (audio_out_ctx) { Debug(4, "Success closing audio_out_ctx"); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + avcodec_close(audio_out_ctx); avcodec_free_context(&audio_out_ctx); -#endif } -#if defined(HAVE_LIBAVRESAMPLE) || defined(HAVE_LIBSWRESAMPLE) - if ( resample_ctx ) { - if ( fifo ) { + if (resample_ctx) { + if (fifo) { av_audio_fifo_free(fifo); fifo = nullptr; } - #if defined(HAVE_LIBSWRESAMPLE) swr_free(&resample_ctx); - #else - #if defined(HAVE_LIBAVRESAMPLE) - avresample_close(resample_ctx); - avresample_free(&resample_ctx); - #endif - #endif } - if ( in_frame ) { + if (in_frame) { av_frame_free(&in_frame); in_frame = nullptr; } - if ( out_frame ) { + if (out_frame) { av_frame_free(&out_frame); out_frame = nullptr; } - if ( converted_in_samples ) { + if (converted_in_samples) { av_free(converted_in_samples); converted_in_samples = nullptr; } -#endif } // end if audio_out_stream Debug(4, "free context"); @@ -728,40 +690,21 @@ VideoStore::~VideoStore() { } // VideoStore::~VideoStore() bool VideoStore::setup_resampler() { -#if !defined(HAVE_LIBSWRESAMPLE) && !defined(HAVE_LIBAVRESAMPLE) - Error("%s", "Not built with resample library. Cannot do audio conversion to AAC"); - return false; -#else int ret; -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) // Newer ffmpeg wants to keep everything separate... so have to lookup our own // decoder, can't reuse the one from the camera. - audio_in_codec = - avcodec_find_decoder(audio_in_stream->codecpar->codec_id); + audio_in_codec = avcodec_find_decoder(audio_in_stream->codecpar->codec_id); audio_in_ctx = avcodec_alloc_context3(audio_in_codec); // Copy params from instream to ctx - ret = avcodec_parameters_to_context( - audio_in_ctx, audio_in_stream->codecpar); - if ( ret < 0 ) { + ret = avcodec_parameters_to_context(audio_in_ctx, audio_in_stream->codecpar); + if (ret < 0) { Error("Unable to copy audio params to ctx %s", av_make_error_string(ret).c_str()); } -#else -// codec is already open in ffmpeg_camera - audio_in_ctx = audio_in_stream->codec; - audio_in_codec = reinterpret_cast(audio_in_ctx->codec); - if (!audio_in_codec) { - audio_in_codec = avcodec_find_decoder(audio_in_stream->codec->codec_id); - if (!audio_in_codec) { - return false; - } - } -#endif - // if the codec is already open, nothing is done. - if ( (ret = avcodec_open2(audio_in_ctx, audio_in_codec, nullptr)) < 0 ) { + if ((ret = avcodec_open2(audio_in_ctx, audio_in_codec, nullptr)) < 0) { Error("Can't open audio in codec!"); return false; } @@ -769,7 +712,7 @@ bool VideoStore::setup_resampler() { Debug(2, "Got something other than AAC (%s)", audio_in_codec->name); // Some formats (i.e. WAV) do not produce the proper channel layout - if ( audio_in_ctx->channel_layout == 0 ) { + if (audio_in_ctx->channel_layout == 0) { Debug(2, "Setting input channel layout to mono"); // Perhaps we should not be modifying the audio_in_ctx.... audio_in_ctx->channel_layout = av_get_channel_layout("mono"); @@ -782,36 +725,34 @@ bool VideoStore::setup_resampler() { audio_out_ctx->channels = audio_in_ctx->channels; audio_out_ctx->channel_layout = audio_in_ctx->channel_layout; audio_out_ctx->sample_fmt = audio_in_ctx->sample_fmt; -#if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100) - if ( !audio_out_ctx->channel_layout ) { + if (!audio_out_ctx->channel_layout) { Debug(3, "Correcting channel layout from (%" PRIi64 ") to (%" PRIi64 ")", audio_out_ctx->channel_layout, av_get_default_channel_layout(audio_out_ctx->channels) ); audio_out_ctx->channel_layout = av_get_default_channel_layout(audio_out_ctx->channels); } -#endif - if ( audio_out_codec->supported_samplerates ) { + + if (audio_out_codec->supported_samplerates) { int found = 0; - for ( unsigned int i = 0; audio_out_codec->supported_samplerates[i]; i++ ) { - if ( audio_out_ctx->sample_rate == - audio_out_codec->supported_samplerates[i] ) { + for (unsigned int i = 0; audio_out_codec->supported_samplerates[i]; i++) { + if (audio_out_ctx->sample_rate == + audio_out_codec->supported_samplerates[i]) { found = 1; break; } } - if ( found ) { + if (found) { Debug(3, "Sample rate is good %d", audio_out_ctx->sample_rate); } else { - audio_out_ctx->sample_rate = - audio_out_codec->supported_samplerates[0]; + audio_out_ctx->sample_rate = audio_out_codec->supported_samplerates[0]; Debug(1, "Sample rate is no good, setting to (%d)", audio_out_codec->supported_samplerates[0]); } } /* check that the encoder supports s16 pcm in */ - if ( !check_sample_fmt(audio_out_codec, audio_out_ctx->sample_fmt) ) { + if (!check_sample_fmt(audio_out_codec, audio_out_ctx->sample_fmt)) { Debug(3, "Encoder does not support sample format %s, setting to FLTP", av_get_sample_fmt_name(audio_out_ctx->sample_fmt)); audio_out_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP; @@ -822,12 +763,12 @@ bool VideoStore::setup_resampler() { AVDictionary *opts = nullptr; // Needed to allow AAC - if ( (ret = av_dict_set(&opts, "strict", "experimental", 0)) < 0 ) { + if ((ret = av_dict_set(&opts, "strict", "experimental", 0)) < 0) { Error("Couldn't set experimental"); } ret = avcodec_open2(audio_out_ctx, audio_out_codec, &opts); av_dict_free(&opts); - if ( ret < 0 ) { + if (ret < 0) { Error("could not open codec (%d) (%s)", ret, av_make_error_string(ret).c_str()); audio_out_codec = nullptr; @@ -838,15 +779,11 @@ bool VideoStore::setup_resampler() { zm_dump_codec(audio_out_ctx); audio_out_stream->time_base = (AVRational){1, audio_out_ctx->sample_rate}; -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - if ( (ret = avcodec_parameters_from_context( - audio_out_stream->codecpar, - audio_out_ctx)) < 0 ) { + if ((ret = avcodec_parameters_from_context(audio_out_stream->codecpar, audio_out_ctx)) < 0) { Error("Could not initialize stream parameteres"); return false; } zm_dump_codecpar(audio_out_stream->codecpar); -#endif Debug(3, "Time bases: AUDIO in stream (%d/%d) in codec: (%d/%d) out " @@ -867,23 +804,14 @@ bool VideoStore::setup_resampler() { audio_out_ctx->channels, audio_out_ctx->sample_fmt, audio_out_ctx->channel_layout, audio_out_ctx->frame_size); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) Debug(1, "Audio out stream bit_rate (%" PRIi64 ") sample_rate(%d) channels(%d) fmt(%d) layout(%" PRIi64 ") frame_size(%d)", audio_out_stream->codecpar->bit_rate, audio_out_stream->codecpar->sample_rate, audio_out_stream->codecpar->channels, audio_out_stream->codecpar->format, audio_out_stream->codecpar->channel_layout, audio_out_stream->codecpar->frame_size); -#else - Debug(1, - "Audio out bit_rate (%d) sample_rate(%d) channels(%d) fmt(%d) " - "layout(%" PRIi64 ") frame_size(%d)", - audio_out_stream->codec->bit_rate, audio_out_stream->codec->sample_rate, - audio_out_stream->codec->channels, audio_out_stream->codec->sample_fmt, - audio_out_stream->codec->channel_layout, audio_out_stream->codec->frame_size); -#endif /** Create a new frame to store the audio samples. */ - if ( ! in_frame ) { + if (!in_frame) { if (!(in_frame = zm_av_frame_alloc())) { Error("Could not allocate in frame"); return false; @@ -891,20 +819,19 @@ bool VideoStore::setup_resampler() { } /** Create a new frame to store the audio samples. */ - if ( !(out_frame = zm_av_frame_alloc()) ) { + if (!(out_frame = zm_av_frame_alloc())) { Error("Could not allocate out frame"); av_frame_free(&in_frame); return false; } out_frame->sample_rate = audio_out_ctx->sample_rate; - if ( !(fifo = av_audio_fifo_alloc( + if (!(fifo = av_audio_fifo_alloc( audio_out_ctx->sample_fmt, - audio_out_ctx->channels, 1)) ) { + audio_out_ctx->channels, 1))) { Error("Could not allocate FIFO"); return false; } -#if defined(HAVE_LIBSWRESAMPLE) resample_ctx = swr_alloc_set_opts(nullptr, audio_out_ctx->channel_layout, audio_out_ctx->sample_fmt, @@ -913,13 +840,13 @@ bool VideoStore::setup_resampler() { audio_in_ctx->sample_fmt, audio_in_ctx->sample_rate, 0, nullptr); - if ( !resample_ctx ) { + if (!resample_ctx) { Error("Could not allocate resample context"); av_frame_free(&in_frame); av_frame_free(&out_frame); return false; } - if ( (ret = swr_init(resample_ctx)) < 0 ) { + if ((ret = swr_init(resample_ctx)) < 0) { Error("Could not open resampler"); av_frame_free(&in_frame); av_frame_free(&out_frame); @@ -927,49 +854,10 @@ bool VideoStore::setup_resampler() { return false; } Debug(1,"Success setting up SWRESAMPLE"); -#else -#if defined(HAVE_LIBAVRESAMPLE) - // Setup the audio resampler - resample_ctx = avresample_alloc_context(); - - if ( !resample_ctx ) { - Error("Could not allocate resample ctx"); - av_frame_free(&in_frame); - av_frame_free(&out_frame); - return false; - } - - av_opt_set_int(resample_ctx, "in_channel_layout", - audio_in_ctx->channel_layout, 0); - av_opt_set_int(resample_ctx, "in_sample_fmt", - audio_in_ctx->sample_fmt, 0); - av_opt_set_int(resample_ctx, "in_sample_rate", - audio_in_ctx->sample_rate, 0); - av_opt_set_int(resample_ctx, "in_channels", - audio_in_ctx->channels, 0); - av_opt_set_int(resample_ctx, "out_channel_layout", - audio_in_ctx->channel_layout, 0); - av_opt_set_int(resample_ctx, "out_sample_fmt", - audio_out_ctx->sample_fmt, 0); - av_opt_set_int(resample_ctx, "out_sample_rate", - audio_out_ctx->sample_rate, 0); - av_opt_set_int(resample_ctx, "out_channels", - audio_out_ctx->channels, 0); - - if ( (ret = avresample_open(resample_ctx)) < 0 ) { - Error("Could not open resample ctx"); - return false; - } else { - Debug(2, "Success opening resampler"); - } -#endif -#endif out_frame->nb_samples = audio_out_ctx->frame_size; out_frame->format = audio_out_ctx->sample_fmt; -#if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100) out_frame->channels = audio_out_ctx->channels; -#endif out_frame->channel_layout = audio_out_ctx->channel_layout; out_frame->sample_rate = audio_out_ctx->sample_rate; @@ -981,7 +869,7 @@ bool VideoStore::setup_resampler() { audio_out_ctx->sample_fmt, 0); converted_in_samples = reinterpret_cast(av_malloc(audioSampleBuffer_size)); - if ( !converted_in_samples ) { + if (!converted_in_samples) { Error("Could not allocate converted in sample pointers"); return false; } else { @@ -989,31 +877,29 @@ bool VideoStore::setup_resampler() { } // Setup the data pointers in the AVFrame - if ( avcodec_fill_audio_frame( + if (avcodec_fill_audio_frame( out_frame, audio_out_ctx->channels, audio_out_ctx->sample_fmt, (const uint8_t *)converted_in_samples, - audioSampleBuffer_size, 0) < 0 ) { + audioSampleBuffer_size, 0) < 0) { Error("Could not allocate converted in sample pointers"); return false; } return true; -#endif } // end bool VideoStore::setup_resampler() -int VideoStore::writePacket(ZMPacket *ipkt) { - if ( ipkt->codec_type == AVMEDIA_TYPE_VIDEO ) { +int VideoStore::writePacket(const std::shared_ptr &ipkt) { + if (ipkt->codec_type == AVMEDIA_TYPE_VIDEO) { return writeVideoFramePacket(ipkt); - } else if ( ipkt->codec_type == AVMEDIA_TYPE_AUDIO ) { + } else if (ipkt->codec_type == AVMEDIA_TYPE_AUDIO) { return writeAudioFramePacket(ipkt); } Error("Unknown stream type in packet (%d)", ipkt->codec_type); return 0; } -int VideoStore::writeVideoFramePacket(ZMPacket *zm_packet) { - int ret; +int VideoStore::writeVideoFramePacket(const std::shared_ptr &zm_packet) { frame_count += 1; // if we have to transcode @@ -1021,10 +907,11 @@ int VideoStore::writeVideoFramePacket(ZMPacket *zm_packet) { Debug(3, "Have encoding video frame count (%d)", frame_count); if (!zm_packet->out_frame) { - Debug(3, "Have no out frame. codec is %s sw_pf %d %s hw_pf %d %s", + Debug(3, "Have no out frame. codec is %s sw_pf %d %s hw_pf %d %s %dx%d", chosen_codec_data->codec_name, chosen_codec_data->sw_pix_fmt, av_get_pix_fmt_name(chosen_codec_data->sw_pix_fmt), - chosen_codec_data->hw_pix_fmt, av_get_pix_fmt_name(chosen_codec_data->hw_pix_fmt) + chosen_codec_data->hw_pix_fmt, av_get_pix_fmt_name(chosen_codec_data->hw_pix_fmt), + video_out_ctx->width, video_out_ctx->height ); AVFrame *out_frame = zm_packet->get_out_frame(video_out_ctx->width, video_out_ctx->height, chosen_codec_data->sw_pix_fmt); if (!out_frame) { @@ -1044,20 +931,19 @@ int VideoStore::writeVideoFramePacket(ZMPacket *zm_packet) { video_out_ctx->width, video_out_ctx->height ); - } else if ( !zm_packet->in_frame ) { + } else if (!zm_packet->in_frame) { Debug(4, "Have no in_frame"); if (zm_packet->packet.size and !zm_packet->decoded) { Debug(4, "Decoding"); - if ( !zm_packet->decode(video_in_ctx) ) { + if (!zm_packet->decode(video_in_ctx)) { Debug(2, "unable to decode yet."); return 0; } // Go straight to out frame swscale.Convert(zm_packet->in_frame, out_frame); - } else { - Error("Have neither in_frame or image in packet %p %d!", - zm_packet, zm_packet->image_index); + Error("Have neither in_frame or image in packet %d!", + zm_packet->image_index); return 0; } // end if has packet or image } else { @@ -1070,6 +956,7 @@ int VideoStore::writeVideoFramePacket(ZMPacket *zm_packet) { #if HAVE_LIBAVUTIL_HWCONTEXT_H if (video_out_ctx->hw_frames_ctx) { + int ret; if (!(hw_frame = av_frame_alloc())) { ret = AVERROR(ENOMEM); return ret; @@ -1100,29 +987,26 @@ int VideoStore::writeVideoFramePacket(ZMPacket *zm_packet) { // Do this to allow the encoder to choose whether to use I/P/B frame //zm_packet->out_frame->pict_type = AV_PICTURE_TYPE_NONE; //zm_packet->out_frame->key_frame = zm_packet->keyframe; -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) frame->pkt_duration = 0; -#endif - int64_t in_pts = zm_packet->timestamp->tv_sec * (uint64_t)1000000 + zm_packet->timestamp->tv_usec; - if ( !video_first_pts ) { - video_first_pts = in_pts; - Debug(2, "No video_first_pts, set to (%" PRId64 ") secs(%" PRIi64 ") usecs(%" PRIi64 ")", + if (!video_first_pts) { + video_first_pts = zm_packet->timestamp.time_since_epoch().count(); + Debug(2, "No video_first_pts, set to (%" PRId64 ") secs(%.2f)", video_first_pts, - static_cast(zm_packet->timestamp->tv_sec), - static_cast(zm_packet->timestamp->tv_usec)); + FPSeconds(zm_packet->timestamp.time_since_epoch()).count()); + frame->pts = 0; } else { - uint64_t useconds = in_pts - video_first_pts; - frame->pts = av_rescale_q(useconds, AV_TIME_BASE_Q, video_out_ctx->time_base); + Microseconds useconds = std::chrono::duration_cast( + zm_packet->timestamp - SystemTimePoint(Microseconds(video_first_pts))); + frame->pts = av_rescale_q(useconds.count(), AV_TIME_BASE_Q, video_out_ctx->time_base); Debug(2, - "Setting pts for frame(%d) to (%" PRId64 ") from (start %" PRIu64 " - %" PRIu64 " - secs(%" PRIi64 ") usecs(%" PRIi64 ") @ %d/%d", + "Setting pts for frame(%d) to (%" PRId64 ") from (start %" PRIu64 " - %" PRIu64 " - us(%" PRIi64 ") @ %d/%d", frame_count, frame->pts, video_first_pts, - useconds, - static_cast(zm_packet->timestamp->tv_sec), - static_cast(zm_packet->timestamp->tv_usec), + static_cast(std::chrono::duration_cast(useconds).count()), + static_cast(std::chrono::duration_cast(zm_packet->timestamp.time_since_epoch()).count()), video_out_ctx->time_base.num, video_out_ctx->time_base.den); } @@ -1131,7 +1015,7 @@ int VideoStore::writeVideoFramePacket(ZMPacket *zm_packet) { opkt.data = nullptr; opkt.size = 0; - ret = zm_send_frame_receive_packet(video_out_ctx, frame, opkt); + int ret = zm_send_frame_receive_packet(video_out_ctx, frame, opkt); if (ret <= 0) { if (ret < 0) { Error("Could not send frame (error '%s')", av_make_error_string(ret).c_str()); @@ -1141,9 +1025,9 @@ int VideoStore::writeVideoFramePacket(ZMPacket *zm_packet) { ZM_DUMP_PACKET(opkt, "packet returned by codec"); // Need to adjust pts/dts values from codec time to stream time - if ( opkt.pts != AV_NOPTS_VALUE ) + if (opkt.pts != AV_NOPTS_VALUE) opkt.pts = av_rescale_q(opkt.pts, video_out_ctx->time_base, video_out_stream->time_base); - if ( opkt.dts != AV_NOPTS_VALUE ) + if (opkt.dts != AV_NOPTS_VALUE) opkt.dts = av_rescale_q(opkt.dts, video_out_ctx->time_base, video_out_stream->time_base); Debug(1, "Timebase conversions using %d/%d -> %d/%d", video_out_ctx->time_base.num, @@ -1151,10 +1035,9 @@ int VideoStore::writeVideoFramePacket(ZMPacket *zm_packet) { video_out_stream->time_base.num, video_out_stream->time_base.den); - int64_t duration = 0; - if ( zm_packet->in_frame ) { - if ( zm_packet->in_frame->pkt_duration ) { + if (zm_packet->in_frame) { + if (zm_packet->in_frame->pkt_duration) { duration = av_rescale_q( zm_packet->in_frame->pkt_duration, video_in_stream->time_base, @@ -1168,9 +1051,8 @@ int VideoStore::writeVideoFramePacket(ZMPacket *zm_packet) { video_out_stream->time_base.num, video_out_stream->time_base.den ); - } else if ( video_last_pts != AV_NOPTS_VALUE ) { - duration = - av_rescale_q( + } else if (video_last_pts != AV_NOPTS_VALUE) { + duration = av_rescale_q( zm_packet->in_frame->pts - video_last_pts, video_in_stream->time_base, video_out_stream->time_base); @@ -1180,8 +1062,10 @@ int VideoStore::writeVideoFramePacket(ZMPacket *zm_packet) { zm_packet->in_frame->pts - video_last_pts, duration ); - if ( duration <= 0 ) { - duration = zm_packet->in_frame->pkt_duration ? zm_packet->in_frame->pkt_duration : av_rescale_q(1, video_in_stream->time_base, video_out_stream->time_base); + if (duration <= 0) { + duration = zm_packet->in_frame->pkt_duration ? + zm_packet->in_frame->pkt_duration : + av_rescale_q(1, video_in_stream->time_base, video_out_stream->time_base); } } // end if in_frmae->pkt_duration video_last_pts = zm_packet->in_frame->pts; @@ -1189,7 +1073,6 @@ int VideoStore::writeVideoFramePacket(ZMPacket *zm_packet) { //duration = av_rescale_q(zm_packet->out_frame->pts - video_last_pts, video_in_stream->time_base, video_out_stream->time_base); } // end if in_frmae opkt.duration = duration; - } else { // Passthrough AVPacket *ipkt = &zm_packet->packet; ZM_DUMP_STREAM_PACKET(video_in_stream, (*ipkt), "Doing passthrough, just copy packet"); @@ -1200,8 +1083,8 @@ int VideoStore::writeVideoFramePacket(ZMPacket *zm_packet) { opkt.flags = ipkt->flags; opkt.duration = ipkt->duration; - if ( ipkt->dts != AV_NOPTS_VALUE ) { - if ( !video_first_dts ) { + if (ipkt->dts != AV_NOPTS_VALUE) { + if (!video_first_dts) { Debug(2, "Starting video first_dts will become %" PRId64, ipkt->dts); video_first_dts = ipkt->dts; } @@ -1210,14 +1093,13 @@ int VideoStore::writeVideoFramePacket(ZMPacket *zm_packet) { opkt.dts = next_dts[video_out_stream->index] ? av_rescale_q(next_dts[video_out_stream->index], video_out_stream->time_base, video_in_stream->time_base) : 0; Debug(3, "Setting dts to video_next_dts %" PRId64 " from %" PRId64, opkt.dts, next_dts[video_out_stream->index]); } - if ( ipkt->pts != AV_NOPTS_VALUE ) { + if (ipkt->pts != AV_NOPTS_VALUE) { opkt.pts = ipkt->pts - video_first_dts; } else { opkt.pts = AV_NOPTS_VALUE; } av_packet_rescale_ts(&opkt, video_in_stream->time_base, video_out_stream->time_base); - ZM_DUMP_STREAM_PACKET(video_out_stream, opkt, "after pts adjustment"); } // end if codec matches @@ -1228,19 +1110,18 @@ int VideoStore::writeVideoFramePacket(ZMPacket *zm_packet) { return 1; } // end int VideoStore::writeVideoFramePacket( AVPacket *ipkt ) -int VideoStore::writeAudioFramePacket(ZMPacket *zm_packet) { - - AVPacket *ipkt = &zm_packet->packet; - int ret; - - if ( !audio_out_stream ) { +int VideoStore::writeAudioFramePacket(const std::shared_ptr &zm_packet) { + if (!audio_out_stream) { Debug(1, "Called writeAudioFramePacket when no audio_out_stream"); return 0; // FIXME -ve return codes do not free packet in ffmpeg_camera at the moment } + + AVPacket *ipkt = &zm_packet->packet; + int ret; ZM_DUMP_STREAM_PACKET(audio_in_stream, (*ipkt), "input packet"); - if ( !audio_first_dts ) { + if (!audio_first_dts) { audio_first_dts = ipkt->dts; audio_next_pts = audio_out_ctx->frame_size; } @@ -1248,10 +1129,10 @@ int VideoStore::writeAudioFramePacket(ZMPacket *zm_packet) { Debug(3, "audio first_dts to %" PRId64, audio_first_dts); // Need to adjust pts before feeding to decoder.... should really copy the pkt instead of modifying it - if ( audio_out_codec ) { + if (audio_out_codec) { // I wonder if we can get multiple frames per packet? Probably ret = zm_send_packet_receive_frame(audio_in_ctx, in_frame, *ipkt); - if ( ret < 0 ) { + if (ret < 0) { Debug(3, "failed to receive frame code: %d", ret); return 0; } @@ -1259,15 +1140,15 @@ int VideoStore::writeAudioFramePacket(ZMPacket *zm_packet) { AVFrame *input_frame = in_frame; - while ( zm_resample_audio(resample_ctx, input_frame, out_frame) ) { + while (zm_resample_audio(resample_ctx, input_frame, out_frame)) { //out_frame->pkt_duration = in_frame->pkt_duration; // resampling doesn't alter duration - if ( zm_add_samples_to_fifo(fifo, out_frame) <= 0 ) + if (zm_add_samples_to_fifo(fifo, out_frame) <= 0) break; // We put the samples into the fifo so we are basically resetting the frame out_frame->nb_samples = audio_out_ctx->frame_size; - if ( zm_get_samples_from_fifo(fifo, out_frame) <= 0 ) + if (zm_get_samples_from_fifo(fifo, out_frame) <= 0) break; out_frame->pts = audio_next_pts; @@ -1276,7 +1157,7 @@ int VideoStore::writeAudioFramePacket(ZMPacket *zm_packet) { zm_dump_frame(out_frame, "Out frame after resample"); av_init_packet(&opkt); - if ( zm_send_frame_receive_packet(audio_out_ctx, out_frame, opkt) <= 0 ) + if (zm_send_frame_receive_packet(audio_out_ctx, out_frame, opkt) <= 0) break; // Scale the PTS of the outgoing packet to be the correct time base @@ -1287,12 +1168,11 @@ int VideoStore::writeAudioFramePacket(ZMPacket *zm_packet) { write_packet(&opkt, audio_out_stream); zm_av_packet_unref(&opkt); - if ( zm_resample_get_delay(resample_ctx, out_frame->sample_rate) < out_frame->nb_samples) + if (zm_resample_get_delay(resample_ctx, out_frame->sample_rate) < out_frame->nb_samples) break; // This will send a null frame, emptying out the resample buffer input_frame = nullptr; - } // end while there is data in the resampler - + } // end while there is data in the resampler } else { av_init_packet(&opkt); opkt.data = ipkt->data; @@ -1318,15 +1198,15 @@ int VideoStore::write_packet(AVPacket *pkt, AVStream *stream) { pkt->pos = -1; pkt->stream_index = stream->index; - if ( pkt->dts == AV_NOPTS_VALUE ) { + if (pkt->dts == AV_NOPTS_VALUE) { Debug(1, "undef dts, fixing by setting to stream cur_dts %" PRId64, stream->cur_dts); pkt->dts = stream->cur_dts; - } else if ( pkt->dts < stream->cur_dts ) { + } else if (pkt->dts < stream->cur_dts) { Debug(1, "non increasing dts, fixing. our dts %" PRId64 " stream cur_dts %" PRId64, pkt->dts, stream->cur_dts); pkt->dts = stream->cur_dts; } - if ( pkt->dts > pkt->pts ) { + if (pkt->dts > pkt->pts) { Debug(1, "pkt.dts(%" PRId64 ") must be <= pkt.pts(%" PRId64 ")." "Decompression must happen before presentation.", @@ -1340,9 +1220,8 @@ int VideoStore::write_packet(AVPacket *pkt, AVStream *stream) { stream->index, next_dts[stream->index]); int ret = av_interleaved_write_frame(oc, pkt); - if ( ret != 0 ) { - Error("Error writing packet: %s", - av_make_error_string(ret).c_str()); + if (ret != 0) { + Error("Error writing packet: %s", av_make_error_string(ret).c_str()); } else { Debug(4, "Success writing packet"); } diff --git a/src/zm_videostore.h b/src/zm_videostore.h index 47872db2f..682fc147b 100644 --- a/src/zm_videostore.h +++ b/src/zm_videostore.h @@ -6,22 +6,16 @@ #include "zm_ffmpeg.h" #include "zm_swscale.h" +#include + extern "C" { -#ifdef HAVE_LIBSWRESAMPLE - #include "libswresample/swresample.h" -#else - #ifdef HAVE_LIBAVRESAMPLE - #include "libavresample/avresample.h" - #endif -#endif -#include "libavutil/audio_fifo.h" +#include +#include #if HAVE_LIBAVUTIL_HWCONTEXT_H - #include "libavutil/hwcontext.h" +#include #endif } -#if HAVE_LIBAVCODEC - class Monitor; class ZMPacket; class PacketQueue; @@ -35,7 +29,7 @@ class VideoStore { const char *codec_name; const enum AVPixelFormat sw_pix_fmt; const enum AVPixelFormat hw_pix_fmt; -#if HAVE_LIBAVUTIL_HWCONTEXT_H +#if HAVE_LIBAVUTIL_HWCONTEXT_H && LIBAVCODEC_VERSION_CHECK(57, 107, 0, 107, 0) const AVHWDeviceType hwdevice_type; #endif }; @@ -49,7 +43,6 @@ class VideoStore { AVStream *video_out_stream; AVStream *audio_out_stream; - AVCodec *video_out_codec; AVCodecContext *video_in_ctx; AVCodecContext *video_out_ctx; @@ -75,21 +68,15 @@ class VideoStore { AVBufferRef *hw_device_ctx; -#ifdef HAVE_LIBSWRESAMPLE SwrContext *resample_ctx; AVAudioFifo *fifo; -#else -#ifdef HAVE_LIBAVRESAMPLE - AVAudioResampleContext* resample_ctx; -#endif -#endif uint8_t *converted_in_samples; const char *filename; const char *format; // These are for in - int64_t video_first_pts; + int64_t video_first_pts; /* starting pts of first in frame/packet */ int64_t video_first_dts; int64_t audio_first_pts; int64_t audio_first_dts; @@ -119,13 +106,12 @@ class VideoStore { void write_video_packet(AVPacket &pkt); void write_audio_packet(AVPacket &pkt); - int writeVideoFramePacket(ZMPacket *pkt); - int writeAudioFramePacket(ZMPacket *pkt); - int writePacket(ZMPacket *pkt); + int writeVideoFramePacket(const std::shared_ptr &pkt); + int writeAudioFramePacket(const std::shared_ptr &pkt); + int writePacket(const std::shared_ptr &pkt); int write_packets(PacketQueue &queue); void flush_codecs(); }; -#endif //havelibav -#endif //zm_videostore_h +#endif // ZM_VIDEOSTORE_H diff --git a/src/zm_zone.cpp b/src/zm_zone.cpp index 106ab2ce0..bc7086614 100644 --- a/src/zm_zone.cpp +++ b/src/zm_zone.cpp @@ -23,7 +23,7 @@ #include "zm_fifo_debug.h" #include "zm_monitor.h" -void Zone::Setup( +void Zone::Setup( ZoneType p_type, const Polygon &p_polygon, const Rgb p_alarm_rgb, @@ -32,7 +32,7 @@ void Zone::Setup( int p_max_pixel_threshold, int p_min_alarm_pixels, int p_max_alarm_pixels, - const Coord &p_filter_box, + const Vector2 &p_filter_box, int p_min_filter_pixels, int p_max_filter_pixels, int p_min_blob_pixels, @@ -104,25 +104,25 @@ void Zone::Setup( diag_path = stringtf("%s/diag-%d-poly.jpg", monitor->getStorage()->Path(), id); } - pg_image->WriteJpeg(diag_path.c_str(), config.record_diag_images_fifo); + + pg_image->WriteJpeg(diag_path, config.record_diag_images_fifo); } } // end Zone::Setup Zone::~Zone() { - if ( image ) + if (image) delete image; delete pg_image; delete[] ranges; } void Zone::RecordStats(const Event *event) { - static char sql[ZM_SQL_MED_BUFSIZ]; - snprintf(sql, sizeof(sql), + std::string sql = stringtf( "INSERT INTO Stats SET MonitorId=%d, ZoneId=%d, EventId=%" PRIu64 ", FrameId=%d, " "PixelDiff=%d, AlarmPixels=%d, FilterPixels=%d, BlobPixels=%d, " "Blobs=%d, MinBlobSize=%d, MaxBlobSize=%d, " "MinX=%d, MinY=%d, MaxX=%d, MaxY=%d, Score=%d", - monitor->Id(), id, event->Id(), event->Frames(), + monitor->Id(), id, event->Id(), event->Frames(), stats.pixel_diff_, stats.alarm_pixels_, stats.alarm_filter_pixels_, @@ -130,10 +130,10 @@ void Zone::RecordStats(const Event *event) { stats.alarm_blobs_, stats.min_blob_size_, stats.max_blob_size_, - stats.alarm_box_.LoX(), - stats.alarm_box_.LoY(), - stats.alarm_box_.HiX(), - stats.alarm_box_.HiY(), + stats.alarm_box_.Lo().x_, + stats.alarm_box_.Lo().y_, + stats.alarm_box_.Hi().x_, + stats.alarm_box_.Hi().y_, stats.score_ ); zmDbDo(sql); @@ -219,10 +219,10 @@ bool Zone::CheckAlarms(const Image *delta_image) { int alarm_mid_x = -1; int alarm_mid_y = -1; - unsigned int lo_y = polygon.LoY(); - unsigned int lo_x = polygon.LoX(); - unsigned int hi_x = polygon.HiX(); - unsigned int hi_y = polygon.HiY(); + unsigned int lo_x = polygon.Extent().Lo().x_; + unsigned int lo_y = polygon.Extent().Lo().y_; + unsigned int hi_x = polygon.Extent().Hi().x_; + unsigned int hi_y = polygon.Extent().Hi().y_; Debug(4, "Checking alarms for zone %d/%s in lines %d -> %d", id, label.c_str(), lo_y, hi_y); @@ -233,8 +233,9 @@ bool Zone::CheckAlarms(const Image *delta_image) { } */ std_alarmedpixels(diff_image, pg_image, &stats.alarm_pixels_, &pixel_diff_count); - if (config.record_diag_images) - diff_image->WriteJpeg(diag_path.c_str(), config.record_diag_images_fifo); + if (config.record_diag_images) { + diff_image->WriteJpeg(diag_path, config.record_diag_images_fifo); + } if (pixel_diff_count && stats.alarm_pixels_) stats.pixel_diff_ = pixel_diff_count/stats.alarm_pixels_; @@ -267,8 +268,8 @@ bool Zone::CheckAlarms(const Image *delta_image) { Debug(5, "Current score is %d", stats.score_); if (check_method >= FILTERED_PIXELS) { - int bx = filter_box.X(); - int by = filter_box.Y(); + int bx = filter_box.x_; + int by = filter_box.y_; int bx1 = bx-1; int by1 = by-1; @@ -317,8 +318,9 @@ bool Zone::CheckAlarms(const Image *delta_image) { stats.alarm_filter_pixels_ = stats.alarm_pixels_; } - if (config.record_diag_images) - diff_image->WriteJpeg(diag_path.c_str(), config.record_diag_images_fifo); + if (config.record_diag_images) { + diff_image->WriteJpeg(diag_path, config.record_diag_images_fifo); + } Debug(5, "Got %d filtered pixels, need %d -> %d", stats.alarm_filter_pixels_, min_filter_pixels, max_filter_pixels); @@ -541,8 +543,9 @@ bool Zone::CheckAlarms(const Image *delta_image) { } } - if (config.record_diag_images) - diff_image->WriteJpeg(diag_path.c_str(), config.record_diag_images_fifo); + if (config.record_diag_images) { + diff_image->WriteJpeg(diag_path, config.record_diag_images_fifo); + } if (!stats.alarm_blobs_) { stats.score_ = 0; @@ -593,8 +596,9 @@ bool Zone::CheckAlarms(const Image *delta_image) { } // end if bs_count } // end for i < WHITE - if (config.record_diag_images) - diff_image->WriteJpeg(diag_path.c_str(), config.record_diag_images_fifo); + if (config.record_diag_images) { + diff_image->WriteJpeg(diag_path, config.record_diag_images_fifo); + } Debug(5, "Got %d blob pixels, %d blobs, need %d -> %d, %d -> %d", stats.alarm_blob_pixels_, stats.alarm_blobs_, min_blob_pixels, max_blob_pixels, min_blobs, max_blobs); @@ -620,20 +624,20 @@ bool Zone::CheckAlarms(const Image *delta_image) { stats.score_ = 0; return false; } - + if (max_blob_pixels != 0) stats.score_ = (100*stats.alarm_blob_pixels_)/max_blob_pixels; - else + else stats.score_ = (100*stats.alarm_blob_pixels_)/polygon.Area(); - + if (stats.score_ < 1) stats.score_ = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */ Debug(5, "Current score is %d", stats.score_); - alarm_lo_x = polygon.HiX()+1; - alarm_hi_x = polygon.LoX()-1; - alarm_lo_y = polygon.HiY()+1; - alarm_hi_y = polygon.LoY()-1; + alarm_lo_x = polygon.Extent().Hi().x_ + 1; + alarm_hi_x = polygon.Extent().Lo().x_ - 1; + alarm_lo_y = polygon.Extent().Hi().y_ + 1; + alarm_hi_y = polygon.Extent().Lo().y_ - 1; for (uint32 i = 1; i < kWhite; i++) { BlobStats *bs = &blob_stats[i]; @@ -684,11 +688,11 @@ bool Zone::CheckAlarms(const Image *delta_image) { // Now outline the changed region if (stats.score_) { - stats.alarm_box_ = Box(Coord(alarm_lo_x, alarm_lo_y), Coord(alarm_hi_x, alarm_hi_y)); + stats.alarm_box_ = Box(Vector2(alarm_lo_x, alarm_lo_y), Vector2(alarm_hi_x, alarm_hi_y)); //if ( monitor->followMotion() ) if ( true ) { - stats.alarm_centre_ = Coord(alarm_mid_x, alarm_mid_y); + stats.alarm_centre_ = Vector2(alarm_mid_x, alarm_mid_y); } else { stats.alarm_centre_ = stats.alarm_box_.Centre(); } @@ -748,37 +752,39 @@ bool Zone::CheckAlarms(const Image *delta_image) { bool Zone::ParsePolygonString(const char *poly_string, Polygon &polygon) { char *str = (char *)poly_string; - int n_coords = 0; int max_n_coords = strlen(str)/4; - Coord *coords = new Coord[max_n_coords]; + + std::vector vertices; + vertices.reserve(max_n_coords); + while (*str != '\0') { char *cp = strchr(str, ','); if (!cp) { Error("Bogus coordinate %s found in polygon string", str); break; - } + } + int x = atoi(str); - int y = atoi(cp+1); + int y = atoi(cp + 1); Debug(3, "Got coordinate %d,%d from polygon string", x, y); - coords[n_coords++] = Coord(x, y); + vertices.emplace_back(x, y); - char *ws = strchr(cp+2, ' '); - if (ws) - str = ws+1; - else + char *ws = strchr(cp + 2, ' '); + if (ws) { + str = ws + 1; + } else { break; - } // end while ! end of string - - if (n_coords > 2) { - Debug(3, "Successfully parsed polygon string %s", str); - polygon = Polygon(n_coords, coords); - } else { - Error("Not enough coordinates to form a polygon!"); - n_coords = 0; + } } - delete[] coords; - return n_coords ? true : false; + if (vertices.size() > 2) { + Debug(3, "Successfully parsed polygon string %s", str); + polygon = Polygon(vertices); + } else { + Error("Not enough coordinates to form a polygon!"); + } + + return !vertices.empty(); } // end bool Zone::ParsePolygonString(const char *poly_string, Polygon &polygon) bool Zone::ParseZoneString(const char *zone_string, int &zone_id, int &colour, Polygon &polygon) { @@ -828,7 +834,7 @@ std::vector Zone::Load(Monitor *monitor) { "OverloadFrames,ExtendAlarmFrames" " FROM Zones WHERE MonitorId = %d ORDER BY Type, Id", monitor->Id()); - MYSQL_RES *result = zmDbFetch(sql.c_str()); + MYSQL_RES *result = zmDbFetch(sql); if (!result) { return {}; } @@ -872,14 +878,21 @@ std::vector Zone::Load(Monitor *monitor) { continue; } - if ( polygon.LoX() < 0 || polygon.HiX() >= (int)monitor->Width() - || polygon.LoY() < 0 || polygon.HiY() >= (int)monitor->Height() ) { + if (polygon.Extent().Lo().x_ < 0 || polygon.Extent().Hi().x_ > static_cast(monitor->Width()) + || polygon.Extent().Lo().y_ < 0 || polygon.Extent().Hi().y_ > static_cast(monitor->Height())) { Error("Zone %d/%s for monitor %s extends outside of image dimensions, (%d,%d), (%d,%d), fixing", - Id, Name, monitor->Name(), polygon.LoX(), polygon.LoY(), polygon.HiX(), polygon.HiY()); - if ( polygon.LoX() < 0 ) polygon.LoX(0); - if ( polygon.HiX() >= (int)monitor->Width()) polygon.HiX((int)monitor->Width()); - if ( polygon.LoY() < 0 ) polygon.LoY(0); - if ( polygon.HiY() >= (int)monitor->Height() ) polygon.HiY((int)monitor->Height()); + Id, + Name, + monitor->Name(), + polygon.Extent().Lo().x_, + polygon.Extent().Lo().y_, + polygon.Extent().Hi().x_, + polygon.Extent().Hi().y_); + + polygon.Clip(Box( + {0, 0}, + {static_cast(monitor->Width()), static_cast(monitor->Height())} + )); } if ( false && !strcmp( Units, "Percent" ) ) { @@ -899,7 +912,7 @@ std::vector Zone::Load(Monitor *monitor) { zones.emplace_back( monitor, Id, Name, Type, polygon, AlarmRGB, CheckMethod, MinPixelThreshold, MaxPixelThreshold, - MinAlarmPixels, MaxAlarmPixels, Coord(FilterX, FilterY), + MinAlarmPixels, MaxAlarmPixels, Vector2(FilterX, FilterY), MinFilterPixels, MaxFilterPixels, MinBlobPixels, MaxBlobPixels, MinBlobs, MaxBlobs, OverloadFrames, ExtendAlarmFrames); @@ -922,9 +935,9 @@ bool Zone::DumpSettings(char *output, bool /*verbose*/) const { type==INACTIVE?"Inactive":( type==PRIVACY?"Privacy":"Unknown" )))))); - sprintf( output+strlen(output), " Shape : %d points\n", polygon.getNumCoords() ); - for ( int i = 0; i < polygon.getNumCoords(); i++ ) { - sprintf( output+strlen(output), " %i: %d,%d\n", i, polygon.getCoord( i ).X(), polygon.getCoord( i ).Y() ); + sprintf( output+strlen(output), " Shape : %zu points\n", polygon.GetVertices().size() ); + for (size_t i = 0; i < polygon.GetVertices().size(); i++) { + sprintf(output + strlen(output), " %zu: %d,%d\n", i, polygon.GetVertices()[i].x_, polygon.GetVertices()[i].y_); } sprintf( output+strlen(output), " Alarm RGB : %06x\n", alarm_rgb ); sprintf( output+strlen(output), " Check Method: %d - %s\n", check_method, @@ -936,7 +949,7 @@ bool Zone::DumpSettings(char *output, bool /*verbose*/) const { sprintf( output+strlen(output), " Max Pixel Threshold : %d\n", max_pixel_threshold ); sprintf( output+strlen(output), " Min Alarm Pixels : %d\n", min_alarm_pixels ); sprintf( output+strlen(output), " Max Alarm Pixels : %d\n", max_alarm_pixels ); - sprintf( output+strlen(output), " Filter Box : %d,%d\n", filter_box.X(), filter_box.Y() ); + sprintf(output+strlen(output), " Filter Box : %d,%d\n", filter_box.x_, filter_box.y_ ); sprintf( output+strlen(output), " Min Filter Pixels : %d\n", min_filter_pixels ); sprintf( output+strlen(output), " Max Filter Pixels : %d\n", max_filter_pixels ); sprintf( output+strlen(output), " Min Blob Pixels : %d\n", min_blob_pixels ); @@ -960,8 +973,8 @@ void Zone::std_alarmedpixels( if ( max_pixel_threshold ) calc_max_pixel_threshold = max_pixel_threshold; - lo_y = polygon.LoY(); - hi_y = polygon.HiY(); + lo_y = polygon.Extent().Lo().y_; + hi_y = polygon.Extent().Hi().y_; for ( unsigned int y = lo_y; y <= hi_y; y++ ) { unsigned int lo_x = ranges[y].lo_x; unsigned int hi_x = ranges[y].hi_x; diff --git a/src/zm_zone.h b/src/zm_zone.h index 30f7e29eb..741b61ae2 100644 --- a/src/zm_zone.h +++ b/src/zm_zone.h @@ -21,12 +21,12 @@ #define ZM_ZONE_H #include "zm_box.h" -#include "zm_coord.h" #include "zm_define.h" #include "zm_config.h" #include "zm_poly.h" #include "zm_rgb.h" #include "zm_zone_stats.h" +#include "zm_vector2.h" #include #include @@ -77,7 +77,7 @@ class Zone { int min_alarm_pixels; int max_alarm_pixels; - Coord filter_box; + Vector2 filter_box; int min_filter_pixels; int max_filter_pixels; @@ -112,7 +112,7 @@ class Zone { int p_max_pixel_threshold, int p_min_alarm_pixels, int p_max_alarm_pixels, - const Coord &p_filter_box, + const Vector2 &p_filter_box, int p_min_filter_pixels, int p_max_filter_pixels, int p_min_blob_pixels, @@ -137,7 +137,7 @@ class Zone { int p_max_pixel_threshold=0, int p_min_alarm_pixels=50, int p_max_alarm_pixels=75000, - const Coord &p_filter_box=Coord( 3, 3 ), + const Vector2 &p_filter_box = Vector2(3, 3), int p_min_filter_pixels=50, int p_max_filter_pixels=50000, int p_min_blob_pixels=10, @@ -164,7 +164,7 @@ class Zone { blob_stats{}, stats(p_id) { - Setup(Zone::INACTIVE, p_polygon, kRGBBlack, (Zone::CheckMethod)0, 0, 0, 0, 0, Coord(0, 0), 0, 0, 0, 0, 0, 0, 0, 0); + Setup(Zone::INACTIVE, p_polygon, kRGBBlack, (Zone::CheckMethod)0, 0, 0, 0, 0, Vector2(0, 0), 0, 0, 0, 0, 0, 0, 0, 0); } Zone(Monitor *p_monitor, int p_id, const char *p_label, ZoneType p_type, const Polygon &p_polygon) : @@ -174,7 +174,7 @@ class Zone { blob_stats{}, stats(p_id) { - Setup(p_type, p_polygon, kRGBBlack, (Zone::CheckMethod)0, 0, 0, 0, 0, Coord( 0, 0 ), 0, 0, 0, 0, 0, 0, 0, 0 ); + Setup(p_type, p_polygon, kRGBBlack, (Zone::CheckMethod)0, 0, 0, 0, 0, Vector2(0, 0), 0, 0, 0, 0, 0, 0, 0, 0 ); } Zone(const Zone &z); @@ -195,7 +195,7 @@ class Zone { inline bool WasAlarmed() const { return was_alarmed; } inline void SetAlarm() { was_alarmed = alarmed; alarmed = true; } inline void ClearAlarm() { was_alarmed = alarmed; alarmed = false; } - inline Coord GetAlarmCentre() const { return stats.alarm_centre_; } + inline Vector2 GetAlarmCentre() const { return stats.alarm_centre_; } inline unsigned int Score() const { return stats.score_; } inline void ResetStats() { diff --git a/src/zm_zone_stats.h b/src/zm_zone_stats.h index f963a0eb4..6243f53b6 100644 --- a/src/zm_zone_stats.h +++ b/src/zm_zone_stats.h @@ -21,8 +21,8 @@ #define ZM_ZONE_STATS_H #include "zm_box.h" -#include "zm_coord.h" #include "zm_logger.h" +#include "zm_vector2.h" class ZoneStats { public: @@ -35,8 +35,6 @@ class ZoneStats { alarm_blobs_(0), min_blob_size_(0), max_blob_size_(0), - alarm_box_({}), - alarm_centre_({}), score_(0) {}; void Reset() { @@ -47,10 +45,7 @@ class ZoneStats { alarm_blobs_ = 0; min_blob_size_ = 0; max_blob_size_ = 0; - alarm_box_.LoX(0); - alarm_box_.LoY(0); - alarm_box_.HiX(0); - alarm_box_.HiY(0); + alarm_box_ = {}; alarm_centre_ = {}; score_ = 0; } @@ -67,12 +62,12 @@ class ZoneStats { alarm_blobs_, min_blob_size_, max_blob_size_, - alarm_box_.LoX(), - alarm_box_.LoY(), - alarm_box_.HiX(), - alarm_box_.HiY(), - alarm_centre_.X(), - alarm_centre_.Y(), + alarm_box_.Lo().x_, + alarm_box_.Lo().y_, + alarm_box_.Hi().x_, + alarm_box_.Hi().y_, + alarm_centre_.x_, + alarm_centre_.y_, score_ ); } @@ -87,7 +82,7 @@ class ZoneStats { int min_blob_size_; int max_blob_size_; Box alarm_box_; - Coord alarm_centre_; + Vector2 alarm_centre_; unsigned int score_; }; diff --git a/src/zmc.cpp b/src/zmc.cpp index c2ee0a071..d974d1bec 100644 --- a/src/zmc.cpp +++ b/src/zmc.cpp @@ -64,8 +64,6 @@ possible, this should run at more or less constant speed. #include "zm_time.h" #include "zm_utils.h" #include -#include -#include void Usage() { fprintf(stderr, "zmc -d or -r -H -P -p or -f or -m \n"); @@ -192,11 +190,11 @@ int main(int argc, char *argv[]) { HwCapsDetect(); std::vector> monitors; -#if ZM_HAS_V4L +#if ZM_HAS_V4L2 if ( device[0] ) { monitors = Monitor::LoadLocalMonitors(device, Monitor::CAPTURE); } else -#endif // ZM_HAS_V4L +#endif // ZM_HAS_V4L2 if ( host[0] ) { if ( !port ) port = "80"; @@ -214,7 +212,7 @@ int main(int argc, char *argv[]) { Error("No monitors found"); exit(-1); } else { - Debug(2, "%zu monitors loaded", monitors.size()); + Debug(2, "%zu monitors loaded", monitors.size()); } Info("Starting Capture version %s", ZM_VERSION); @@ -234,7 +232,6 @@ int main(int argc, char *argv[]) { while (!zm_terminate) { result = 0; - static char sql[ZM_SQL_SML_BUFSIZ]; for (const std::shared_ptr &monitor : monitors) { monitor->LoadCamera(); @@ -242,53 +239,51 @@ int main(int argc, char *argv[]) { if (!monitor->connect()) { Warning("Couldn't connect to monitor %d", monitor->Id()); } - time_t now = (time_t)time(nullptr); - monitor->setStartupTime(now); - monitor->setHeartbeatTime(now); + SystemTimePoint now = std::chrono::system_clock::now(); + monitor->SetStartupTime(now); + monitor->SetHeartbeatTime(now); - snprintf(sql, sizeof(sql), + std::string sql = stringtf( "INSERT INTO Monitor_Status (MonitorId,Status,CaptureFPS,AnalysisFPS)" " VALUES (%u, 'Running',0,0) ON DUPLICATE KEY UPDATE Status='Running',CaptureFPS=0,AnalysisFPS=0", monitor->Id()); zmDbDo(sql); - int sleep_time = 0; + Seconds sleep_time = Seconds(0); while (monitor->PrimeCapture() <= 0) { if (prime_capture_log_count % 60) { - logPrintf(Logger::ERROR+monitor->Importance(), - "Failed to prime capture of initial monitor"); + logPrintf(Logger::ERROR + monitor->Importance(), + "Failed to prime capture of initial monitor"); } else { Debug(1, "Failed to prime capture of initial monitor"); } - prime_capture_log_count ++; - if (zm_terminate) break; - if (sleep_time < 60) sleep_time++; - sleep(sleep_time); - } - if (zm_terminate) break; - snprintf(sql, sizeof(sql), + prime_capture_log_count++; + if (zm_terminate) { + break; + } + if (sleep_time < Seconds(60)) { + sleep_time++; + } + + std::this_thread::sleep_for(sleep_time); + } + if (zm_terminate){ + break; + } + + sql = stringtf( "INSERT INTO Monitor_Status (MonitorId,Status) VALUES (%u, 'Connected') ON DUPLICATE KEY UPDATE Status='Connected'", - monitor->Id()); + monitor->Id()); zmDbDo(sql); } // end foreach monitor - if (zm_terminate) break; - int *capture_delays = new int[monitors.size()]; - int *alarm_capture_delays = new int[monitors.size()]; - struct timeval * last_capture_times = new struct timeval[monitors.size()]; - - for (size_t i = 0; i < monitors.size(); i++) { - last_capture_times[i].tv_sec = last_capture_times[i].tv_usec = 0; - capture_delays[i] = monitors[i]->GetCaptureDelay(); - alarm_capture_delays[i] = monitors[i]->GetAlarmCaptureDelay(); - Debug(2, "capture delay(%u mSecs 1000/capture_fps) alarm delay(%u)", - capture_delays[i], alarm_capture_delays[i]); + if (zm_terminate){ + break; } - struct timeval now; - struct DeltaTimeval delta_time; - int sleep_time = 0; + std::vector last_capture_times = std::vector(monitors.size()); + Microseconds sleep_time = Microseconds(0); while (!zm_terminate) { //sigprocmask(SIG_BLOCK, &block_set, 0); @@ -315,30 +310,28 @@ int main(int argc, char *argv[]) { } // capture_delay is the amount of time we should sleep in useconds to achieve the desired framerate. - int delay = (monitors[i]->GetState() == Monitor::ALARM) ? alarm_capture_delays[i] : capture_delays[i]; - if (delay) { - gettimeofday(&now, nullptr); - if (last_capture_times[i].tv_sec) { - // DT_PREC_3 means that the value will be in thousands of a second - DELTA_TIMEVAL(delta_time, now, last_capture_times[i], DT_PREC_6); + Microseconds delay = (monitors[i]->GetState() == Monitor::ALARM) ? monitors[i]->GetAlarmCaptureDelay() + : monitors[i]->GetCaptureDelay(); + if (delay != Seconds(0)) { + SystemTimePoint now = std::chrono::system_clock::now(); + if (last_capture_times[i].time_since_epoch() != Seconds(0)) { + Microseconds delta_time = std::chrono::duration_cast(now - last_capture_times[i]); // You have to add back in the previous sleep time - sleep_time = delay - (delta_time.delta - sleep_time); + sleep_time = delay - (delta_time - sleep_time); Debug(4, - "Sleep time is %d from now: %" PRIi64 ".%" PRIi64" last: %" PRIi64 ".% " PRIi64 " delta %lu delay: %d", - sleep_time, - static_cast(now.tv_sec), - static_cast(now.tv_usec), - static_cast(last_capture_times[i].tv_sec), - static_cast(last_capture_times[i].tv_usec), - delta_time.delta, - delay); + "Sleep time is %" PRIi64 " from now: %.2f s last: %.2f s delta % " PRIi64 " us delay: %" PRIi64 " us", + static_cast(Microseconds(sleep_time).count()), + FPSeconds(now.time_since_epoch()).count(), + FPSeconds(last_capture_times[i].time_since_epoch()).count(), + static_cast(delta_time.count()), + static_cast(Microseconds(delay).count())); - if (sleep_time > 0) { - Debug(4, "usleeping (%d)", sleep_time); - usleep(sleep_time); + if (sleep_time > Seconds(0)) { + std::this_thread::sleep_for(sleep_time); } } // end if has a last_capture time + last_capture_times[i] = now; } // end if delay } // end foreach n_monitors @@ -349,32 +342,25 @@ int main(int argc, char *argv[]) { } } // end while ! zm_terminate and connected - for (size_t i = 0; i < monitors.size(); i++) { - monitors[i]->Close(); - monitors[i]->disconnect(); + for (std::shared_ptr & monitor : monitors) { + monitor->Close(); + monitor->disconnect(); } - delete [] alarm_capture_delays; - delete [] capture_delays; - delete [] last_capture_times; - if (zm_reload) { for (std::shared_ptr &monitor : monitors) { monitor->Reload(); } logTerm(); logInit(log_id_string); - + zm_reload = false; } // end if zm_reload } // end while ! zm_terminate outer connection loop - Debug(1,"Updating Monitor status"); - for (std::shared_ptr &monitor : monitors) { - static char sql[ZM_SQL_SML_BUFSIZ]; - snprintf(sql, sizeof(sql), - "INSERT INTO Monitor_Status (MonitorId,Status) VALUES (%u, 'NotRunning') ON DUPLICATE KEY UPDATE Status='NotRunning'", + std::string sql = stringtf( + "INSERT INTO Monitor_Status (MonitorId,Status) VALUES (%u, 'NotRunning') ON DUPLICATE KEY UPDATE Status='NotRunning'", monitor->Id()); zmDbDo(sql); } @@ -385,5 +371,5 @@ int main(int argc, char *argv[]) { dbQueue.stop(); zmDbClose(); - return zm_terminate ? 0 : result; + return zm_terminate ? 0 : result; } diff --git a/src/zms.cpp b/src/zms.cpp index d98f24418..dda3791ac 100644 --- a/src/zms.cpp +++ b/src/zms.cpp @@ -241,7 +241,7 @@ int main(int argc, const char *argv[], char **envp) { } fprintf(stdout, "Server: ZoneMinder Video Server/%s\r\n", ZM_VERSION); - time_t now = time(nullptr); + time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); char date_string[64]; tm now_tm = {}; strftime(date_string, sizeof(date_string)-1, @@ -265,7 +265,7 @@ int main(int argc, const char *argv[], char **envp) { stream.setStreamQueue(connkey); stream.setStreamBuffer(playback_buffer); if ( !stream.setStreamStart(monitor_id) ) { - Error("Unable set start stream for monitor %d", monitor_id); + fputs("Content-Type: multipart/x-mixed-replace; boundary=" BOUNDARY "\r\n\r\n", stdout); stream.sendTextFrame("Unable to connect to monitor"); logTerm(); zmDbClose(); @@ -281,19 +281,9 @@ int main(int argc, const char *argv[], char **envp) { } else if ( mode == ZMS_SINGLE ) { stream.setStreamType(MonitorStream::STREAM_SINGLE); } else { -#if HAVE_LIBAVCODEC stream.setStreamFormat(format); stream.setStreamBitrate(bitrate); stream.setStreamType(MonitorStream::STREAM_MPEG); -#else // HAVE_LIBAVCODEC - Error("MPEG streaming of '%s' attempted while disabled", query); - fprintf(stderr, "MPEG streaming is disabled.\n" - "You should configure with the --with-ffmpeg" - " option and rebuild to use this functionality.\n"); - logTerm(); - zmDbClose(); - return -1; -#endif // HAVE_LIBAVCODEC } stream.runStream(); } else if ( source == ZMS_FIFO ) { @@ -320,19 +310,9 @@ int main(int argc, const char *argv[], char **envp) { if ( mode == ZMS_JPEG ) { stream.setStreamType(EventStream::STREAM_JPEG); } else { -#if HAVE_LIBAVCODEC stream.setStreamFormat(format); stream.setStreamBitrate(bitrate); stream.setStreamType(EventStream::STREAM_MPEG); -#else // HAVE_LIBAVCODEC - Error("MPEG streaming of '%s' attempted while disabled", query); - fprintf(stderr, "MPEG streaming is disabled.\n" - "You should ensure the ffmpeg libraries are installed and detected" - " and rebuild to use this functionality.\n"); - logTerm(); - zmDbClose(); - return -1; -#endif // HAVE_LIBAVCODEC } // end if jpeg or mpeg stream.runStream(); } else { diff --git a/src/zmu.cpp b/src/zmu.cpp index acc4b21dc..ddf66a3dd 100644 --- a/src/zmu.cpp +++ b/src/zmu.cpp @@ -197,6 +197,7 @@ bool ValidateAccess(User *user, int mon_id, int function) { void exit_zmu(int exit_code) { logTerm(); + dbQueue.stop(); zmDbClose(); exit(exit_code); @@ -248,7 +249,7 @@ int main(int argc, char *argv[]) { {nullptr, 0, nullptr, 0} }; - const char *device = nullptr; + std::string device; int mon_id = 0; bool verbose = false; int function = ZMU_BOGUS; @@ -264,13 +265,9 @@ int main(int argc, char *argv[]) { char *password = nullptr; char *auth = nullptr; std::string jwt_token_str = ""; -#if ZM_HAS_V4L #if ZM_HAS_V4L2 int v4lVersion = 2; -#elif ZM_HAS_V4L1 - int v4lVersion = 1; -#endif // ZM_HAS_V4L2/1 -#endif // ZM_HAS_V4L +#endif // ZM_HAS_V4L2 while (1) { int option_index = 0; @@ -382,11 +379,11 @@ int main(int argc, char *argv[]) { case 'T': jwt_token_str = std::string(optarg); break; -#if ZM_HAS_V4L +#if ZM_HAS_V4L2 case 'V': v4lVersion = (atoi(optarg)==1)?1:2; break; -#endif // ZM_HAS_V4L +#endif // ZM_HAS_V4L2 case 'h': case '?': Usage(0); @@ -408,7 +405,7 @@ int main(int argc, char *argv[]) { Usage(); } - if ( device && !(function&ZMU_QUERY) ) { + if ( !device.empty() && !(function&ZMU_QUERY) ) { fprintf(stderr, "Error, -d option cannot be used with this option\n"); Usage(); } @@ -502,20 +499,27 @@ int main(int argc, char *argv[]) { } } if ( function & ZMU_TIME ) { - struct timeval timestamp = monitor->GetTimestamp(image_idx); - if ( verbose ) { + SystemTimePoint timestamp = monitor->GetTimestamp(image_idx); + if (verbose) { char timestamp_str[64] = "None"; - if ( timestamp.tv_sec ) { + if (timestamp.time_since_epoch() != Seconds(0)) { tm tm_info = {}; - strftime(timestamp_str, sizeof(timestamp_str), "%Y-%m-%d %H:%M:%S", localtime_r(×tamp.tv_sec, &tm_info)); + time_t timestamp_t = std::chrono::system_clock::to_time_t(timestamp); + strftime(timestamp_str, sizeof(timestamp_str), "%Y-%m-%d %H:%M:%S", localtime_r(×tamp_t, &tm_info)); + } + Seconds ts_sec = std::chrono::duration_cast(timestamp.time_since_epoch()); + Microseconds ts_usec = std::chrono::duration_cast(timestamp.time_since_epoch() - ts_sec); + if (image_idx == -1) { + printf("Time of last image capture: %s.%02d\n", timestamp_str, static_cast(ts_usec.count())); + } + else { + printf("Time of image %d capture: %s.%02d\n", image_idx, timestamp_str, static_cast(ts_usec.count())); } - if ( image_idx == -1 ) - printf("Time of last image capture: %s.%02ld\n", timestamp_str, timestamp.tv_usec/10000); - else - printf("Time of image %d capture: %s.%02ld\n", image_idx, timestamp_str, timestamp.tv_usec/10000); } else { - if ( have_output ) fputc(separator, stdout); - printf("%ld.%02ld", timestamp.tv_sec, timestamp.tv_usec/10000); + if (have_output) { + fputc(separator, stdout); + } + printf("%.2f", FPSeconds(timestamp.time_since_epoch()).count()); have_output = true; } } @@ -589,13 +593,16 @@ int main(int argc, char *argv[]) { // Ensure that we are not recording. So the forced alarm is distinct from what was recording before monitor->ForceAlarmOff(); monitor->ForceAlarmOn(config.forced_alarm_score, "Forced Web"); - int wait = 10*1000*1000; // 10 seconds - while ((monitor->GetState() != Monitor::ALARM) and !zm_terminate and wait) { + + Microseconds wait_time = Seconds(10); + while ((monitor->GetState() != Monitor::ALARM) and !zm_terminate and wait_time > Seconds(0)) { // Wait for monitor to notice. - usleep(1000); - wait -= 1000; + Microseconds sleep = Microseconds(1); + std::this_thread::sleep_for(sleep); + wait_time -= sleep; } - if ( monitor->GetState() != Monitor::ALARM and !wait ) { + + if (monitor->GetState() != Monitor::ALARM and wait_time == Seconds(0)) { Error("Monitor failed to respond to forced alarm."); } else { printf("Alarmed event id: %" PRIu64 "\n", monitor->GetLastEventId()); @@ -712,15 +719,15 @@ int main(int argc, char *argv[]) { } } else { // non monitor functions if ( function & ZMU_QUERY ) { -#if ZM_HAS_V4L +#if ZM_HAS_V4L2 char vidString[0x10000] = ""; bool ok = LocalCamera::GetCurrentSettings(device, vidString, v4lVersion, verbose); printf("%s", vidString); exit_zmu(ok ? 0 : -1); -#else // ZM_HAS_V4L +#else // ZM_HAS_V4L2 Error("Video4linux is required for device querying"); exit_zmu(-1); -#endif // ZM_HAS_V4L +#endif // ZM_HAS_V4L2 } if ( function & ZMU_LIST ) { @@ -730,7 +737,7 @@ int main(int argc, char *argv[]) { } sql += " ORDER BY Id ASC"; - MYSQL_RES *result = zmDbFetch(sql.c_str()); + MYSQL_RES *result = zmDbFetch(sql); if (!result) { exit_zmu(-1); } @@ -744,13 +751,14 @@ int main(int argc, char *argv[]) { if ( monitor_function > 1 ) { std::shared_ptr monitor = Monitor::Load(monitor_id, false, Monitor::QUERY); if ( monitor && monitor->connect() ) { - struct timeval tv = monitor->GetTimestamp(); - printf( "%4d%5d%6d%9d%11ld.%02ld%6d%6d%8" PRIu64 "%8.2f\n", + SystemTimePoint timestamp = monitor->GetTimestamp(); + + printf( "%4d%5d%6d%9d%14.2f%6d%6d%8" PRIu64 "%8.2f\n", monitor->Id(), monitor_function, monitor->GetState(), monitor->GetTriggerState(), - tv.tv_sec, tv.tv_usec/10000, + FPSeconds(timestamp.time_since_epoch()).count(), monitor->GetLastReadIndex(), monitor->GetLastWriteIndex(), monitor->GetLastEventId(), @@ -758,13 +766,12 @@ int main(int argc, char *argv[]) { ); } } else { - struct timeval tv = { 0, 0 }; printf("%4d%5d%6d%9d%11ld.%02ld%6d%6d%8d%8.2f\n", mon_id, function, 0, 0, - tv.tv_sec, tv.tv_usec/10000, + 0l, 0l, 0, 0, 0, diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e0c68aeff..880a3d3db 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -12,10 +12,13 @@ include(Catch) set(TEST_SOURCES + zm_box.cpp zm_comms.cpp zm_crypt.cpp zm_font.cpp - zm_utils.cpp) + zm_poly.cpp + zm_utils.cpp + zm_vector2.cpp) add_executable(tests main.cpp ${TEST_SOURCES}) diff --git a/tests/zm_box.cpp b/tests/zm_box.cpp new file mode 100644 index 000000000..e85056c85 --- /dev/null +++ b/tests/zm_box.cpp @@ -0,0 +1,52 @@ +/* + * This file is part of the ZoneMinder Project. See AUTHORS file for Copyright information + * + * 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, see . + */ + +#include "zm_catch2.h" + +#include "zm_box.h" + +TEST_CASE("Box: default constructor") { + Box b; + + REQUIRE(b.Lo() == Vector2(0, 0)); + REQUIRE(b.Hi() == Vector2(0, 0)); + REQUIRE(b.Size() == Vector2(0, 0)); + REQUIRE(b.Area() == 0); +} + +TEST_CASE("Box: construct from lo and hi") { + Box b({1, 1}, {5, 5}); + + SECTION("basic properties") { + REQUIRE(b.Lo() == Vector2(1, 1)); + REQUIRE(b.Hi() == Vector2(5, 5)); + + REQUIRE(b.Size() == Vector2(4 ,4)); + REQUIRE(b.Area() == 16); + REQUIRE(b.Centre() == Vector2(3, 3)); + + REQUIRE(b.Vertices() == std::vector{{1, 1}, {5, 1}, {5, 5}, {1, 5}}); + } + + SECTION("contains") { + REQUIRE(b.Contains({0, 0}) == false); + REQUIRE(b.Contains({1, 1}) == true); + REQUIRE(b.Contains({3, 3}) == true); + REQUIRE(b.Contains({5, 5}) == true); + REQUIRE(b.Contains({6, 6}) == false); + } +} diff --git a/tests/zm_catch2.h b/tests/zm_catch2.h new file mode 100644 index 000000000..5f92277ea --- /dev/null +++ b/tests/zm_catch2.h @@ -0,0 +1,30 @@ +/* + * This file is part of the ZoneMinder Project. See AUTHORS file for Copyright information + * + * 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, see . + */ + +#ifndef ZONEMINDER_TESTS_ZM_CATCH2_H_ +#define ZONEMINDER_TESTS_ZM_CATCH2_H_ + +#include "catch2/catch.hpp" + +#include "zm_vector2.h" + +inline std::ostream &operator<<(std::ostream &os, Vector2 const &value) { + os << "{ X: " << value.x_ << ", Y: " << value.y_ << " }"; + return os; +} + +#endif //ZONEMINDER_TESTS_ZM_CATCH2_H_ diff --git a/tests/zm_comms.cpp b/tests/zm_comms.cpp index dfe98e1bf..5a0c41207 100644 --- a/tests/zm_comms.cpp +++ b/tests/zm_comms.cpp @@ -15,13 +15,13 @@ * with this program. If not, see . */ -#include "catch2/catch.hpp" +#include "zm_catch2.h" #include "zm_comms.h" #include TEST_CASE("ZM::Pipe basics") { - ZM::Pipe pipe; + zm::Pipe pipe; SECTION("setBlocking on non-opened") { REQUIRE(pipe.setBlocking(true) == false); @@ -64,7 +64,7 @@ TEST_CASE("ZM::Pipe basics") { } TEST_CASE("ZM::Pipe read/write") { - ZM::Pipe pipe; + zm::Pipe pipe; std::array msg = {'a', 'b', 'c'}; std::array rcv{}; @@ -93,7 +93,7 @@ TEST_CASE("ZM::Pipe read/write") { } TEST_CASE("ZM::SockAddrInet") { - ZM::SockAddrInet addr; + zm::SockAddrInet addr; REQUIRE(addr.getAddrSize() == sizeof(sockaddr_in)); SECTION("resolve") { @@ -101,7 +101,7 @@ TEST_CASE("ZM::SockAddrInet") { REQUIRE(addr.getDomain() == AF_INET); SECTION("newSockAddr from resolved addr") { - ZM::SockAddr *addr2 = ZM::SockAddr::newSockAddr(&addr); + zm::SockAddr *addr2 = zm::SockAddr::newSockAddr(&addr); REQUIRE(addr2->getDomain() == AF_INET); REQUIRE(addr2->getAddrSize() == sizeof(sockaddr_in)); } @@ -109,7 +109,7 @@ TEST_CASE("ZM::SockAddrInet") { } TEST_CASE("ZM::SockAddrUnix") { - ZM::SockAddrUnix addr; + zm::SockAddrUnix addr; REQUIRE(addr.getAddrSize() == sizeof(sockaddr_un)); SECTION("resovle") { @@ -117,7 +117,7 @@ TEST_CASE("ZM::SockAddrUnix") { REQUIRE(addr.getDomain() == AF_UNIX); SECTION("newSockAddr from resolved addr") { - ZM::SockAddr *addr2 = ZM::SockAddr::newSockAddr(&addr); + zm::SockAddr *addr2 = zm::SockAddr::newSockAddr(&addr); REQUIRE(addr2->getDomain() == AF_UNIX); REQUIRE(addr2->getAddrSize() == sizeof(sockaddr_un)); } @@ -125,7 +125,7 @@ TEST_CASE("ZM::SockAddrUnix") { } TEST_CASE("ZM::UdpInetSocket basics") { - ZM::UdpInetSocket socket; + zm::UdpInetSocket socket; REQUIRE(socket.isClosed() == true); REQUIRE(socket.isOpen() == false); REQUIRE(socket.isConnected() == false); @@ -161,8 +161,8 @@ TEST_CASE("ZM::UdpInetSocket basics") { } TEST_CASE("ZM::UdpInetSocket send/recv") { - ZM::UdpInetSocket srv_socket; - ZM::UdpInetSocket client_socket; + zm::UdpInetSocket srv_socket; + zm::UdpInetSocket client_socket; std::array msg = {'a', 'b', 'c'}; std::array rcv{}; @@ -190,7 +190,7 @@ TEST_CASE("ZM::UdpUnixSocket basics") { std::string sock_path = "/tmp/zm.unittest.sock"; unlink(sock_path.c_str()); // make sure the socket file does not exist - ZM::UdpUnixSocket socket; + zm::UdpUnixSocket socket; REQUIRE(socket.isClosed() == true); REQUIRE(socket.isOpen() == false); REQUIRE(socket.isConnected() == false); @@ -221,33 +221,52 @@ TEST_CASE("ZM::UdpUnixSocket send/recv") { std::string sock_path = "/tmp/zm.unittest.sock"; unlink(sock_path.c_str()); // make sure the socket file does not exist - ZM::UdpUnixSocket srv_socket; - ZM::UdpUnixSocket client_socket; + zm::UdpUnixSocket srv_socket; + zm::UdpUnixSocket client_socket; - std::array msg = {'a', 'b', 'c'}; - std::array rcv{}; + SECTION("send/recv byte buffer") { + std::array msg = {'a', 'b', 'c'}; + std::array rcv{}; - SECTION("send/recv on unbound socket") { - REQUIRE(client_socket.send(msg.data(), msg.size()) == -1); - REQUIRE(srv_socket.recv(rcv.data(), rcv.size()) == -1); + SECTION("on unbound socket") { + REQUIRE(client_socket.send(msg.data(), msg.size()) == -1); + REQUIRE(srv_socket.recv(rcv.data(), rcv.size()) == -1); + } + + SECTION("on bound socket") { + REQUIRE(srv_socket.bind(sock_path.c_str()) == true); + REQUIRE(srv_socket.isOpen() == true); + + REQUIRE(client_socket.connect(sock_path.c_str()) == true); + REQUIRE(client_socket.isConnected() == true); + + REQUIRE(client_socket.send(msg.data(), msg.size()) == msg.size()); + REQUIRE(srv_socket.recv(rcv.data(), rcv.size()) == msg.size()); + + REQUIRE(rcv == msg); + } } - SECTION("send/recv") { + SECTION("send/recv string") { + std::string msg = "abc"; + std::string rcv; + rcv.reserve(msg.length()); + REQUIRE(srv_socket.bind(sock_path.c_str()) == true); REQUIRE(srv_socket.isOpen() == true); REQUIRE(client_socket.connect(sock_path.c_str()) == true); REQUIRE(client_socket.isConnected() == true); - REQUIRE(client_socket.send(msg.data(), msg.size()) == msg.size()); - REQUIRE(srv_socket.recv(rcv.data(), rcv.size()) == msg.size()); + REQUIRE(client_socket.send(msg) == static_cast(msg.size())); + REQUIRE(srv_socket.recv(rcv) == static_cast(msg.size())); REQUIRE(rcv == msg); } } TEST_CASE("ZM::TcpInetClient basics") { - ZM::TcpInetClient client; + zm::TcpInetClient client; REQUIRE(client.isClosed() == true); REQUIRE(client.isOpen() == false); REQUIRE(client.isConnected() == false); @@ -261,7 +280,7 @@ TEST_CASE("ZM::TcpInetClient basics") { } TEST_CASE("ZM::TcpInetServer basics", "[notCI]") { - ZM::TcpInetServer server; + zm::TcpInetServer server; REQUIRE(server.isClosed() == true); REQUIRE(server.isOpen() == false); REQUIRE(server.isConnected() == false); @@ -287,8 +306,8 @@ TEST_CASE("ZM::TcpInetServer basics", "[notCI]") { } TEST_CASE("ZM::TcpInetClient/Server send/recv", "[notCI]") { - ZM::TcpInetServer server; - ZM::TcpInetClient client; + zm::TcpInetServer server; + zm::TcpInetClient client; std::array msg = {'a', 'b', 'c'}; std::array rcv{}; diff --git a/tests/zm_crypt.cpp b/tests/zm_crypt.cpp index d3a315ef4..94004527c 100644 --- a/tests/zm_crypt.cpp +++ b/tests/zm_crypt.cpp @@ -15,7 +15,7 @@ * with this program. If not, see . */ -#include "catch2/catch.hpp" +#include "zm_catch2.h" #include "zm_crypt.h" @@ -76,3 +76,62 @@ TEST_CASE("JWT validation") { REQUIRE(result.second == 0); } } + +TEST_CASE("zm::crypto::MD5") { + using namespace zm::crypto; + MD5 md5; + + REQUIRE(md5.GetDigest() == MD5::Digest()); + + SECTION("hash from const char*") { + md5.UpdateData("abcdefghijklmnopqrstuvwxyz"); + md5.Finalize(); + + REQUIRE(md5.GetDigest() == MD5::Digest{0xc3, 0xfc, 0xd3, 0xd7, 0x61, 0x92, 0xe4, 0x00, 0x7d, 0xfb, 0x49, 0x6c, 0xca, + 0x67, 0xe1, 0x3b}); + } + + SECTION("hash from std::string") { + md5.UpdateData(std::string("abcdefghijklmnopqrstuvwxyz")); + md5.Finalize(); + + REQUIRE(md5.GetDigest() == MD5::Digest{0xc3, 0xfc, 0xd3, 0xd7, 0x61, 0x92, 0xe4, 0x00, 0x7d, 0xfb, 0x49, 0x6c, 0xca, + 0x67, 0xe1, 0x3b}); + } +} + +TEST_CASE("zm::crypto::MD5::GetDigestOf") { + using namespace zm::crypto; + std::array data = {'a', 'b', 'c'}; + + SECTION("data and len") { + MD5::Digest digest = MD5::GetDigestOf(reinterpret_cast(data.data()), data.size()); + + REQUIRE(digest == MD5::Digest{0x90, 0x01, 0x50, 0x98, 0x3c, 0xd2, 0x4f, 0xb0, 0xd6, 0x96, 0x3f, 0x7d, 0x28, 0xe1, + 0x7f, 0x72}); + } + + SECTION("container") { + MD5::Digest digest = MD5::GetDigestOf(data); + + REQUIRE(digest == MD5::Digest{0x90, 0x01, 0x50, 0x98, 0x3c, 0xd2, 0x4f, 0xb0, 0xd6, 0x96, 0x3f, 0x7d, 0x28, 0xe1, + 0x7f, 0x72}); + } + + SECTION("multiple containers") { + MD5::Digest digest = MD5::GetDigestOf(data, data); + + REQUIRE(digest == MD5::Digest{0x44, 0x0a, 0xc8, 0x58, 0x92, 0xca, 0x43, 0xad, 0x26, 0xd4, 0x4c, 0x7a, 0xd9, 0xd4, + 0x7d, 0x3e}); + } +} + +TEST_CASE("zm::crypto::SHA1::GetDigestOf") { + using namespace zm::crypto; + std::array data = {'a', 'b', 'c'}; + + SHA1::Digest digest = SHA1::GetDigestOf(data); + + REQUIRE(digest == SHA1::Digest{0xa9, 0x99, 0x3e, 0x36, 0x47, 0x06, 0x81, 0x6a, 0xba, 0x3e, 0x25, 0x71, 0x78, 0x50, + 0xc2, 0x6c, 0x9c, 0xd0, 0xd8, 0x9d}); +} diff --git a/tests/zm_font.cpp b/tests/zm_font.cpp index d862c3291..432f26d7e 100644 --- a/tests/zm_font.cpp +++ b/tests/zm_font.cpp @@ -15,7 +15,7 @@ * with this program. If not, see . */ -#include "catch2/catch.hpp" +#include "zm_catch2.h" #include "zm_font.h" @@ -80,8 +80,11 @@ TEST_CASE("FontVariant: GetCodepoint") { std::vector bitmap(FontVariant::kMaxNumCodePoints * height); // fill bitmap for each codepoint alternating with 1 and std::numeric_limits::max() + // TODO: restore capture initializer when C++14 is supported + int32 n = 0; + bool zero = true; std::generate(bitmap.begin(), bitmap.end(), - [n = 0, zero = true]() mutable { + [n, zero]() mutable { if (n == height) { zero = !zero; n = 0; diff --git a/tests/zm_poly.cpp b/tests/zm_poly.cpp new file mode 100644 index 000000000..c7e37673b --- /dev/null +++ b/tests/zm_poly.cpp @@ -0,0 +1,71 @@ +/* + * This file is part of the ZoneMinder Project. See AUTHORS file for Copyright information + * + * 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, see . + */ + +#include "zm_catch2.h" + +#include "zm_poly.h" + +TEST_CASE("Polygon: default constructor") { + Polygon p; + + REQUIRE(p.Area() == 0); + REQUIRE(p.Centre() == Vector2(0, 0)); +} + +TEST_CASE("Polygon: construct from vertices") { + std::vector vertices{{{0, 0}, {6, 0}, {0, 6}}}; + Polygon p(vertices); + + REQUIRE(p.Area() == 18); + REQUIRE(p.Extent().Size() == Vector2(6, 6)); +} + +TEST_CASE("Polygon: clipping") { + // This a concave polygon in a shape resembling a "W" + std::vector v = { + {3, 1}, + {5, 1}, + {6, 3}, + {7, 1}, + {9, 1}, + {10, 8}, + {8, 8}, + {7, 5}, + {5, 5}, + {4, 8}, + {2, 8} + }; + + Polygon p(v); + + REQUIRE(p.GetVertices().size() == 11); + REQUIRE(p.Extent().Size() == Vector2(8, 7)); + + SECTION("boundary box larger than polygon") { + p.Clip(Box({1, 0}, {11, 9})); + + REQUIRE(p.GetVertices().size() == 11); + REQUIRE(p.Extent().Size() == Vector2(8, 7)); + } + + SECTION("boundary box smaller than polygon") { + p.Clip(Box({2, 4}, {10, 7})); + + REQUIRE(p.GetVertices().size() == 8); + REQUIRE(p.Extent().Size() == Vector2(8, 3)); + } +} diff --git a/tests/zm_utils.cpp b/tests/zm_utils.cpp index 408b9f43e..43b6a4a71 100644 --- a/tests/zm_utils.cpp +++ b/tests/zm_utils.cpp @@ -15,7 +15,7 @@ * with this program. If not, see . */ -#include "catch2/catch.hpp" +#include "zm_catch2.h" #include "zm_utils.h" #include @@ -140,6 +140,18 @@ TEST_CASE("Join") { REQUIRE(Join({"a", "b"}, "") == "ab"); } +TEST_CASE("ByteArrayToHexString") { + std::vector bytes; + + REQUIRE(ByteArrayToHexString(bytes) == ""); + + bytes = {0x00}; + REQUIRE(ByteArrayToHexString(bytes) == "00"); + + bytes = {0x00, 0x01, 0x02, 0xff}; + REQUIRE(ByteArrayToHexString(bytes) == "000102ff"); +} + TEST_CASE("Base64Encode") { REQUIRE(Base64Encode("") == ""); REQUIRE(Base64Encode("f") == "Zg=="); @@ -151,9 +163,9 @@ TEST_CASE("Base64Encode") { } TEST_CASE("ZM::clamp") { - REQUIRE(ZM::clamp(1, 0, 2) == 1); - REQUIRE(ZM::clamp(3, 0, 2) == 2); - REQUIRE(ZM::clamp(-1, 0, 2) == 0); + REQUIRE(zm::clamp(1, 0, 2) == 1); + REQUIRE(zm::clamp(3, 0, 2) == 2); + REQUIRE(zm::clamp(-1, 0, 2) == 0); } TEST_CASE("UriDecode") { diff --git a/tests/zm_vector2.cpp b/tests/zm_vector2.cpp new file mode 100644 index 000000000..625420486 --- /dev/null +++ b/tests/zm_vector2.cpp @@ -0,0 +1,94 @@ +/* + * This file is part of the ZoneMinder Project. See AUTHORS file for Copyright information + * + * 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, see . + */ + +#include "zm_catch2.h" + +#include "zm_vector2.h" + +TEST_CASE("Vector2: default constructor") { + Vector2 c; + REQUIRE(c.x_ == 0); + REQUIRE(c.y_ == 0); +} + +TEST_CASE("Vector2: x/y constructor") { + Vector2 c(1, 2); + + REQUIRE(c.x_ == 1); + REQUIRE(c.y_ == 2); +} + +TEST_CASE("Vector2: assignment/copy") { + Vector2 c; + Vector2 c2(1, 2); + + REQUIRE(c.x_ == 0); + REQUIRE(c.y_ == 0); + + SECTION("assignment operator") { + c = c2; + REQUIRE(c.x_ == 1); + REQUIRE(c.y_ == 2); + } + + SECTION("copy constructor") { + Vector2 c3(c2); // NOLINT(performance-unnecessary-copy-initialization) + REQUIRE(c3.x_ == 1); + REQUIRE(c3.y_ == 2); + } +} + +TEST_CASE("Vector2: comparison operators") { + Vector2 c1(1, 2); + Vector2 c2(1, 2); + Vector2 c3(1, 3); + + REQUIRE((c1 == c2) == true); + REQUIRE((c1 != c3) == true); +} + +TEST_CASE("Vector2: arithmetic operators") { + Vector2 c(1, 1); + + SECTION("addition") { + Vector2 c1 = c + Vector2(1, 1); + REQUIRE(c1 == Vector2(2, 2)); + + c += {1, 2}; + REQUIRE(c == Vector2(2, 3)); + } + + SECTION("subtraction") { + Vector2 c1 = c - Vector2(1, 1); + REQUIRE(c1 == Vector2(0, 0)); + + c -= {1, 2}; + REQUIRE(c == Vector2(0, -1)); + } + + SECTION("scalar multiplication") { + c = c * 2; + REQUIRE(c == Vector2(2, 2)); + } +} + +TEST_CASE("Vector2: determinate") { + Vector2 v(1, 1); + REQUIRE(v.Determinant({0, 0}) == 0); + REQUIRE(v.Determinant({1, 1}) == 0); + REQUIRE(v.Determinant({1, 2}) == 1); +} diff --git a/utils/do_debian_package.sh b/utils/do_debian_package.sh index 289158c77..691fb530d 100755 --- a/utils/do_debian_package.sh +++ b/utils/do_debian_package.sh @@ -58,6 +58,14 @@ case $i in PACKAGE_VERSION="${i#*=}" shift ;; + -x=*|--debbuild-extra=*) + DEBBUILD_EXTRA="${i#*=}" + shift + ;; + --dput=*) + DPUT="${i#*=}" + shift + ;; --default) DEFAULT=YES shift # past argument with no value @@ -80,7 +88,7 @@ fi; if [ "$DISTROS" == "" ]; then if [ "$RELEASE" != "" ]; then - DISTROS="xenial,bionic,focal,groovy,hirsute" + DISTROS="bionic,focal,hirsute,impish" else DISTROS=`lsb_release -a 2>/dev/null | grep Codename | awk '{print $2}'`; fi; @@ -112,6 +120,11 @@ else if [ "$BRANCH" == "" ]; then #REV=$(git rev-list --tags --max-count=1) BRANCH=`git describe --tags $(git rev-list --tags --max-count=1)`; + if [ -z "$BRANCH" ]; then + # This should only happen in CI environments where tag info isn't available + BRANCH=`cat version` + echo "Building branch $BRANCH" + fi if [ "$BRANCH" == "" ]; then echo "Unable to determine latest stable branch!" exit 0; @@ -216,6 +229,7 @@ rm -rf .git rm .gitignore cd ../ + if [ ! -e "$DIRECTORY.orig.tar.gz" ]; then tar zcf $DIRECTORY.orig.tar.gz $DIRECTORY.orig fi; @@ -229,13 +243,10 @@ IFS=',' ;for DISTRO in `echo "$DISTROS"`; do fi; # Generate Changlog - if [ "$DISTRO" == "focal" ] || [ "$DISTRO" == "buster" ]; then - cp -Rpd distros/ubuntu2004 debian - elif [ "$DISTRO" == "beowulf" ] - then + if [ "$DISTRO" == "beowulf" ]; then cp -Rpd distros/beowulf debian else - cp -Rpd distros/ubuntu1604 debian + cp -Rpd distros/ubuntu2004 debian fi; if [ "$DEBEMAIL" != "" ] && [ "$DEBFULLNAME" != "" ]; then @@ -285,30 +296,37 @@ zoneminder ($VERSION-$DISTRO${PACKAGE_VERSION}) $DISTRO; urgency=$URGENCY EOF fi; + # Leave the .orig so that we don't pollute it when building deps + cd .. if [ $TYPE == "binary" ]; then - # Auto-install all ZoneMinder's depedencies using the Debian control file - sudo apt-get install devscripts equivs - sudo mk-build-deps -ir ./debian/control - echo "Status: $?" - DEBUILD=debuild + # Auto-install all ZoneMinder's depedencies using the Debian control file + sudo apt-get install devscripts equivs + sudo mk-build-deps -ir $DIRECTORY.orig/debian/control + echo "Status: $?" + DEBUILD=debuild else - if [ $TYPE == "local" ]; then - # Auto-install all ZoneMinder's depedencies using the Debian control file - sudo apt-get install devscripts equivs - sudo mk-build-deps -ir ./debian/control - echo "Status: $?" - DEBUILD="debuild -i -us -uc -b" - else - # Source build, don't need build depends. - DEBUILD="debuild -S -sa -sd" - fi; + if [ $TYPE == "local" ]; then + # Auto-install all ZoneMinder's depedencies using the Debian control file + sudo apt-get install devscripts equivs + sudo mk-build-deps -ir $DIRECTORY.orig/debian/control + echo "Status: $?" + DEBUILD="debuild -i -us -uc -b" + else + # Source build, don't need build depends. + DEBUILD="debuild -S -sa" + fi; fi; + + cd $DIRECTORY.orig + if [ "$DEBSIGN_KEYID" != "" ]; then DEBUILD="$DEBUILD -k$DEBSIGN_KEYID" fi + # Add any extra options specified on the CLI + DEBUILD="$DEBUILD $DEBBUILD_EXTRA" eval $DEBUILD if [ $? -ne 0 ]; then - echo "Error status code is: $?" + echo "Error status code is: $?" echo "Build failed."; exit $?; fi; @@ -344,8 +362,10 @@ EOF dput $PPA $SC fi; else - echo "dputting to $PPA"; - dput $PPA $SC + if [ "$DPUT" != "no" ]; then + echo "dputting to $PPA"; + dput $PPA $SC + fi; fi; fi; done; # foreach distro diff --git a/utils/packpack/startpackpack.sh b/utils/packpack/startpackpack.sh index f2d629dbd..e8a8a182d 100755 --- a/utils/packpack/startpackpack.sh +++ b/utils/packpack/startpackpack.sh @@ -160,7 +160,7 @@ movecrud () { } # previsouly part of installzm.sh -# install the xenial deb and test zoneminder +# install the deb and test zoneminder install_deb () { # Check we've got gdebi installed @@ -173,7 +173,7 @@ install_deb () { exit 1 fi - # Install and test the zoneminder package (only) for Ubuntu Xenial + # Install and test the zoneminder package pkgname="build/zoneminder_${VERSION}-${RELEASE}_amd64.deb" if [ -e $pkgname ]; then @@ -369,12 +369,10 @@ elif [ "${OS}" == "debian" ] || [ "${OS}" == "ubuntu" ] || [ "${OS}" == "raspbia setdebpkgname movecrud - if [ "${DIST}" == "focal" ] || [ "${DIST}" == "groovy" ] || [ "${DIST}" == "hirsuit" ] || [ "${DIST}" == "buster" ]; then + if [ "${DIST}" == "bionic" ] || [ "${DIST}" == "focal" ] || [ "${DIST}" == "hirsute" ] || [ "${DIST}" == "impish" ] || [ "${DIST}" == "buster" ] || [ "${DIST}" == "bullseye" ]; then ln -sfT distros/ubuntu2004 debian elif [ "${DIST}" == "beowulf" ]; then ln -sfT distros/beowulf debian - else - ln -sfT distros/ubuntu1604 debian fi setdebchangelog diff --git a/version b/version index b538e3240..9cf86ad0f 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.35.28 +1.37.1 diff --git a/web/ajax/event.php b/web/ajax/event.php index 1456bd192..906fe255a 100644 --- a/web/ajax/event.php +++ b/web/ajax/event.php @@ -5,7 +5,7 @@ if ( empty($_REQUEST['id']) && empty($_REQUEST['eids']) ) { ajaxError('No event id(s) supplied'); } -if ( canView('Events') ) { +if ( canView('Events') or canView('Snapshots') ) { switch ( $_REQUEST['action'] ) { case 'video' : if ( empty($_REQUEST['videoFormat']) ) { @@ -74,10 +74,15 @@ if ( canView('Events') ) { else $exportCompress = false; + if ( !empty($_REQUEST['exportStructure']) ) + $exportStructure = $_SESSION['export']['structure'] = $_REQUEST['exportStructure']; + else + $exportStructure = false; + session_write_close(); $exportIds = !empty($_REQUEST['eids']) ? $_REQUEST['eids'] : $_REQUEST['id']; - if ( $exportFile = exportEvents( + if ($exportFile = exportEvents( $exportIds, (isset($_REQUEST['connkey'])?$_REQUEST['connkey']:''), $exportDetail, @@ -86,11 +91,14 @@ if ( canView('Events') ) { $exportVideo, $exportMisc, $exportFormat, - $exportCompress - ) ) - ajaxResponse(array('exportFile'=>$exportFile)); - else + $exportCompress, + $exportStructure, + (!empty($_REQUEST['exportFile'])?$_REQUEST['exportFile']:'zmExport'), + )) { + ajaxResponse(array('exportFile'=>$exportFile)); + } else { ajaxError('Export Failed'); + } break; case 'download' : require_once(ZM_SKIN_PATH.'/includes/export_functions.php'); @@ -104,7 +112,7 @@ if ( canView('Events') ) { false,#detail false,#frames false,#images - $exportVideo, + true, #$exportVideo, false,#Misc $exportFormat, false#,#Compress diff --git a/web/ajax/events.php b/web/ajax/events.php index 8a8e3a4f1..562cce9c2 100644 --- a/web/ajax/events.php +++ b/web/ajax/events.php @@ -38,10 +38,20 @@ $search = isset($_REQUEST['search']) ? $_REQUEST['search'] : ''; // Bootstrap table sends json_ecoded array, which we must decode $advsearch = isset($_REQUEST['advsearch']) ? json_decode($_REQUEST['advsearch'], JSON_OBJECT_AS_ARRAY) : array(); +// Order specifies the sort direction, either asc or desc +$order = (isset($_REQUEST['order']) and (strtolower($_REQUEST['order']) == 'asc')) ? 'ASC' : 'DESC'; + // Sort specifies the name of the column to sort on $sort = 'StartDateTime'; -if ( isset($_REQUEST['sort']) ) { +if (isset($_REQUEST['sort'])) { $sort = $_REQUEST['sort']; + if ($sort == 'EndDateTime') { + if ($order == 'ASC') { + $sort = 'EndDateTime IS NULL, EndDateTime'; + } else { + $sort = 'EndDateTime IS NOT NULL, EndDateTime'; + } + } } // Offset specifies the starting row to return, used for pagination @@ -54,8 +64,6 @@ if ( isset($_REQUEST['offset']) ) { } } -// Order specifies the sort direction, either asc or desc -$order = (isset($_REQUEST['order']) and (strtolower($_REQUEST['order']) == 'asc')) ? 'ASC' : 'DESC'; // Limit specifies the number of rows to return // Set the default to 0 for events view, to prevent an issue with ALL pagination diff --git a/web/ajax/modals/eventrename.php b/web/ajax/modals/eventrename.php index c877d57b4..f556a09b0 100644 --- a/web/ajax/modals/eventrename.php +++ b/web/ajax/modals/eventrename.php @@ -1,10 +1,9 @@