Commit Graph
210 Commits
Author SHA1 Message Date
Jash 5a369adb8e docs: the conversion is what resizes, not the decode refs #3681 2026-08-26 18:48:15 +05:30
Jash 5e0d6afaf4 fix: send pre-scaled frames as built when scale or zoom changes refs #3681 2026-08-26 18:22:39 +05:30
Jash e48ae03275 perf: scale during colour conversion in zms playback fixes #3681 2026-08-26 11:15:19 +05:30
Jash f5cfbcd54c fix: make MonitorStream playback state shared with the command thread atomic fixes #4939 2026-08-16 16:46:47 +05:30
Isaac Connor b18ac74d9e fix: let zms stream the event a monitor is currently recording
loadInitialEventData() selected on unix_timestamp(`EndDateTime`) > t. An event
still being written has no EndDateTime, unix_timestamp(NULL) is NULL, and
NULL > t is never true, so the event currently recording could never be found.

zms.cpp:374 prefers the monitor_id form of setStreamStart whenever both
monitor and time are given, which is what montage review always sends, so its
`event` parameter never got a chance to help. Reviewing the present moment
returned the no-image placeholder, and every such request logged at ERR and
inserted a row into Logs.

Falls back to StartDateTime + Length, which zmc flushes every few seconds. An
event with neither ends where it starts, so a crash-orphaned event cannot match
every query and be picked ahead of the real one by the ORDER BY. Same
expression the API's Event model uses for EndTimeSecs.

Finding no event for a time is also no longer ERR. A client may ask for a time
the monitor was not recording, and it does so once per request.

Verified against a live instance: for a montage review request into the
recording event, the installed zms returned 2935 bytes (the placeholder) and
logged the failure, the rebuilt one returned a 134949 byte frame and logged
nothing.
2026-08-10 20:54:22 -04:00
Isaac ConnorandClaude Opus 4.8 7cd4be5cfb feat: add nph-zms memory diagnostics refs #5006
Add instrumentation to trace which stream type and workload precede the
occasional runaway memory growth in nph-zms.

- zm_utils: add zm_get_rss_kb() reading resident set size from
  /proc/self/statm
- zms: log a one-shot Debug summary of the decoded request (source, mode,
  monitor/event, connkey, scale, buffer, ttl, rss) so a runaway process
  can be tied to its stream parameters
- MonitorStream/EventStream: emit a periodic Debug RSS trace every 30s.
  The EventStream trace includes frames-vector size and frame_count,
  which scale with reloaded event length and will show whether the
  in-process event reload path drives the growth

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 09:48:44 -04:00
Isaac ConnorandClaude Opus 4.8 8342effd49 perf: reuse Image buffer in EventStream::sendFrame to cut per-frame heap allocations
Streaming an event allocated and freed a ~2MB Image on every frame. Add a
reuse_image_ member and decode JPEGs into it via ReadJpeg, which reuses its
pixel buffer when dimensions match (every frame of a given event), on both
the JPEG and MPEG paths. The uncommon ffmpeg (mp4) path uses a unique_ptr so
it frees automatically without a manual delete or pointer-identity guard.

Replace the existence-check stat() calls, which filled an unused struct stat,
with std::filesystem::exists() using the error_code overload to keep stat's
non-throwing 'any failure means not present' behaviour.

This achieves the heap-allocation reduction of #4609 using modern C++ rather
than fixed char buffers and string_view. The per-frame path-string allocation
was already eliminated on master via the reuse_filepath_ member.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 17:45:12 -04:00
copilot-swe-agent[bot]andconnortechnology 1bce09f4e8 fix: address review feedback - endian decode, send_twice init, setLastViewed, curr_frame_id gating
Agent-Logs-Url: https://github.com/ZoneMinder/zoneminder/sessions/b457dcf6-616f-4df9-bc28-76640bf5dd39

Co-authored-by: connortechnology <925519+connortechnology@users.noreply.github.com>
2026-05-17 03:29:41 +00:00
copilot-swe-agent[bot]andconnortechnology 35b453c935 fix: improve stopped state handling in EventStream run loop
Agent-Logs-Url: https://github.com/ZoneMinder/zoneminder/sessions/ba9cb47a-a3e8-4e13-aec7-c9cd258e2a3d

Co-authored-by: connortechnology <925519+connortechnology@users.noreply.github.com>
2026-05-06 02:11:15 +00:00
copilot-swe-agent[bot]andconnortechnology 1be352c790 fix: add stopped state for CMD_STOP, fixing paused=true response bug
When CMD_STOP is sent via AJAX using ZMS MJPEG streaming, the response
was incorrectly returning paused=true instead of indicating a stopped
state (paused=false).

Changes:
- Add `stopped` boolean to StreamBase (zm_stream.h)
- MonitorStream: CMD_STOP now sets stopped=true, paused=false instead
  of paused=true; run loop skips frame sending when stopped
