Commit Graph

1429 Commits

Author SHA1 Message Date
Isaac Connor
b2b50aba5d perf: skip clearPackets early-returns and allow drop-to-iterator-keyframe
Two related changes to PacketQueue::clearPackets, called by the analysis
thread on every video packet:

1. Lock-free call-site gate (should_try_clear) on the analysis path.
   In keep_keyframes mode the existing early-return at the top of
   clearPackets discards most non-keyframe video packets after acquiring
   the queue mutex. Add an inline lock-free check at the call site so
   non-keyframe packets skip the mutex acquire entirely. clear_packets_pending_
   is now std::atomic<bool> so it can be read without the lock; a stale
   read is harmless (at worst we make one extra cheap early-returning call).
   The !keep_keyframes path always returns true from the gate because that
   mode pops one packet at a time on every video packet.

2. Iterator boundary in the scan loop changed from >= to >. Setting
   next_front to a packet that an iterator points at is safe because
   clearPackets deletes strictly before next_front, so the iterator's
   own packet stays in the queue. Previously, an event-start (or other)
   iterator landing exactly on a keyframe blocked the leading GOP from
   being dropped until the iterator advanced; now we can include that
   keyframe as next_front while the iterator continues to point at it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 19:39:04 -04:00
Isaac Connor
33a2148c7c Merge branch 'master' of github.com:ZoneMinder/zoneminder 2026-04-02 14:17:21 -04:00
Isaac Connor
af33926aa3 fix: reset last_write_index in Pause to restore DECODING_ONDEMAND bootstrap
Pause() did not restore last_write_index to the sentinel value
(image_buffer_count). After a Pause/Play cycle, the DECODING_ONDEMAND
fallback condition (last_write_index == image_buffer_count) was dead,
making decoding depend entirely on hasViewers(). This created a timing
gap where the decoder skipped packets after Play before zms called
setLastViewed, causing the decoder to fall behind capture.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 13:45:08 -04:00
Isaac Connor
1ec9c449b4 fix: null-check StorageId column in Monitor::Load to prevent segfault
dbrow[col] for StorageId can be NULL when the database column contains
a NULL value. Passing NULL to atoi causes a segfault in strtol.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 09:13:50 -04:00
Isaac Connor
0e78b5a6c6 Merge branch 'master' of github.com:ZoneMinder/zoneminder 2026-03-26 17:20:23 -04:00
Isaac Connor
277eece633 Log the value when event_close_mode is unkown 2026-03-26 17:19:59 -04:00
Isaac Connor
9adbed166e fix: add missing CLOSE_DURATION event close mode handling
- Add "duration" string mapping in constructor and DB loader — was
  silently falling back to CLOSE_IDLE
- Add CLOSE_DURATION handler in analysis logic: close event when
  duration >= section_length regardless of alarm state

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 14:01:14 -04:00
Isaac Connor
1b689d6e67 fix: include alarm_frame_count in ready_count calculation
ready_count only considered warmup_count and pre_event_count, but
openEvent walks back max(pre_event_count, alarm_frame_count) frames.
When pre_event_count=0 and alarm_frame_count=2, analysis started before
the queue had enough packets, causing spurious "Hit end of packetqueue
before satisfying pre_event_count" warnings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 12:32:28 -05:00
Isaac Connor
635439eb5b feat: add zma utility for offline event re-analysis
Add a new zma binary that re-analyses recorded events using current zone
settings. It decodes frames from stored video (FFmpeg) or JPEG files and
runs the full motion detection pipeline (DetectMotion + ref_image blending
+ state machine).

Two modes of operation:
- Default: updates the original event's motion stats (AlarmFrames,
  TotScore, AvgScore, MaxScore) in the database
- --create-events (-c): creates new events from detected motion regions,
  with video files hard linked (copy fallback) to the new event directory

Additional features:
- --save-analysis (-a): writes analysis JPEGs with zone alarm overlays
  to the event directory for visual inspection
- --monitor (-m): override which monitor's zone config to use
- --verbose (-v): increase debug verbosity

