mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-06-22 20:50:06 -04:00
Merge pull request #4941 from ZoneMinder/perf-eventstream-reuse-image-modern-cpp
perf: reuse Image buffer in EventStream::sendFrame (modern C++ alternative to #4609)
This commit is contained in:
@@ -27,6 +27,7 @@
|
||||
#include "zm_storage.h"
|
||||
#include <algorithm>
|
||||
#include <arpa/inet.h>
|
||||
#include <memory>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <filesystem>
|
||||
@@ -861,23 +862,25 @@ bool EventStream::sendFrame(Microseconds delta_us) {
|
||||
// Reusable string member avoids per-frame heap allocations.
|
||||
// After the first frame, the string's buffer is reused (unless path exceeds capacity).
|
||||
reuse_filepath_.clear();
|
||||
struct stat filestat = {};
|
||||
// exists() with an error_code overload mirrors stat()'s behaviour (treat any
|
||||
// failure as "not present") without throwing or filling an unused struct stat.
|
||||
std::error_code exists_ec;
|
||||
|
||||
// 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 ((frame_type == FRAME_ANALYSIS) && (event_data->SaveJPEGs & 2)) {
|
||||
reuse_filepath_ = stringtf(staticConfig.analyse_file_format.c_str(), event_data->path.c_str(), curr_frame_id);
|
||||
if (stat(reuse_filepath_.c_str(), &filestat) < 0) {
|
||||
if (!std::filesystem::exists(reuse_filepath_, exists_ec)) {
|
||||
Debug(1, "analyze file %s not found will try to stream from other", reuse_filepath_.c_str());
|
||||
reuse_filepath_ = stringtf(staticConfig.capture_file_format.c_str(), event_data->path.c_str(), curr_frame_id);
|
||||
if (stat(reuse_filepath_.c_str(), &filestat) < 0) {
|
||||
if (!std::filesystem::exists(reuse_filepath_, exists_ec)) {
|
||||
Debug(1, "capture file %s not found either", reuse_filepath_.c_str());
|
||||
reuse_filepath_.clear();
|
||||
}
|
||||
}
|
||||
} else if (event_data->SaveJPEGs & 1) {
|
||||
reuse_filepath_ = stringtf(staticConfig.capture_file_format.c_str(), event_data->path.c_str(), curr_frame_id);
|
||||
if (stat(reuse_filepath_.c_str(), &filestat) < 0) {
|
||||
if (!std::filesystem::exists(reuse_filepath_, exists_ec)) {
|
||||
Debug(1, "Capture file %s not found (bulk/interpolated frame %d), trying ffmpeg_input",
|
||||
reuse_filepath_.c_str(), curr_frame_id);
|
||||
reuse_filepath_.clear();
|
||||
@@ -889,9 +892,12 @@ bool EventStream::sendFrame(Microseconds delta_us) {
|
||||
}
|
||||
|
||||
if ( type == STREAM_MPEG ) {
|
||||
Image image(reuse_filepath_.c_str());
|
||||
// Decode into the reused member rather than a fresh per-frame stack Image;
|
||||
// ReadJpeg reuses its buffer when dimensions match, avoiding a ~2MB
|
||||
// malloc/free each frame.
|
||||
reuse_image_.ReadJpeg(reuse_filepath_, ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB);
|
||||
|
||||
Image *send_image = prepareImage(&image);
|
||||
Image *send_image = prepareImage(&reuse_image_);
|
||||
|
||||
if ( !vid_stream ) {
|
||||
vid_stream = new VideoStream("pipe:", format, bitrate, effective_fps,
|
||||
@@ -919,9 +925,18 @@ bool EventStream::sendFrame(Microseconds delta_us) {
|
||||
}
|
||||
} else {
|
||||
Image *image = nullptr;
|
||||
// Owns the image only on the ffmpeg (mp4) path; the JPEG path decodes into
|
||||
// the reused member, so nothing is freed there. unique_ptr replaces the
|
||||
// old new/delete pair and frees automatically at scope exit.
|
||||
std::unique_ptr<Image> owned_image;
|
||||
|
||||
if (!reuse_filepath_.empty()) {
|
||||
image = new Image(reuse_filepath_.c_str());
|
||||
// Decode into the reused member instead of allocating a fresh Image each
|
||||
// frame. ReadJpeg's WriteBuffer reuses the pixel allocation when the
|
||||
// dimensions match (every frame of a given event), eliminating a ~2MB
|
||||
// malloc/free per streamed frame.
|
||||
reuse_image_.ReadJpeg(reuse_filepath_, ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB);
|
||||
image = &reuse_image_;
|
||||
} else if (ffmpeg_input) {
|
||||
// Get the frame from the mp4 input
|
||||
const FrameData *frame_data = &event_data->frames[curr_frame_id-1];
|
||||
@@ -929,7 +944,8 @@ bool EventStream::sendFrame(Microseconds delta_us) {
|
||||
ffmpeg_input->get_video_stream_id(),
|
||||
FPSeconds(frame_data->offset).count());
|
||||
if (frame) {
|
||||
image = new Image(frame, monitor->Width(), monitor->Height());
|
||||
owned_image = std::make_unique<Image>(frame, monitor->Width(), monitor->Height());
|
||||
image = owned_image.get();
|
||||
} else {
|
||||
Error("Failed getting a frame.");
|
||||
sendTextFrame("Failed getting frame");
|
||||
@@ -998,7 +1014,7 @@ bool EventStream::sendFrame(Microseconds delta_us) {
|
||||
break;
|
||||
}
|
||||
int rc = send_buffer(img_buffer, img_buffer_size);
|
||||
delete image;
|
||||
// owned_image (ffmpeg path) frees here; reuse_image_ persists for next frame
|
||||
image = nullptr;
|
||||
if (!rc) return false;
|
||||
} // end if send_raw or not
|
||||
|
||||
@@ -128,6 +128,7 @@ class EventStream : public StreamBase {
|
||||
Storage *storage;
|
||||
FFmpeg_Input *ffmpeg_input;
|
||||
std::string reuse_filepath_; // reused across sendFrame calls to avoid per-frame heap alloc
|
||||
Image reuse_image_; // reused JPEG decode target; ReadJpeg reuses its buffer when dimensions match
|
||||
};
|
||||
|
||||
#endif // ZM_EVENTSTREAM_H
|
||||
|
||||
Reference in New Issue
Block a user