Repository navigation
fix: ONVIF events record 0 alarm frames for momentary alarms (DetectedSound) refs #5301 - #5305
Conversation
…inder#5301 The ONVIF alarm was a plain atomic bool that the poll thread set and the analysis thread sampled once per frame. A momentary alarm -- a DetectedSound pulse the camera reports active then inactive between two analysis frames -- was raised and cleared on the raw level before any frame sampled it, so the recorded event had no alarm frames. A motion alarm stays active for seconds and so was always sampled, which is why MotionAlarm never showed the problem. Replace the bool with an AlarmLatch: raise() marks the level active and that an activation is waiting to be seen, clear() drops the level, and the analysis thread calls observeAlarm() once it has sampled the alarm. isAlarmed() stays true from a raise until the level is down and the activation has been observed, so a pulse is reported on at least one frame however short it was. A still-active alarm is unaffected. The latch is released only on a frame that scored, so a pulse arriving just after the isAlarmed() check is kept for the next frame rather than lost. The activation guards in WaitForMessage() now test the raw level (alarm_.active()) instead of isAlarmed(), so a new topic still raises the level while an earlier pulse's latch is still pending. Add Catch2 coverage for AlarmLatch (tests/zm_onvif_alarm_latch.cpp), testable without a gSOAP context like the other ONVIF free-function tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Thank you for the prompt fix.
I think everything is fine, but why do we still occasionally see 0 alarm frames? According to the new algorithm, that shouldn't be happening. |
|
Thanks for the careful testing — that breakdown (most events 20–50 frames, ~1/10 at 0, ~1/20 at 1) is exactly the shape I needed to see. Here's where I've got to tracing it in code:
So the residual 0-cases aren't "the frame wasn't scored" — they're about which event the scored frame lands in. With a continuous/Mocord recording and event-close-on-alarm, when the alarm begins ZM closes the running continuous event and opens a fresh one. For a sub-second pulse the scored frame(s) land in the closing event, and the new "alarm" event opens just after them with none — so the event you're looking at shows 0, while its immediate neighbour holds the alarm frame. The 1-frame cases are the same boundary landing a single frame on the other side. Two things would confirm this quickly:
If that confirms the boundary theory, the fix is to hold the ONVIF alarm "on" for a small minimum number of frames (so a momentary pulse behaves like a short real alarm and its scored frames continue into the new event after the split), rather than releasing it after a single observation as this PR does. I can add that on top of this change for you to retest. |
… refs ZoneMinder#5301 The latch was released in Analyse() at the isAlarmed() sample, before the packet was committed. Two paths then spent a momentary DetectedSound pulse without recording it: - A video packet not yet decoded makes Analyse() return at the decode gate before packet->score is set, and the same packet is retried. The sample -- and the release -- ran on every retry, so the pulse was gone by the time the packet was decoded and scored, and the event recorded no alarm frame. This is why raising AlarmFrameCount or the earlier hold did not help: the retries of one undecoded packet consumed the latch. - An audio packet (these cameras alarm on AudioAnalytics/DetectedSound, so audio packets are present) could sample and release the latch, but alarm_frames counts video frames, so the pulse was again recorded on no counted frame. Defer the release to the point where a video packet is committed with its score, so the latch is spent only on a frame the event actually writes. Also keep the topic/value of the last activation and report it from SetNoteSet() when the alarms map has already been cleared, so a momentary pulse's event note still names the cause (e.g. AudioAnalytics/Audio/ DetectedSound/true) instead of a bare "ONVIF". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
@IgorA100 thanks — your details pinned it, and it's a different cause than either of my earlier guesses. I've closed #5310 (the hold) and pushed the real fix here. What was happening, in
This also explains your Go2RTC observation: watching live via go2rtc widens the decode-wait window, so with AlarmFrameCount=1 nearly every pulse was consumed before a video frame committed and no event opened at all. It should behave now — please retest that path too. The fix releases the latch only when a video frame is actually committed with its score. I also made the event note keep the triggering topic for a momentary pulse, so the low-alarm-frame events should now show Updated branch is ready to build when you have a chance. |
|
@connortechnology |
|
I installed this version for testing. |
|
Everything is working great so far, with one exception: if you open the stream via Go2RTC on the "Watch" page, event recording does not take place. |
|
Oh, I’m completely stumped... |
|
What happens if you use go2rtc but with the rtsp re-streamer? Claude thinks the Dahua cameras have a concurrent access limitation and the second stream is killing them. |
|
@connortechnology |
Fixes ONVIF events (notably
AudioAnalytics/DetectedSound) intermittently recording 0 alarm frames (refs #5301).Two commits
1. Latch the ONVIF alarm. The alarm was a level-sampled atomic bool. A momentary pulse (DetectedSound goes active then inactive between two analysis frames) was raised and cleared before any frame sampled it, so the event recorded no alarm frames. A motion alarm persists for seconds and so was always sampled. The latch holds a pulse until a frame has observed it.
2. Release the latch only when a video frame records it. The latch was released at the
isAlarmed()sample inMonitor::Analyse(), before the packet was committed. A not-yet-decoded video packet makesAnalyse()return at the decode gate and retry the same packet, so the release ran on every retry and spent a momentary pulse before any decoded frame scored it — which is why raising AlarmFrameCount and the earlier minimum-hold attempt (#5310, now closed) didn't help. An audio packet could also consume it, butalarm_framescounts video frames. The release is now deferred to the point a video packet is committed with its score.Also keeps the last activation's topic/value so a momentary pulse's event note names the cause (e.g.
AudioAnalytics/Audio/DetectedSound/true) instead of a bareONVIF.This also addresses the reported "no recording while watching via Go2RTC" case: go2rtc widens the decode-wait window, so with AlarmFrameCount=1 nearly every pulse was consumed before a video frame committed.
Testing
tests/zm_onvif_alarm_latch.cppcovers the latch. zmc builds clean; all[onvif]cases pass. TheAnalyse()release path needs a live packet pipeline, so it's verified by build + field retest rather than a unit test.🤖 Generated with Claude Code