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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>
- 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>
- 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>