- EventStream: CMD_STOP sets stopped=true (was already setting
  paused=false); run loop skips frame sending when stopped
- All other play/pause commands reset stopped=false
- Both streams include stopped field in the status response struct
- stream.php unpacks the new stopped field from MSG_DATA_WATCH and
  MSG_DATA_EVENT responses
- MonitorStream.js handles stopped status in UI (shows 'Stopped' mode)
- EventStream.js tracks stopped state from server response

Fixes issue: CMD_STOP response paused=true should be paused=false

Agent-Logs-Url: https://github.com/ZoneMinder/zoneminder/sessions/ba9cb47a-a3e8-4e13-aec7-c9cd258e2a3d

Co-authored-by: connortechnology <925519+connortechnology@users.noreply.github.com>
2026-05-06 01:58:48 +00:00
copilot-swe-agent[bot]andconnortechnology 2dfb79975d fix: prevent segfault when MPEG stream codec fails to open
Co-authored-by: connortechnology <925519+connortechnology@users.noreply.github.com>
2026-03-05 02:15:08 +00:00
Isaac Connor 2b33bdcd5e Fix missing _ 2026-03-04 17:50:35 -05:00
Isaac Connor f61a2e0a6b Merge branch 'master' into copilot/refactor-filepath-handling 2026-03-04 17:34:44 -05:00
Isaac ConnorandClaude Opus 4.6 bf82278b6e feat: integrate EventStream into montagereview with zms recovery and fixes
- Integrate EventStream.js into montagereview.js for persistent MJPEG
  streaming in replay mode (speeds >= 1x), falling back to per-frame
  mode=single for speeds < 1x where zms lacks slow-motion support
- Add EventStream recovery logic: detect zms death via AJAX failures,
  img.onerror, and error responses; auto-restart with new connkey and
  exponential backoff (max 5 retries)
- Fix zms crash on bulk/interpolated frames: add stat() check in
  sendFrame() for SaveJPEGs & 1 path, fall through to ffmpeg_input
  when JPEG file doesn't exist on disk; send "No frame available"
  text frame instead of terminating when no source available
- Center monitor canvases: text-align in CSS for scale mode,
  horizontal offset calculation in maxfit2() for fit mode
- Reduce console noise: comment out high-frequency debug logs,
  convert error-path logs to console.warn/error
- Fix ESLint sourceType to "script" for traditional non-module JS
  files, resolving false-positive no-unused-vars on global functions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 19:14:06 -05:00
Isaac ConnorandClaude Opus 4.6 4c42e736af fix: reset send_twice flag after use in EventStream::runStream
send_twice was set to true by zoom/pan/scale/seek commands when paused
but never reset to false. Once set, every subsequent frame was sent
twice forever, even after unpausing. This doubled bandwidth usage and
increased exposure to the processCommand race condition.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 18:19:20 -05:00
Isaac ConnorandClaude Opus 4.6 d23860ab96 fix: hold mutex for entire processCommand to prevent frame vector race
processCommand runs on a separate thread but several command handlers
(CMD_PREV, CMD_NEXT, CMD_FASTREV, CMD_PAUSE, CMD_STOP, and zoom/pan/scale
commands) modified shared state (curr_frame_id, paused, replay_rate, etc.)
without holding the mutex. This allowed the main runStream loop to read a
corrupted curr_frame_id between its bounds check and the vector access in
sendFrame, causing a vector out-of-bounds assertion failure.

Move the mutex acquisition to the top of processCommand and remove the
redundant per-case scoped_locks that would otherwise deadlock.

maybe fixes #4644

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 18:19:20 -05:00
copilot-swe-agent[bot]andconnortechnology a843684ff3 refactor: use reusable reuse_filepath_ member in EventStream::sendFrame
Co-authored-by: connortechnology <925519+connortechnology@users.noreply.github.com>
2026-02-11 04:19:19 +00:00
nic d91556e1ff Revert "perf: skip FFmpeg video probe for JPEG-based event streams" 2026-02-10 20:08:48 -06:00
Nic BoetandClaude Opus 4.6 f77ca859ed perf: skip FFmpeg video probe for JPEG-based event streams
When SaveJPEGs is enabled, sendFrame() reads JPEG files directly from
disk and never touches the FFmpeg input. However, loadEventData()
unconditionally called FFmpeg_Input::Open() on the event video file,
which runs avformat_find_stream_info() — a 2-5 second probe that was
pure overhead for JPEG-based streaming.

Gate the Open() call on !(SaveJPEGs & 1) so the expensive probe is
only performed when frame extraction from the video container is
actually needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 23:14:55 -06:00
Isaac Connor f7ececbf83 Merge pull request #4607 from nabbi/event-playback-fixes-master
Fix event playback stability and optimize seek performance
2026-02-09 17:44:05 -05:00
Nic Boet 4b098fb760 perf: replace O(n) linear frame seeks with O(log n) binary search
Replace two linear scans in event playback seeking:

