Commit Graph
361 Commits
Author SHA1 Message Date
Isaac Connor 706d9324f2 feat: allow newlines to separate entries in the monitor Options field
The Options field was a single line text input, so a list of options had to
be typed as one comma separated run. Make it a textarea, sized to the number
of entries it already holds, so each option can go on its own line.

Nothing needs to parse this: both consumers already take a set of separator
characters, they were just never given the newlines.
av_dict_parse_string() (Ffmpeg) and Split() (Libvlc) both skip empty
entries, so a blank line, crlf, or a trailing newline all work, and comma
separated values keep working unchanged.

The separator set is a named constant rather than a literal at each call
site so the tests exercise the value the cameras actually pass - with a
literal they would keep passing if a call site were reverted. Verified by
setting it back to "," which fails both test cases.
2026-08-08 11:56:43 -04:00
Антон a63229e07b fix(decoder): retry hwaccel on reconnect instead of latching to software
use_hwaccel was set to false on the first failure and never restored, so
the decision survived every later PrimeCapture(). A camera that came up
while the GPU was still resetting, or on a host where zmc won the race
against the graphics driver, stayed on software decoding until someone
restarted the process - at roughly one extra CPU core per stream.

Close() already tears down hw_device_ctx and is the entry point of every
reconnect, so reset the flag there and let the next open try again. A
GPU that is genuinely gone simply fails again and now says so.

refs #4984
2026-07-26 17:58:41 +03:00
Антон 78fa66caa8 fix(decoder): warn when explicitly requested hwaccel falls back to software
The fallback path logged at Debug(1), which a default install does not
write, so a monitor that lost hardware decoding looked identical to one
that never asked for it. The message is also emitted once, at capture
start, so the only remaining symptom was a doubled load average.

Restore the visibility that existed before the auto-detection work: if
DecoderHWAccelName names specific hwaccels and none of them can be used,
that is a warning. "auto" keeps logging at Debug, since probing and
finding nothing is the expected outcome there rather than a fault.

refs #4984
2026-07-26 17:58:26 +03:00
Isaac ConnorandClaude Opus 4.8 74bf0637f0 fix: restore generic decoder fallback for ffmpeg cameras
Extract the "use whatever decoder ffmpeg provides" logic from
FFmpeg_Input into a shared open_fallback_decoder() and call it from
FfmpegCamera, which had lost the fallback: its generic path only ran
inside the preferred-codec loop, so an empty get_decoder_data() result
failed outright instead of trying the codec's default decoder.

The dec_codecs table stays a preference list; a codec present in the
ffmpeg build but absent from the table (e.g. libopenh264 for H264) now
decodes via the fallback instead of requiring a hard-coded entry.

Add tests/zm_ffmpeg_fallback.cpp covering the fallback with a codec not
in dec_codecs (mpeg2video), the optional codec_out argument, and the
no-decoder case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 11:26:03 -04:00
АнтонandClaude Opus 4.8 572af3449d feat(decoder): DecoderHWAccelName accepts a comma-separated priority list
Follow-up to #4984 as suggested by @connortechnology: besides "auto"
(probe everything) and a single device-type name, DecoderHWAccelName now
accepts a comma-separated priority list such as "cuda,vaapi" - each
name is tried in order and the first type the decoder supports and whose
device can be created wins, with the existing transparent software
fallback when none do. A single name is the one-element case and behaves
exactly as before. Unknown names are skipped with a Warning. Whitespace
around commas is ignored.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 18:49:39 +03:00
Anton VinogradovandClaude Opus 4.8 d9784c73de feat(decoder): "auto" hardware-decode detection with software fallback
DecoderHWAccelName previously required naming a specific device type and
only fell back to software if creating that one device failed. This adds
an "auto" value that iterates over every hwaccel type libav exposes and,
for each, checks that the decoder advertises a matching hw config and
that the device can be created — using the first that works and falling
back to software when none do. Explicit names (e.g. "vaapi", "cuda")
behave as before. Makes hardware decoding safe to enable out of the box
across unknown hardware.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 03:46:56 +03:00
Isaac ConnorandClaude Opus 4.8 53afbafb84 feat: add realtime pacing option for ffmpeg file sources
Add a "realtime=1" (alias "re=1") ffmpeg input option, modeled on the
existing "loop" option and equivalent to ffmpeg's -re flag. When set on a
file source, FfmpegCamera throttles packet delivery to the rate implied by
the stream timestamps instead of reading the file as fast as possible.