Adds Monitor::AnalyseFrame() public methods that encapsulate DetectMotion
+ ref_image initialization and blending, with an optional analysis_image
output parameter for zone overlay rendering. Also guards shared_data
access in DetectMotion to allow offline use without shared memory.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 18:19:20 -05:00
Isaac Connor
66628ae163 perf: replace sleep polling with condition wait in Event::Run()
The event thread was sleeping 33ms (ZM_SAMPLE_RATE) between checks for
packet decoded/analyzed status. Replace with a condition variable wait
on the packet itself, using a 2ms timeout as safety net for the race
between flag check and wait entry.

Add packet->notify_all() at every site where decoded or analyzed is set
to true, so the event thread wakes up near-instantly. Add wait_for()
to ZMPacketLock to support timed waits.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 15:38:07 -05:00
Pliable Pixels
e97a37df29 fix: preserve legacy execlp() behavior for commands without % tokens
If EventStartCommand/EventEndCommand contains a % character, use the
new token substitution (%EID%, %MID%, %EC%) with sh -c execution.
Otherwise, fall back to the original execlp() behavior that passes
event_id and monitor_id as $1 and $2, so existing installs are not
broken.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 15:38:07 -05:00
Pliable Pixels
cf3f44a466 fix: shell-escape %EC% token to prevent command injection
cause can include trigger_data->trigger_cause (writable via zmtrigger
over the network) and zone labels (user-configured). Without escaping,
shell metacharacters in cause would be interpreted by sh -c.

Wraps cause in single quotes (with embedded single-quote escaping)
before substitution. %EID% and %MID% are safe as they are always
numeric from std::to_string.

Note on backward compatibility: the old execlp() passed event_id and
monitor_id as argv[1]/argv[2]. This PR intentionally does not preserve
that behavior — the old execlp() treated the entire command string as
an executable path, making it impossible to pass arguments, so any
working setup was already a simple path with no args. Users should
migrate to %EID%/%MID% tokens which are more explicit and flexible.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 15:38:07 -05:00
Pliable Pixels
7f9fc2ee2f feat: support token substitution in EventStartCommand/EventEndCommand
Replace execlp() with execl("/bin/sh") and substitute %EID%, %MID%,
and %EC% tokens before execution. This allows users to pass arguments
directly in the command string, e.g.:

  /path/to/zm_detect.py -c /etc/zm/config.yml -e %EID% -m %MID% -r "%EC%" -n

Previously execlp() treated the entire command string as the executable
path, making it impossible to pass arguments without a wrapper script.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 15:38:07 -05:00
Isaac Connor
0f7aceb19e perf: replace sleep polling with condition wait in Event::Run()
The event thread was sleeping 33ms (ZM_SAMPLE_RATE) between checks for
packet decoded/analyzed status. Replace with a condition variable wait
on the packet itself, using a 2ms timeout as safety net for the race
between flag check and wait entry.

Add packet->notify_all() at every site where decoded or analyzed is set
to true, so the event thread wakes up near-instantly. Add wait_for()
to ZMPacketLock to support timed waits.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 09:44:42 -05:00
Isaac Connor
08144613fc Merge pull request #4618 from pliablepixels/fix_event_start_stop_commands
feat: support token substitution in EventStartCommand/EventEndCommand
2026-02-13 14:40:18 -05:00
Pliable Pixels
c4fa045d1e fix: preserve legacy execlp() behavior for commands without % tokens
If EventStartCommand/EventEndCommand contains a % character, use the
new token substitution (%EID%, %MID%, %EC%) with sh -c execution.
Otherwise, fall back to the original execlp() behavior that passes
event_id and monitor_id as $1 and $2, so existing installs are not
broken.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 14:35:17 -05:00
Pliable Pixels
20f0d9f6f9 fix: shell-escape %EC% token to prevent command injection
cause can include trigger_data->trigger_cause (writable via zmtrigger
over the network) and zone labels (user-configured). Without escaping,
shell metacharacters in cause would be interpreted by sh -c.

Wraps cause in single quotes (with embedded single-quote escaping)
before substitution. %EID% and %MID% are safe as they are always
numeric from std::to_string.

