diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Floureon.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Floureon.pm index 521409e11..4f05cd3da 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/Floureon.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Floureon.pm @@ -283,7 +283,7 @@ sub presetSet my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); - my $cmd = "cgi/ptz_set?Channel=1&Group=PTZCtrlInfo&PresetNumber=1&Preset=0"; + my $cmd = 'form/presetSet?flag=3&existFlag=1&language=cn&presetNum='.$preset; $self->sendCmd( $cmd ); } @@ -294,7 +294,7 @@ sub presetGoto my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); - my $cmd = "cgi/ptz_set?Channel=1&Group=PTZCtrlInfo&PresetNumber=1&Preset=1"; + my $cmd = 'form/presetSet?flag=4&existFlag=1&language=cn&presetNum='.$preset; $self->sendCmd( $cmd ); } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5f37ff0ab..80e09e9c3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -107,6 +107,7 @@ endif() add_executable(zmc zmc.cpp) add_executable(zms zms.cpp) add_executable(zmu zmu.cpp) +add_executable(zmbenchmark zmbenchmark.cpp) target_link_libraries(zmc PRIVATE @@ -129,6 +130,13 @@ target_link_libraries(zmu ${ZM_EXTRA_LIBS} ${CMAKE_DL_LIBS}) +target_link_libraries(zmbenchmark + PRIVATE + zm-core-interface + zm + ${ZM_EXTRA_LIBS} + ${CMAKE_DL_LIBS}) + # Generate man files for the binaries destined for the bin folder if(BUILD_MAN) foreach(CBINARY zmc zmu) diff --git a/src/zm_time.h b/src/zm_time.h index f7574bbf7..d3d5b95c5 100644 --- a/src/zm_time.h +++ b/src/zm_time.h @@ -83,4 +83,41 @@ Duration duration_cast(timeval const &tv) { } } +// +// This can be used for benchmarking. It will measure the time in between +// its constructor and destructor (or when you call Finish()) and add that +// duration to a microseconds clock. +// +class TimeSegmentAdder { + public: + TimeSegmentAdder(Microseconds &in_target) : + target_(in_target), + start_time_(std::chrono::steady_clock::now()), + finished_(false) { + } + + ~TimeSegmentAdder() { + Finish(); + } + + // Call this to stop the timer and add the timed duration to `target`. + void Finish() { + if (!finished_) { + const TimePoint end_time = std::chrono::steady_clock::now(); + target_ += (std::chrono::duration_cast(end_time - start_time_)); + } + finished_ = true; + } + + private: + // This is where we will add our duration to. + Microseconds &target_; + + // The time we started. + const TimePoint start_time_; + + // True when it has finished timing. + bool finished_; +}; + #endif // ZM_TIME_H diff --git a/src/zmbenchmark.cpp b/src/zmbenchmark.cpp new file mode 100644 index 000000000..244094a54 --- /dev/null +++ b/src/zmbenchmark.cpp @@ -0,0 +1,326 @@ +// +// ZoneMinder Benchmark, $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 +#include +#include +#include +#include +#include + +#include "zm_config.h" +#include "zm_image.h" +#include "zm_monitor.h" +#include "zm_time.h" +#include "zm_utils.h" +#include "zm_zone.h" + +static std::mt19937 mt_rand(111); + +// +// This allows you to feed in a set of columns and timing rows, and print it +// out in a nice-looking table. +// +class TimingsTable { + public: + explicit TimingsTable(std::vector in_columns) : columns_(std::move(in_columns)) {} + + // + // Add a row to the end of the table. + // + // Args: + // label: The name of the row (printed in the first column). + // timings: The values for all the other columns in this row. + void AddRow(const std::string &label, const std::vector &timings) { + assert(timings.size() == columns_.size()); + Row row; + row.label = label; + row.timings = timings; + rows_.push_back(row); + } + + // + // Print out the table. + // + // Args: + // columnPad: # characters between table columns + // + void Print(const int column_pad = 5) { + // Figure out column widths. + std::vector widths(columns_.size() + 1); + + // The first width is the max of the row labels. + auto result = std::max_element(rows_.begin(), + rows_.end(), + [](const Row &a, const Row &b) -> bool { + return a.label.length() < b.label.length(); + }); + widths[0] = result->label.length() + column_pad; + + // Calculate the rest of the column widths. + for (size_t i = 0 ; i < columns_.size() ; i++) + widths[i + 1] = columns_[i].length() + column_pad; + + auto PrintColStr = [&](size_t icol, const std::string &str) { + printf("%s", str.c_str()); + PrintPadding(widths[icol] - str.length()); + }; + + // Print the header. + PrintColStr(0, ""); + for (size_t i = 0 ; i < columns_.size() ; i++) { + PrintColStr(i + 1, columns_[i]); + } + printf("\n"); + + // Print the timings rows. + for (const Row &row : rows_) { + PrintColStr(0, row.label); + + for (size_t i = 0 ; i < row.timings.size() ; i++) { + std::string num = stringtf("%.2f", std::chrono::duration_cast(row.timings[i]).count()); + PrintColStr(i + 1, num); + } + + printf("\n"); + } + } + + private: + static void PrintPadding(size_t count) { + std::string str(count, ' '); + printf("%s", str.c_str()); + } + + struct Row { + std::string label; + std::vector timings; + }; + + std::vector columns_; + std::vector rows_; +}; + +// +// Generate a greyscale image that simulates a delta that can be fed to +// Zone::CheckAlarms. This first creates a black image, and then it fills +// a box of a certain size inside the image with random data. This is to simulate +// a typical scene where most of the scene doesn't change except a specific region. +// +// Args: +// changeBoxPercent: 0-100 value telling how large the box with random data should be. +// Set to 0 to leave the whole thing black. +// width: The width of the new image. +// height: The height of the new image. +// +// Return: +// An image with all pixels initialized to values in the [minVal,maxVal] range. +// +std::shared_ptr GenerateRandomImage( + const int change_box_percent, + const int width = 3840, + const int height = 2160) { + // Create the image. + Image *image = new Image(width, height, ZM_COLOUR_GRAY8, ZM_SUBPIX_ORDER_NONE); + + // Set it to black initially. + memset((void *) image->Buffer(0, 0), 0, (size_t) image->LineSize() * (size_t) image->Height()); + + // Now randomize the pixels inside a box. + const int box_width = (width * change_box_percent) / 100; + const int box_height = (height * change_box_percent) / 100; + const int box_x = (int) ((uint64_t) mt_rand() * (width - box_width) / RAND_MAX); + const int box_y = (int) ((uint64_t) mt_rand() * (height - box_height) / RAND_MAX); + + for (int y = 0 ; y < box_height ; y++) { + uint8_t *row = (uint8_t *) image->Buffer(box_x, box_y + y); + for (int x = 0 ; x < box_width ; x++) { + row[x] = (uint8_t) mt_rand(); + } + } + + return std::shared_ptr(image); +} + +// +// This is used to help rig up Monitor benchmarks. +// +class TestMonitor : public Monitor { + public: + TestMonitor(int width, int height) : cur_zone_id(111) { + this->width = width; + this->height = height; + + // Create a dummy ref_image. + std::shared_ptr tempImage = GenerateRandomImage(0, width, height); + ref_image = *tempImage; + + shared_data = &temp_shared_data; + } + + // + // Add a new zone to this monitor. + // + // Args: + // checkMethod: This controls how this zone will actually do motion detection. + // + // p_filter_box: The size of the filter to use. + // + void AddZone(Zone::CheckMethod checkMethod, const Vector2 &p_filter_box = Vector2(5, 5)) { + const int p_min_pixel_threshold = 50; + const int p_max_pixel_threshold = 255; + const int p_min_alarm_pixels = 1000; + const int p_max_alarm_pixels = 10000000; + + const int zone_id = cur_zone_id++; + const std::string zone_label = std::string("zone_") + std::to_string(zone_id); + const Zone::ZoneType zone_type = Zone::ZoneType::ACTIVE; + const Polygon poly({Vector2(0, 0), + Vector2(width - 1, 0), + Vector2(width - 1, height - 1), + Vector2(0, height - 1)}); + + Zone zone(this, + zone_id, + zone_label.c_str(), + zone_type, + poly, + kRGBGreen, + Zone::CheckMethod::FILTERED_PIXELS, + p_min_pixel_threshold, + p_max_pixel_threshold, + p_min_alarm_pixels, + p_max_alarm_pixels, + p_filter_box); + zones.push_back(zone); + } + + void SetRefImage(const Image *image) { + ref_image = *image; + } + + private: + SharedData temp_shared_data; + int cur_zone_id; +}; + +// +// Run zone benchmarks on the given image. +// +// Args: +// label: A label to be printed before the output. +// +// image: The image to run the tests on. +// +// p_filter_box: The size of the filter to use for alarm detection. +// +// Return: +// The average time taken for each DetectMotion call. +// +Microseconds RunDetectMotionBenchmark(const std::string &label, + const std::shared_ptr& image, + const Vector2 &p_filter_box) { + // Create a monitor to use for the benchmark. Give it 1 zone that uses + // a 5x5 filter. + TestMonitor testMonitor(image->Width(), image->Height()); + testMonitor.AddZone(Zone::CheckMethod::FILTERED_PIXELS, p_filter_box); + + // Generate a black image to use as the reference image. + std::shared_ptr blackImage = GenerateRandomImage( + 0, image->Width(), image->Height()); + testMonitor.SetRefImage(blackImage.get()); + + Microseconds totalTimeTaken(0); + + // Run a series of passes over DetectMotion. + const int numPasses = 10; + for (int i = 0 ; i < numPasses ; i++) { + printf("\r%s - pass %2d / %2d ", label.c_str(), i + 1, numPasses); + fflush(stdout); + + TimeSegmentAdder adder(totalTimeTaken); + + Event::StringSet zoneSet; + testMonitor.DetectMotion(*image, zoneSet); + } + printf("\n"); + + return totalTimeTaken / numPasses; +} + +// +// This runs a set of Monitor::DetectMotion benchmarks, one for each of the +// "delta box percents" that are passed in. This adds one row to the +// TimingsTable specified. +// +// Args: +// table: The table to add timings into. +// +// deltaBoxPercents: Each of these defines a box size in the delta images +// passed to DetectMotion (larger boxes make it slower, sometimes significantly so). +// +// p_filter_box: Defines the filter size used in DetectMotion. +// +void RunDetectMotionBenchmarks( + TimingsTable &table, + const std::vector &delta_box_percents, + const Vector2 &p_filter_box) { + std::vector timings; + + for (int percent : delta_box_percents) { + Microseconds timing = RunDetectMotionBenchmark( + std::string("DetectMotion: ") + std::to_string(p_filter_box.x_) + "x" + std::to_string(p_filter_box.y_) + + " box, " + std::to_string(percent) + "% delta", + GenerateRandomImage(percent), + p_filter_box); + timings.push_back(timing); + } + + table.AddRow( + std::to_string(p_filter_box.x_) + "x" + std::to_string(p_filter_box.y_) + " filter", + timings); +} + +int main(int argc, char *argv[]) { + // Init global stuff that we need. + config.font_file_location = "../fonts/default.zmfnt"; + config.event_close_mode = "time"; + config.cpu_extensions = true; + + // Detect SSE version. + HwCapsDetect(); + + // Setup the column titles for the TimingsTable we'll generate. + // Each column represents how large the box in the image is with delta pixels. + // Each row represents a different filter size. + const std::vector percents = {0, 10, 50, 100}; + std::vector columns(percents.size()); + std::transform(percents.begin(), percents.end(), columns.begin(), + [](const int percent) { return std::to_string(percent) + "% delta (ms)"; }); + TimingsTable table(columns); + + std::vector filterSizes = {Vector2(3, 3), Vector2(5, 5), Vector2(13, 13)}; + for (const auto filterSize : filterSizes) { + RunDetectMotionBenchmarks(table, percents, filterSize); + } + + table.Print(); + return 0; +} + diff --git a/utils/do_debian_package.sh b/utils/do_debian_package.sh index 691fb530d..2a4dd6989 100755 --- a/utils/do_debian_package.sh +++ b/utils/do_debian_package.sh @@ -230,9 +230,13 @@ rm .gitignore cd ../ -if [ ! -e "$DIRECTORY.orig.tar.gz" ]; then +if [ !-e "$DIRECTORY.orig.tar.gz" ]; then +read -p "$DIRECTORY.orig.tar.gz does not exist, create it? [Y/n]" + if [[ $REPLY == [yY] ]]; then + tar zcf $DIRECTORY.orig.tar.gz $DIRECTORY.orig fi; +fi; IFS=',' ;for DISTRO in `echo "$DISTROS"`; do echo "Generating package for $DISTRO"; @@ -358,7 +362,7 @@ EOF dput="Y"; if [ "$INTERACTIVE" != "no" ]; then read -p "Ready to dput $SC to $PPA ? Y/n..."; - if [[ "$REPLY" == [yY] ]]; then + if [[ "$REPLY" == "" || "$REPLY" == [yY] ]]; then dput $PPA $SC fi; else diff --git a/web/skins/classic/views/js/filter.js b/web/skins/classic/views/js/filter.js index a135d9f1b..b59c68417 100644 --- a/web/skins/classic/views/js/filter.js +++ b/web/skins/classic/views/js/filter.js @@ -57,8 +57,8 @@ function validateForm(form) { form.elements['filter[AutoUnarchive]'].checked || form.elements['filter[UpdateDiskSpace]'].checked || form.elements['filter[AutoVideo]'].checked || - form.elements['filter[AutoEmail]'].checked || - form.elements['filter[AutoMessage]'].checked || + (form.elements['filter[AutoEmail]'].checked && form.elements['filter[AutoEmail]'].checked) || + (form.elements['filter[AutoMessage]'] && form.elements['filter[AutoMessage]'].checked) || form.elements['filter[AutoExecute]'].checked || form.elements['filter[AutoDelete]'].checked || form.elements['filter[AutoCopy]'].checked ||