The option is parsed out of the ffmpeg Options string in OpenFfmpeg and
consumed so it is not passed to the demuxer or decoder. Capture() anchors
wall-clock time to the first delivered packet and sleeps before each
subsequent packet so it is not delivered ahead of schedule. Pacing uses dts
(monotonic in read order) with a pts fallback, runs after the existing
drop/jump filters, and re-anchors on backward jumps or gaps beyond a 10s cap
to avoid stalling on a discontinuity. Works alongside loop, whose
offset-adjusted timestamps stay monotonic across restarts.

The pacing decision is factored into a pure ComputeRealtimePace() function
and unit-tested in tests/zm_ffmpeg_camera.cpp (8 cases covering full/partial
interval waits, behind-schedule, on-schedule, backward jump, over-cap, and
the cap boundary).

Ported from the ai_server branch onto master's OpenFfmpeg structure.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 10:08:58 -04:00
Isaac ConnorandClaude Opus 4.8 c9be205846 feat: loop file-based ffmpeg input on EOF via loop=1 option
When an ffmpeg monitor reads a seekable file source (e.g. file:///x.mp4)
and reaches the end, it previously failed and reconnected. Add a loop
mode, enabled with loop=1 in the monitor Options, that seeks back to the
start and continues instead.

Packet pts/dts are shifted by a per-stream absolute offset recomputed on
each loop (last emitted dts + frame duration - stream start_time) so
timestamps stay monotonically increasing for analysis and recording. The
loop option is consumed before passing options to ffmpeg (both the demuxer
in OpenFfmpeg and the decoder), and non-seekable inputs fall through to the
normal reconnect path.

Ported from the ai_server branch onto master's OpenFfmpeg structure.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 10:08:58 -04:00
Isaac ConnorandClaude Opus 4.7 9687faac67 refactor: remove dead FfmpegCamera::imagePixFormat field
The field was assigned in three branches of the constructor (always to
the same value as Camera::pixelFormat) and never read anywhere. Unlike
LocalCamera, FfmpegCamera doesn't run a sws_scale step at the Camera
layer — libavcodec hands frames to the pipeline directly — so there's
no separate capture-vs-image format distinction to track.

Drop the redundant assignments and the member declaration. The
canonical camera-side format is now exclusively Camera::pixelFormat
(via Camera::PixelFormat()).

refs #4788

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-06 12:29:35 -04:00
Isaac ConnorandClaude Opus 4.7 ec8d48aa51 fix: switch remaining av_get_pix_fmt_name calls in zm_ffmpeg_camera.cpp to zm_ wrapper
Two Debug log sites in the hwaccel selection loop still called
av_get_pix_fmt_name directly. AV_PIX_FMT_NONE is a real possibility for
config->pix_fmt during the loop (the search may hit the sentinel) and
for hw_pix_fmt before find_fmt_by_hw_type returns a match. Route both
through zm_get_pix_fmt_name so a nullptr return can't be passed to %s.

refs #4788

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-31 09:31:34 -04:00
Isaac ConnorandClaude Opus 4.7 6765962737 fix: nullptr-safe pixfmt name; route GetImage/getSnapshot through ReadShmFrame (PR #4788)
- Added zm_get_pix_fmt_name() in zm_pixformat.h: av_get_pix_fmt_name()
  returns nullptr for unknown formats (including AV_PIX_FMT_NONE), and
  passing nullptr to %s is undefined and can crash. Wraps with an
  "unknown" fallback. Replaced the five raw uses flagged by review
  (RemoteCameraRtsp/FfmpegCamera/VncCamera/LocalCamera constructors and
  MonitorStream::sendFrame's Debug log) with the wrapper. Each can hit
  AV_PIX_FMT_NONE before the first frame or via an unexpected
  (colours, subpixelorder) pair.
- Monitor::GetImage and Monitor::getSnapshot now route the slot read
  through ReadShmFrame so the per-slot AVPixelFormat zmc recorded is
  adopted on image_buffer[index] before its bytes are interpreted.
  Without this, the JPEG encode in GetImage and the ZMPacket returned
  by getSnapshot would interpret SHM bytes using the placeholder format
  set at attach time and produce garbled output when the slot's actual
  format differs (the contract previously relied on callers calling
  ReadShmFrame themselves). getSnapshot/GetTimestamp drop const since
  ReadShmFrame mutates image_buffer[index]; callers in this codebase
  already use a non-const Monitor pointer.
- Monitor::connect: updated the misleading "+64 bytes reserved" comment
  to match the actual reservation of 63 + (alignof(AVPixelFormat) - 1).

refs #4788

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-31 09:22:09 -04:00
Isaac ConnorandClaude Opus 4.7 e5446d3af5 fix: report AVPixelFormat in Panic messages for actionable diagnosis (PR #4788)
Six format-dispatch fallbacks were still printing only the legacy
`colours` value, but the dispatch in each spot is now keyed off
pixelFormat/imagePixFormat. With the GRAY8/YUV420P alias collision
(both colours=1) the legacy value frequently doesn't identify the true
format, so a "Unexpected colours: 1" log on a misroute is useless.

Updated each Panic to include the AVPixelFormat enum value and its
human-readable name (via av_get_pix_fmt_name), plus the legacy
(colours, subpixelorder) for context:
- RemoteCameraRtsp constructor
- FfmpegCamera constructor
- VncCamera constructor
- LocalCamera conversion-selection branch
- VideoStream::SetupCodec mpeg helper
- Image::Delta unknown-format fallback

refs #4788

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 23:03:33 -04:00
Isaac Connor 7b21371083 Reapply "fix: replace ZM_COLOUR system with AVPixelFormat for format dispatch"
This reverts commit 60b6b37c60.
2026-05-03 12:24:43 -04:00
Isaac Connor 60b6b37c60 Revert "fix: replace ZM_COLOUR system with AVPixelFormat for format dispatch" 2026-05-02 18:26:48 -04:00
Isaac ConnorandClaude Opus 4.6 b416aa5293 fix: replace ZM_COLOUR_*/ZM_SUBPIX_ORDER_* with AVPixelFormat for format dispatch
ZM_COLOUR_GRAY8, ZM_COLOUR_YUV420P, and ZM_COLOUR_YUVJ420P were all
defined to 1, making format identification via colours ambiguous.
LocalCamera misidentified YUV420P as GRAY8, causing V4L2 MJPEG cameras
to decode to grayscale via expensive sws_scale conversion.

Replace the legacy ZM_COLOUR_*/ZM_SUBPIX_ORDER_* integer pair with
AVPixelFormat as the single source of truth for pixel format dispatch:

- Add src/zm_pixformat.h with central format helpers:
  zm_pixformat_from_colours, zm_colours_from_pixformat,
  zm_bytes_per_pixel, zm_db_colours_to_pixformat, zm_is_rgb32,
  zm_is_rgb24, zm_is_yuv420
- Add AVPixelFormat pixelFormat member + PixelFormat() accessor to Camera
- Add PixFormat() accessor to Image, delegate AVPixFormat methods
  to shared helpers
- Migrate all ~100 format dispatch comparisons in zm_image.cpp,
  zm_local_camera.cpp, zm_ffmpeg_camera.cpp, zm_remote_camera_rtsp.cpp,
  zm_libvlc_camera.cpp, zm_libvnc_camera.cpp, zm_monitor.cpp,
  zm_mpeg.cpp from colours/subpixelorder checks to imagePixFormat/
  AVPixelFormat checks
- Deprecate GetFFMPEGPixelFormat, delegate to zm_pixformat_from_colours
- Fix DeColourise bug: imagePixFormat was not updated to GRAY8
- Deprecate ZM_COLOUR_* and ZM_SUBPIX_ORDER_* constants in zm_rgb.h
- Add deprecation notice on Monitor.Colours web UI dropdown
- Add 13 Catch2 test cases (105 assertions) for format mapping helpers

refs #4735

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 11:42:06 -04:00
Isaac ConnorandClaude Opus 4.6 aa5e493694 perf: default ffmpeg decoder thread_count to 2
libavcodec's avcodec_alloc_context3 leaves thread_count at 1, so
software 1080p H.264 decoding hits ~60ms/frame and saturates a core
per camera.  Default to 2 frame-threads, which roughly halves per-
frame decode time without oversubscribing systems with many cameras.

User can still override via thread_count in monitor Options (0 = let
ffmpeg auto-detect based on CPU count).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 15:58:44 -04:00
Isaac ConnorandClaude Opus 4.6 cf27243055 fix: add DTS backward jump detection in ffmpeg camera capture
The existing PTS backward jump check in FfmpegCamera::Capture() does not
catch cases where DTS jumps back significantly (e.g. stream restarts,
encoder resets) while PTS may remain unaffected. This causes the
VideoStore to spend minutes forcing DTS monotonicity on every packet via
the write_packet fixup path, flooding logs with warnings.

Add per-stream DTS tracking (mLastVideoDTS/mLastAudioDTS) and a backward
jump check mirroring the existing PTS check: if DTS jumps back more than
10 seconds, increment error_count and after 6 occurrences return -1 to
trigger capture reconnection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 09:52:00 -04:00
Isaac ConnorandClaude Opus 4.6 a17aea1261 refactor: use CxxUrl for URL auth injection in FFmpegCamera
Replace fragile string manipulation (hardcoded substr offsets) with
CxxUrl's Url parser for injecting mUser/mPass into mPath and
mSecondPath. Only applies credentials when the URL has no existing
auth info, preserving inline credentials on the secondary path.
Non-URL paths (e.g. v4l2 devices) are handled gracefully via
try/catch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 18:16:55 -05:00
Isaac ConnorandClaude Opus 4.6 b62285b90b fix: null-check mAudioStream before dereferencing in FfmpegCamera::Capture
When mSecondFormatContext is set but no audio stream was found during
PrimeCapture, mAudioStream is null. Dereferencing mAudioStream->time_base
causes a segfault (SIGSEGV at fault address 0x20). Add mAudioStream to
the condition check, consistent with the existing null guard on line 172.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 15:57:22 -05:00
Isaac ConnorandClaude Opus 4.5 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 c012f55962 Set reference frames and turn off multi-threading when doing hwaccel 2026-02-04 21:35:06 -05:00
Isaac ConnorandClaude Opus 4.5 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 ConnorandClaude Opus 4.5 65bbaa4ab8 fix: add null check and fix incomplete error message in FFmpegCamera
- Add null check after avformat_alloc_context() to prevent null pointer
  dereference if allocation fails
- Fix incomplete error message that was missing the path parameter

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 11:00:38 -05:00
Isaac Connor 2eebda8c64 Cleanup hw_pix_fmt and get_hw_format 2026-01-26 14:40:10 -05:00
Isaac Connor 12ffa835c7 Merge in code to iterate through available codecs and try each one. 2026-01-26 13:17:25 -05:00
Isaac Connor 5c5cce12fa Fix spacing 2025-07-26 11:52:21 -04:00
Isaac Connor fbd2dd6c58 Allow setting of decoder threads using opts 2025-06-21 21:37:10 -04:00
Isaac Connor 66adcab8ea Merge pull request #4131 from SteveGilvarry/ffmpeg7-avcodec_close
Removing avcodec_close, avcodec_free_context is replacement and been …
2025-02-12 15:07:56 -05:00
Isaac Connor f2e8741984 Choose altnernate video stream if it matches the specified resolution. Don't return packets that don't match our stream. discard unmatched streams. 2025-01-24 12:37:36 -05:00
Isaac Connor 10b3619a66 clear probesize so ffmpeg doesn't complain abou it 2024-10-28 13:59:21 -04:00
Isaac Connor e600472625 If using WallClockTimestamps, don't sweat a big jump in time. 2024-10-22 18:22:50 -04:00
Isaac Connor e7d4fa334d Initialize lastPTS and only set lastAudioPTS if the stream is audio. 2024-09-25 10:53:47 -04:00
Isaac Connor 07d221569d Let higher ups deal with non-video non-audio packets. Might fix a memleak 2024-09-24 19:30:30 -04:00
Steve Gilvarry 88227105b8 Removing avcodec_close, avcodec_free_context is replacement and been around since 2014, so should be no reason to version test. 2024-09-14 11:06:55 +10:00
Isaac Connor ae34457031 Take importance into count when logging failure to read packet.
Clear reorder_queue_size from opts as it only applies to avformat
2024-07-24 12:07:56 -04:00
Isaac Connor 63a2ed4b06 Take Importance into accoutn when logging Unable to read from stream 2024-07-08 08:39:41 -04:00
Isaac Connor aa1aee230f If camera time jumps back more than 10 seconds, fail the capture. 2024-07-03 08:54:46 -04:00
Isaac Connor 90b5b1f154 Reduce time jump amount to 20seconds. We have many cameras that jump more than 20, but less than 40 and so the dts are accepted by reordering. Better to restart capture I think. 2024-04-07 11:30:22 -04:00
Aaron Kling c4683d90a9 Format code using astyle google format
Commands used:
astyle --style=google --indent=spaces=2 --keep-one-line-blocks src/*.cpp
astyle --style=google --indent=spaces=2 --keep-one-line-blocks src/*.h
2024-03-26 13:43:58 -05:00
Isaac Connor 26231c4579 Return 0 if packet is not for video or audio stream 2024-03-02 11:19:27 -05:00
Isaac Connor e4a2f9879e Don't assume video if not audio, could be data 2024-03-02 11:00:03 -05:00
Isaac Connor bb4858103d Increase time jump to 40 seconds because Avigilon cameras seem to do this a lot 2024-03-02 09:58:22 -05:00
Isaac Connor 3eede7228c Use Importance when logging about time jumping back too far 2024-02-27 17:39:52 -05:00
Isaac Connor ff41c1612f Use time instead of pts for legibility in warning about jumping back in time. Don't break capture until we have at least 5 of these errors. 2024-02-19 09:52:10 -05:00
Isaac Connor de7b915291 Increase the time jump to 20 seconds. 2024-02-06 15:59:08 -05:00
Isaac Connor b869a6e5a9 Must always CLose in PrimeCapture because each OpenFfmpeg allocates structures. 2024-01-29 10:10:56 -05:00
Isaac Connor 21743f71a1 Reset lastPTS on Close 2024-01-27 10:27:39 -05:00
Isaac Connor 543d3bdd98 Fix lastPTS assignment when not using a second input. Use a 10 sec max pts gap before fail to capture 2024-01-26 19:47:39 -05:00
Isaac Connor 4888019f1b Add detection for significant jump back in time in incoming packets 2024-01-26 18:16:24 -05:00
Isaac Connor db135cba46 Add isPrimed to camera class instead of it just being in ffmpeg_camera. Rename mCanCapture to isPrimed in ffmpeg_camera. 2023-12-06 09:38:38 -05:00