Note on backward compatibility: the old execlp() passed event_id and
monitor_id as argv[1]/argv[2]. This PR intentionally does not preserve
that behavior — the old execlp() treated the entire command string as
an executable path, making it impossible to pass arguments, so any
working setup was already a simple path with no args. Users should
migrate to %EID%/%MID% tokens which are more explicit and flexible.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 10:51:04 -05:00
Isaac Connor
0882a3ad1e fix: resolve Event::Run thread hang preventing zmc clean shutdown
Event::Run could block indefinitely in PacketQueue methods during normal
event closing (closeEvent from analysis thread), because their wait
predicates only check deleting/zm_terminate, not Event's terminate_ flag.

Three changes fix this:
- get_packet_no_wait: return immediately when iterator at end instead of
  blocking on condition variable (makes it truly non-blocking)
- Event::Run: use increment_it(wait=false) since deletePacket can advance
  the iterator to end() during AddPacket_ without the queue lock
- Event::Stop: call packetqueue->notify_all() to wake timed waits so
  Run() checks terminate_ promptly

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 23:00:59 -05:00
Isaac Connor
d5b4709584 fix: reduce packet queue backpressure from event thread blocking
Three changes to prevent the analysis thread from stalling and the
packet queue from filling up:

1. Replace blind sleep_for/usleep in Event::Run() with
   packetqueue->wait_for() condition variable waits. The event thread
   now wakes immediately when decoder/analysis completes or new packets
   are queued, instead of always sleeping the full 33ms/10ms.

2. Add missing packetqueue.notify_all() calls after setting
   packet->analyzed (Monitor::Analyse) and packet->decoded
   (DECODING_NONE path in Monitor::Capture) so the event thread's
   condition waits actually get signaled.

3. Replace synchronous zmDbDoUpdate() calls in Event::~Event() with
   async dbQueue.push(). The two Events UPDATE queries (with Name
   fallback logic) are combined into a single query using MySQL IF().
   This eliminates blocking DB I/O from the close_event_thread, which
   the analysis thread joins on the next closeEvent() call.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 17:17:45 -05:00
Pliable Pixels
5ffc2120c7 feat: support token substitution in EventStartCommand/EventEndCommand
Replace execlp() with execl("/bin/sh") and substitute %EID%, %MID%,
and %EC% tokens before execution. This allows users to pass arguments
directly in the command string, e.g.:

  /path/to/zm_detect.py -c /etc/zm/config.yml -e %EID% -m %MID% -r "%EC%" -n

Previously execlp() treated the entire command string as the executable
path, making it impossible to pass arguments without a wrapper script.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 20:29:10 -05:00
Isaac Connor
dc74cb928f perf: reduce logging overhead and string temporaries in Analyse()
Raise ZoneStats::DumpToLog() from Debug level 1 to 4 since
per-zone stats are detailed diagnostics, not basic debug info.
Remove redundant DumpToLog call in zone loop (GetStats() already
calls it). Remove std::string temporaries in alarm cause building.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 22:52:15 -05:00
Isaac Connor
3907eb1687 fix: update last_write_time when skipping decode in KEYFRAMESONDEMAND
In KEYFRAMESONDEMAND mode without viewers, only keyframes are decoded.
Non-keyframe packets skip decoding and never reach the Phase 5 code
that updates last_write_time.  If the keyframe interval exceeds
ZM_WATCH_MAX_DELAY, zmwatch sees the stale timestamp and restarts
the capture daemon unnecessarily.

Update last_write_time for skipped video packets so zmwatch knows the
decode thread is still processing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 13:23:24 -05:00
Isaac Connor
7f97f2b77e fix: flush decoder when on-demand decoding is no longer needed
When using DECODING_ONDEMAND or DECODING_KEYFRAMESONDEMAND, packets
accumulate in the decoder_queue while a viewer is connected. When the
viewer disconnects, should_decode becomes false but stale packets
remain queued in the decoder indefinitely — Phase 1 tries
receive_frame (gets EAGAIN), Phase 2 skips sending new packets, and
the cycle repeats.

