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

This commit is contained in:
Isaac Connor 2021-10-12 14:12:20 -04:00
commit 2056172ea9
6 changed files with 399 additions and 64 deletions

View File

@ -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)

View File

@ -91,46 +91,8 @@ bool PacketQueue::queuePacket(std::shared_ptr<ZMPacket> add_packet) {
Warning("You have set the max video packets in the queue to %u."
" The queue is full. Either Analysis is not keeping up or"
" your camera's keyframe interval is larger than this setting."
" We are dropping packets.", max_video_packet_count);
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)) {
std::shared_ptr <ZMPacket>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");
delete lp;
break;
}
delete lp;
, max_video_packet_count);
for (
std::list<packetqueue_iterator *>::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(1, "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;
Debug(1,
"Deleting a packet with stream index:%d image_index:%d with keyframe:%d, video frames in queue:%d max: %d, queuesize:%zu",
zm_packet->packet.stream_index,
zm_packet->image_index,
zm_packet->keyframe,
packet_counts[video_stream_id],
max_video_packet_count,
pktQueue.size());
} // end while
}
} // end if too many video packets
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.notify_all();
@ -148,16 +110,13 @@ bool PacketQueue::queuePacket(std::shared_ptr<ZMPacket> add_packet) {
packet_counts[add_packet->packet.stream_index]);
for (
std::list<packetqueue_iterator *>::iterator iterators_it = iterators.begin();
auto iterators_it = iterators.begin();
iterators_it != iterators.end();
++iterators_it
) {
packetqueue_iterator *iterator_it = *iterators_it;
if (*iterator_it == pktQueue.end()) {
Debug(4, "pointing it %p to back", iterator_it);
--(*iterator_it);
} else {
Debug(4, "it %p not at end", iterator_it);
}
} // end foreach iterator
} // end lock scope
@ -166,7 +125,7 @@ bool PacketQueue::queuePacket(std::shared_ptr<ZMPacket> add_packet) {
condition.notify_all();
return true;
} // end bool PacketQueue::queuePacket(ZMPacket* zm_packet)
} // end bool PacketQueue::queuePacket(ZMPacket* zm_packet)
void PacketQueue::clearPackets(const std::shared_ptr<ZMPacket> &add_packet) {
// Only do queueCleaning if we are adding a video keyframe, so that we guarantee that there is one.
@ -195,7 +154,6 @@ void PacketQueue::clearPackets(const std::shared_ptr<ZMPacket> &add_packet) {
add_packet->packet.stream_index, video_stream_id, add_packet->keyframe, keep_keyframes, packet_counts[video_stream_id], pre_event_video_packet_count,
( *(pktQueue.begin()) != add_packet )
);
Warning("Keyframe interval may be larger than MaxImageBufferCount and PreEventCount. Please increase MaxImageBufferCount");
return;
}
std::unique_lock<std::mutex> lck(mutex);
@ -241,8 +199,8 @@ void PacketQueue::clearPackets(const std::shared_ptr<ZMPacket> &add_packet) {
return;
}
packetqueue_iterator it = pktQueue.begin();
packetqueue_iterator next_front = pktQueue.begin();
auto it = pktQueue.begin();
auto next_front = pktQueue.begin();
// First packet is special because we know it is a video keyframe and only need to check for lock
std::shared_ptr<ZMPacket> zm_packet = *it;
@ -250,18 +208,13 @@ void PacketQueue::clearPackets(const std::shared_ptr<ZMPacket> &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");
Debug(4, "Have lock on first packet");
++it;
delete lp;
if (it == pktQueue.end()) {
Debug(1, "Hit end already");
it = pktQueue.begin();
} else {
// Since we have many packets in the queue, we should NOT be pointing at end so don't need to test for that
while (*it != add_packet) {
zm_packet = *it;
@ -272,10 +225,14 @@ void PacketQueue::clearPackets(const std::shared_ptr<ZMPacket> &add_packet) {
}
delete lp;
if (is_there_an_iterator_pointing_to_packet(zm_packet) and (pktQueue.begin() == next_front)) {
Warning("Found iterator at beginning of queue. Some thread isn't keeping up");
#if 0
// There are no threads that follow analysis thread. So there cannot be an it pointing here
if (is_there_an_iterator_pointing_to_packet(zm_packet)) {
if (pktQueue.begin() == next_front)
Warning("Found iterator at beginning of queue. Some thread isn't keeping up");
break;
}
#endif
if (zm_packet->packet.stream_index == video_stream_id) {
if (zm_packet->keyframe) {
@ -283,15 +240,14 @@ void PacketQueue::clearPackets(const std::shared_ptr<ZMPacket> &add_packet) {
next_front = it;
}
++video_packets_to_delete;
Debug(4, "Counted %d video packets. Which would leave %d in packetqueue tail count is %d",
video_packets_to_delete, packet_counts[video_stream_id]-video_packets_to_delete, tail_count);
Debug(4, "Counted %d video packets. Which would leave %d in packetqueue tail count is %d",
video_packets_to_delete, packet_counts[video_stream_id]-video_packets_to_delete, tail_count);
if (packet_counts[video_stream_id] - video_packets_to_delete <= pre_event_video_packet_count + tail_count) {
break;
}
}
++it;
} // end while
}
} // end if first packet not locked
Debug(1, "Resulting pointing at latest packet? %d, next front points to begin? %d",
( *it == add_packet ),

View File

@ -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<Microseconds>(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

326
src/zmbenchmark.cpp Normal file
View File

@ -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 <algorithm>
#include <cassert>
#include <cstdlib>
#include <memory>
#include <random>
#include <utility>
#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<std::string> 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<Microseconds> &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<size_t> 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<FPSeconds>(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<Microseconds> timings;
};
std::vector<std::string> columns_;
std::vector<Row> 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<Image> 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>(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<Image> 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>& 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<Image> 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<int> &delta_box_percents,
const Vector2 &p_filter_box) {
std::vector<Microseconds> 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<int> percents = {0, 10, 50, 100};
std::vector<std::string> 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<Vector2> filterSizes = {Vector2(3, 3), Vector2(5, 5), Vector2(13, 13)};
for (const auto filterSize : filterSizes) {
RunDetectMotionBenchmarks(table, percents, filterSize);
}
table.Print();
return 0;
}

View File

@ -506,16 +506,16 @@ if ( ZM_OPT_EMAIL ) {
<button type="button" data-on-click-this="submitToEvents"><?php echo translate('ListMatches') ?></button>
<button type="button" data-on-click-this="submitToMontageReview"><?php echo translate('ViewMatches') ?></button>
<button type="button" data-on-click-this="submitToExport"><?php echo translate('ExportMatches') ?></button>
<button type="submit" name="action" value="execute" id="executeButton"><?php echo translate('Execute') ?></button>
<button type="button" data-on-click-this="submitAction" value="execute" id="executeButton"><?php echo translate('Execute') ?></button>
<?php
if ( canEdit('Events') ) {
?>
<button type="submit" name="action" value="Save" id="Save"><?php echo translate('Save') ?></button>
<button type="submit" name="action" value="SaveAs" id="SaveAs"><?php echo translate('SaveAs') ?></button>
<button type="button" data-on-click-this="submitAction" value="Save" id="Save"><?php echo translate('Save') ?></button>
<button type="button" data-on-click-this="submitAction" value="SaveAs" id="SaveAs"><?php echo translate('SaveAs') ?></button>
<?php
if ( $filter->Id() ) {
?>
<button type="button" value="Delete" data-on-click-this="deleteFilter"><?php echo translate('Delete') ?></button>
<button type="button" value="delete" data-on-click-this="deleteFilter"><?php echo translate('Delete') ?></button>
<?php
}
}

View File

@ -166,9 +166,17 @@ function submitToExport(element) {
window.location.assign('?view=export&'+$j(form).serialize());
}
function deleteFilter( element ) {
function submitAction(button) {
console.log(button.value);
var form = button.form;
form.elements['action'].value = button.value;
form.submit();
}
function deleteFilter(element) {
var form = element.form;
if ( confirm( deleteSavedFilterString+" '"+form.elements['filter[Name]'].value+"'?" ) ) {
console.log(form);
if (confirm(deleteSavedFilterString+" '"+form.elements['filter[Name]'].value+"'?")) {
form.elements['action'].value = 'delete';
form.submit();
}