- seek(): O(n) walk from frame 0 replaced with std::lower_bound on
  timestamp. Falls back to last frame if target is past end.

- processCommand(CMD_SEEK): O(n) estimate-then-walk replaced with
  std::upper_bound on offset, then step back one. Handles edge cases
  where offset lands before first frame or past last frame.

For a 10-minute event at 30fps (~18,000 frames), worst case drops
from 18,000 comparisons to ~14 (log2).
2026-02-09 12:44:59 -06:00
Nic BoetandClaude Opus 4.6 0ca3ed3155 fix: reset keepalive timer on event-end pause to prevent connection drop
When checkEventLoaded pauses at the end of an event in MODE_SINGLE/
MODE_NONE, the main loop relies on last_frame_sent to decide when to
send keepalive frames. If last_frame_sent is stale, the loop waits up
to 5 seconds (MAX_STREAM_DELAY) before sending anything. During that
gap the HTTP connection can time out, causing SIGPIPE on the next write.

Reset last_frame_sent to epoch so the next iteration sends a keepalive
immediately.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 12:29:12 -06:00
Nic BoetandClaude Opus 4.6 328d5c2a99 fix: move sendFrame inside mutex scope to prevent race condition
sendFrame() reads frames[curr_frame_id-1] but was called outside the
mutex scope. The command processor thread (running processCommand via
checkCommandQueue) can modify curr_frame_id or reload the event
(clearing/rebuilding frames) between the mutex unlock and the
sendFrame call, causing out-of-bounds access.

Move sendFrame into the first mutex scope so frames[] access is
protected from concurrent modification by the command thread.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 00:07:19 -06:00
Nic Boet 011defdad2 fix: harden eventstream against vector bounds, dangling pointers, and off-by-one
Three related safety fixes for event_data->frames vector usage:

- Replace frame_count/last_frame_id with frames.size() for bounds checks,
  since synthetic frame interpolation inflates the vector beyond the DB
  row count, making the old fields unreliable for vector indexing.

- Replace dangling last_frame pointer with last_frame_idx index, since
  emplace_back can reallocate the vector and invalidate all pointers.

- Fix seek backward off-by-one in checkEventLoaded() that set
  curr_frame_id = event_data->frames.size() then accessed
  frames[curr_frame_id - 1] before the vector was populated.
2026-02-09 00:07:14 -06:00
Nic BoetandClaude Opus 4.6 637fe24f0a perf: skip Frames table scan during event playback unless debugging
Remove the FramesDuration subquery from the event metadata query in
EventStream::loadEventData(). Previously every playback initiation
ran:

  SELECT max(Delta)-min(Delta) FROM Frames WHERE EventId=Events.Id

as an embedded subquery. Without a covering index on (EventId, Delta),
MySQL walks the EventId_idx to find matching rows then fetches each
table row to read the Delta column. For a 10-minute event in
Record/Mocord mode at 30fps with bulk_frame_interval=100, that is
~180 index lookups + row fetches. For alarm-heavy events where every
frame gets a DB row, it can reach thousands of row fetches. This
blocks playback start on every event view.

The FramesDuration value was only consumed by a Debug level 2 log
comparing it against Event Length. It was never used in any playback
computation, frame timing, seek logic, or client-facing output.
The frames_duration field has been removed from the EventData struct
entirely. The diagnostic query and its log are now colocated inside
a logLevel() >= DEBUG2 guard using a local variable.

Production impact: none. Default ZoneMinder log level is INFO (0).
DEBUG2 is only reached via explicit operator configuration
(Options > System > LOG_LEVEL_FILE or ZM_DBG_LEVEL env variable).
No production deployment runs at DEBUG2 as it generates thousands
of log lines per second per daemon. The subquery code path is
unreachable under default configuration. Operators who enable DEBUG2
get the same diagnostic output as before.

Theoretical gains on playback initiation per event:
- Eliminates 1 SQL subquery performing N row fetches from the
  critical path (N = Frames rows for the event)
- Record mode, 10min event: ~180 fewer row fetches
- Alarm-heavy 10min event: ~3,000-18,000 fewer row fetches
- Reduces MySQL buffer pool pressure from Frames page reads
- Reduces InnoDB row lock contention with concurrent frame INSERTs
  from active recording daemons hitting the same EventId range

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 16:18:51 -06:00
Isaac ConnorandClaude Opus 4.5 df6c4a4a13 fix: address multiple bugs across core source files
- zm_analysis_thread.cpp, zm_decoder_thread.cpp: Fix potential deadlock
  in Start() by calling Stop() before joining thread