Flush the decoder via avcodec_flush_buffers in both Phase 1 (before
attempting receive_frame) and Phase 2 (after determining decoding is
not needed), marking queued packets as decoded and clearing the queue.
This releases held packet locks and resets the decoder so it starts
clean when a viewer reconnects.

Also rename the 'dominated' variable to 'already_decoded' for clarity.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-05 22:47:37 -05:00
Isaac Connor
94f3a5771b fix: release GPU surfaces immediately after hw transfer
The nvidia-vaapi-driver would fail with "list argument exceeds maximum
number" when decoding HEVC because GPU surfaces were being held in the
packet queue after transfer, exhausting the VAAPI surface pool.

Changes:
- Transfer hw frames to software immediately in receive_frame() while
  the VA context is still valid, then release the GPU surface
- Check hw_frames_ctx in needs_hw_transfer() to detect already-transferred
  frames
- Remove extra_hw_frames and thread_count settings (not needed with
  immediate surface release)
- Fix EAGAIN handling in send_packet to wait instead of busy-loop

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 22:51:21 -05:00
Isaac Connor
2892db312f Check return from transfer_hwframe so as not to crash 2026-02-04 21:34:42 -05:00
Isaac Connor
ae42c3c94d Wait on the packetqueue condition instead of sleeping. Should help analysis keep up with decoding better and offer faster shutdown. 2026-02-04 20:44:17 -05:00
Isaac Connor
a96b776949 fix: apply credentials to secondary URL and fix buffer overflow in DumpSettings
- Apply credentials to secondary stream URL in FFmpegCamera (was causing 401 Unauthorized)
- Add empty check for rtsp_second_path in RTSP2WebManager before applying credentials
- Replace unsafe sprintf pattern in Monitor::DumpSettings with std::string + stringtf
- Refactor Zone::DumpSettings to return std::string instead of writing to char buffer
- Add decimal precision to event duration debug output

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 17:40:26 -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
eae89025ee refactor: rename RTSP2WebStream to StreamChannel
Rename applies to Go2RTC, Janus, and RTSP2Web streaming options.
Update enum values from Primary/Secondary to Restream/CameraDirectPrimary/CameraDirectSecondary.

- Add db migration zm_update-1.37.79.sql to rename column and migrate data
- Update C++ enum StreamChannelOption and member stream_channel
- Update PHP getStreamChannelOptions() method
- Update all JavaScript references
- Auto-select CameraDirectPrimary when Restream option becomes disabled

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 19:37:08 -05:00
Isaac Connor
d89f2e59db refactor: rename Janus_Use_RTSP_Restream to Restream
Rename Janus-specific restream fields to be more generic since they are
now used by Go2RTC and RTSP2Web as well:
- Janus_Use_RTSP_Restream → Restream
- Janus_RTSP_User → RTSP_User

Update visibility logic so the Restream checkbox appears when RTSPServer
is enabled AND any streaming service (Janus, Go2RTC, or RTSP2Web) is
selected, rather than only when Janus is enabled.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 18:47:37 -05:00
Isaac Connor
b76a7c9640 fix: add null checks for y_image before dereferencing in Analyse
When analysis_image is set to ANALYSISIMAGE_YCHANNEL but in_frame is
not populated (e.g., LocalCamera which captures directly to image),
get_y_image() returns nullptr. The code was dereferencing this null
pointer in DetectMotion and Blend calls, causing a segfault.

Now checks if y_image is valid before use and skips the operation
with a debug message if unavailable.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 14:29:04 -05:00
Isaac Connor
1d23bd297f Warn about too slow send_packet only if debug is on 2026-01-28 14:51:15 -05:00
Isaac Connor
141df0a912 Only do y_image stuff if we have a decoded in_frame 2026-01-27 15:17:29 -05:00
Isaac Connor
0f917ae1ed refactor: restructure Decode() and fix PacketQueue locking issues
Monitor::Decode():
- Reorganize into 5 clear phases with descriptive comments
- Phase 1: Receive decoded frame from decoder
- Phase 2: Get and send new packet to decoder
- Phase 3: Convert decoded frame to Image
- Phase 4: Prepare Y-channel for analysis
- Phase 5: Process RGB image (deinterlace, rotate, privacy, timestamp)
- Extract applyOrientation() and applyDeinterlacing() helper functions
- Keep slow send_packet detection timing for diagnostics

