Commit Graph

1414 Commits

Author SHA1 Message Date
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
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
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
Isaac Connor
51334e8d18 Don't crash when decoding=keyframes due to y_image not being defined 2026-01-19 14:40:53 -06:00
Isaac Connor
4db0a431f8 Use packet->analyzed to keep event behind analysis. 2026-01-16 16:02:01 -05:00
Isaac Connor
b696d5edcd Re-enable copying the analysis_image to shm so that zms can show it. 2026-01-16 16:01:10 -05:00
Isaac Connor
131b5caee0 Update to RAII style ZMPacketLock. Rename snap to packet in zm_monitor. Keep hw_frame in packet." 2026-01-15 12:46:23 -05:00
Isaac Connor
0e7d215f99 move Event_Poller_Closes_Event into ONVIF as it only matters to ONVIF. Remove it from Amcrest. 2026-01-14 13:28:52 -05:00
Steve Gilvarry
cea4bf3402 Add to Go2RTC was never called for me, seemed to point check_go2rtc being called from this poller thread but was not enabled for go2rtc alone. 2026-01-14 21:36:37 +11:00
Isaac Connor
e3e998854a AMCREST and ONVIF are not mutually exclusive. 2026-01-05 12:17:50 -05:00
Isaac Connor
c325610aba Change structure of availableTags to be an array of objects so that we don't lose sorting. Sort alphabetically. Use json_decode instead of explode, but use isset instead of empty since empty('0') == true 2025-12-08 17:46:46 -05:00
Isaac Connor
e8545e0b6f Move setting packetqueue pre_event down to AFTER we load the db value. 2025-11-17 11:58:58 -05:00
Isaac Connor
b634cbe3ca Use auto 2025-10-07 20:13:12 -04:00
Isaac Connor
fbc86631b1 Detect & fix case where new event is instantly greater than min_section_length. Fix logging of logic when using CLOSE_MODE=TIME 2025-09-27 11:48:14 -04:00
Isaac Connor
e0c5ea6c61 Further split amcrest and onvif 2025-09-25 18:06:02 -04:00
Isaac Connor
c622af5e22 Initialize use_Amcrest_API and onvif_event_listener. Split onvif/amcrest init. Can have both now. Should fix crash if amcrest is on 2025-09-25 10:10:10 -04:00
Isaac Connor
94e5eb7efe Fix init order 2025-09-23 10:02:52 -04:00
Isaac Connor
16b95e3673 add missing Go2RTC_Manager(nullptr), 2025-09-22 19:03:50 -04:00