- zm_camera.cpp: Add null check after avformat_alloc_context()
- zm_comms.cpp: Fix memory leak in InetSocket::bind(), fix error message
  typo in deleteReader(), fix clearReaders/clearWriters to recalculate
  mMaxFd properly
- zm_config.cpp: Fix potential buffer underrun when parsing config files,
  fix misplaced SERVER_ID check logic
- zm_db.cpp: Fix logger level not restored on early return in zmDbDo(),
  fix empty string access in DB_HOST parsing
- zm_event.cpp: Fix typo "foudn" -> "found", fix memory leak with Tag
  allocation, fix variable shadowing with video_file
- zm_event_tag.cpp: Fix null dereference when AssignedBy is NULL
- zm_eventstream.cpp: Fix dangling pointer bugs with emplace_back
  (use auto& instead of auto), fix memory leak in loadInitialEventData

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 10:18:49 -05:00
Isaac Connor c6ab1c143b fix: address multiple bugs in eventstream, fifo, and monitor
zm_eventstream.cpp:
- Fix null pointer dereference when video_file or scheme column is NULL
- Fix out-of-bounds array access in CMD_SEEK handler when curr_frame_id
  decrements to 0
- Fix setStreamStart calling wrong loadInitialEventData overload (event_id
  was being truncated and used as monitor_id)

zm_fifo.cpp:
- Fix close(-1) call when file creation fails
- Fix use of uninitialized raw_fd when on_blocking_abort is false
- Reset outfile and raw_fd in close() to prevent use-after-close

zm_monitor.cpp:
- Fix shm_id being zeroed before use in shmctl() call
2026-02-02 23:24:08 -05:00
Isaac Connor 4863febff0 Split out seeking code into ::seek. Test for no rows returned when loading by monitor_id and time. Use SystemTimePoint instead of time_t. Add setStreamStart(event_id, time) 2026-01-05 16:37:30 -05:00
Isaac Connor 920d38b4cd include filesystem since we use std::filesystem::exists 2025-11-11 13:13:53 -05:00
Isaac Connor 1b2fd12f57 Fix warnings about unequal frame coutns 2025-10-11 10:56:05 -04:00
Isaac Connor b66bfc8960 Prevent looking for a last frame that doesn't actually exist 2025-09-16 15:58:19 -04:00
Isaac Connor e931f5ee40 Send an error image instead of ISE when we can't get the required frame 2025-08-26 08:08:06 -04:00
Isaac Connor 4757d711ef Change various cases of int img_buffer_size to size_t 2025-07-19 14:18:32 -04:00
Isaac Connor 9d0f2c25bb Small debug cleanup 2025-06-11 14:51:43 -04:00
Isaac Connor 0d34e4647a Log scale in debug about status command 2024-12-09 19:07:51 -05:00
Isaac Connor 02871576a5 Add fps to event status. Convert CmdTimer to Interval 2024-10-22 18:22:19 -04:00
Isaac Connor 33ede2f7d1 FIx floating point exception 2024-09-27 16:17:09 -04:00
Isaac Connor c6e5ff43e7 Merge branch 'master' of github.com:ZoneMinder/zoneminder 2024-09-27 16:01:20 -04:00
Isaac Connor 3802ec17d3 Add test for 0 duration may fix floating point exceptions 2024-09-27 10:01:09 -04:00
Isaac Connor bf7920bad8 Merge branch 'master' of github.com:ZoneMinder/zoneminder 2024-09-24 19:30:47 -04:00
Isaac Connor 37fef75eff Handle 0 frame_mod 2024-09-24 17:23:23 -04:00
Isaac Connor a28d88880f Merge branch 'master' of github.com:ZoneMinder/zoneminder 2024-09-22 08:20:08 -04:00
Isaac Connor 312a15801e Using last_delta really doesn't work. Instead let's use remaining time/remaining frames. 2024-09-20 18:54:54 -04:00
Isaac Connor 3dd08cd213 Load duration from the db instead of calculating it from start and endtime. This gives us the decimal part as well. 2024-09-19 16:09:38 -04:00
Isaac Connor 7c8738e976 Correct log in debug statusment 2024-09-16 17:22:27 -04:00
Isaac Connor e68fb77e4b Take frame_mod into account when calculating time to sleep. Fixes rate not really working. 2024-09-16 16:41:00 -04:00
Isaac Connor 1deccb8cf4 Instead of just exiting, set zm_terminate to true so we exit gracefully 2024-08-22 19:09:15 -04:00
Isaac Connor d968e243ff Implement mode=single for events, so that we can efficiently just grab a single frame 2024-08-19 18:26:35 -04:00
Isaac Connor 5237641efe Fix calculation for when to send a keepalive image. Possible fixes #4101 2024-08-13 16:38:14 -04:00
Isaac Connor 9b1f3fd3e1 Use better format specifiers 2024-05-24 16:06:21 -04:00