PacketQueue locking fixes:
- Move lock acquisition before accessing shared state in queuePacket()
- Keep lock held while iterating in stop()
- Add lock to addStream()
- Remove duplicate packet_counts allocation in clear()

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 11:18:50 -05:00
Isaac Connor
7194008fe6 Set decoded=true in error case when de-interlacing 2026-01-27 10:36:03 -05:00
Isaac Connor
5c402ccab4 Add wait parameter to increment_it. Sometimes we should wait and sometimes not. 2026-01-27 09:30:28 -05:00
Isaac Connor
f8256a1d0e ONly generate y_image if we are doing analysing. 2026-01-27 07:57:43 -05:00
Isaac Connor
8eee695e63 Cleanup return values, kill dead code and duplicated checks 2026-01-26 18:28:59 -05:00
Isaac Connor
46ebe10d73 Fix missing increment_it 2026-01-26 18:01:04 -05:00
Isaac Connor
5ecb5fdb1c Need to call trasnfer_hwframe in case we are using hwaccel. 2026-01-26 15:11:05 -05:00
Isaac Connor
94702b8932 Codecs can return EAGAIN. We have to queue and wait for it to start giving us decoded frames. 2026-01-26 14:56:18 -05:00
Isaac Connor
91f5333e7d Cleanup access to y_image using get_y_image(). 2026-01-26 13:18:32 -05:00
Isaac Connor
91aa3b5d90 Remove no longer used dest_frame 2026-01-22 20:54:14 -05:00
Ben Dailey
17abf8c048 fix: restore ONVIF health check reconnection logic
The ONVIF polling thread was failing to reconnect after connection loss
because:

1. Exponential backoff delay was calculated but never used - the thread
   only slept 500ms between attempts regardless of retry count
2. After max_retries was exceeded, retry_count was never reset, causing
   permanent failure state
3. Monitor::connect() created new ONVIF objects without deleting old
   ones, causing duplicate threads and memory leaks

Changes:
- Use get_retry_delay() for actual exponential backoff (1s, 2s, 4s... up to 300s)
- Add 5-minute cool-down period after max_retries, then reset for fresh attempts
- Sleep in 1-second increments to remain responsive to termination signals
- Clean up existing ONVIF object in Monitor::connect() before creating new one
- Add setHealthy() accessor for consistency with setAlarmed() pattern
- Replace direct healthy_.load/store calls with isHealthy()/setHealthy()

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 09:06:58 -05:00
Ben Dailey
0079ee0107 feat: add dedicated polling thread for ONVIF events
Move ONVIF polling from shared Poll() loop (10s cadence) to dedicated
thread with 500ms cadence. This reduces worst-case ONVIF event latency
from ~10 seconds to ~500ms.

Changes:
- Add thread management to ONVIF class (thread_, terminate_, Run())
- Launch polling thread in start() after successful subscription
- Stop thread in destructor before SOAP cleanup
- Remove ONVIF handling from Monitor::Poll()
- Call onvif->start() immediately after creation in Monitor::connect()
- Increase default pull_timeout from PT5S to PT10S for better listening window

The ONVIF thread handles its own reconnection when unhealthy. Other
managers (Amcrest, RTSP2Web, Go2RTC, Janus) remain on 10s Poll() cadence.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 08:37:37 -05:00
Isaac Connor
f4a6227132 Fix comment. long long is 64bits 2026-01-20 16:29:40 -05:00
Isaac Connor
8fbe4be2c0 Fix SQL loading groups 2026-01-20 16:26:41 -05:00
Isaac Connor
e663605b56 Use correct, non-truncating cast to test success of shmat 2026-01-20 16:26:12 -05:00
Isaac Connor
b64520649d Use std::shared_ptr to fix memory leak due to use of GetSnapshot and not freeing the returned packet. 2026-01-20 16:18:43 -05:00
Isaac Connor
5c4afc9532 Only blend if y_image exists 2026-01-19 15:03:58 -06:00