A monitor whose source signals full range (Dahua h264) decodes to
AV_PIX_FMT_YUVJ420P, and Monitor passes that format through to shared memory
unchanged. When zms scales such a frame for montage or console thumbnails,
SWScale::Convert mapped only the input format through fix_deprecated_pix_fmt()
and handed the still-deprecated YUVJ format to swscale as the destination, so
libswscale logged
deprecated pixel format used, make sure you did set range correctly
for every context it built. Watch view at 100% scale never hit it because
Image::Scale returns early when the dimensions already match.
Map deprecated formats on both sides in SWScale::Convert (both the buffer and
the AVFrame overload), in Image::Assign(AVFrame*), in
Monitor::setupConvertContext and in the LocalCamera conversion context.
Mapping the destination YUVJ format to its non-J equivalent makes swscale
default that side to limited range, which would compress full-range output.
Replace zm_sws_set_input_range() with zm_sws_set_ranges(), which takes the
original pre-fix formats for both sides and sets srcRange/dstRange accordingly.
Image::Assign(AVFrame*) now compares the mapped source and destination formats,
so a YUVJ420P frame into a YUVJ420P image takes the av_image_copy fast path
instead of running through swscale.
Image::Scale built a fresh SWScale, and therefore a fresh sws context, on every
call. That is a full sws_init_context per scaled frame per stream, and it is
what turned the warning into a per-frame flood rather than a one-off. Reuse a
thread_local SWScale; sws_getCachedContext re-inits itself when the geometry
changes.
Tests: zm_swscale_range.cpp installs an av_log callback and asserts that
SWScale::Convert with a YUVJ destination and Image::Scale on a YUVJ420P image
emit no deprecated-format message, plus a luma check that the range handling
still survives the mapping.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NDhTBPj9xEaT52pRmAufaP
- 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>
Two issues flagged by Copilot review on the AVPixelFormat-migration PR,
plus one build fix that was needed to land them:
1. zm_local_camera.cpp set subpixelorder to BGR for V4L2_PIX_FMT_RGB24
captures. V4L2_PIX_FMT_RGB24 is byte-order R,G,B in memory and is
mapped to AV_PIX_FMT_RGB24 by getFfPixFormatFromV4lPalette earlier
in the same file, so the matching ZM subpixel order is RGB. Setting
BGR meant red and blue were swapped in the captured image whenever
a V4L2 camera was configured with the RGB24 palette. Long-standing
bug — preserved unchanged through the AVPixelFormat migration —
now fixed to ZM_SUBPIX_ORDER_RGB.
2. Image::AssignDirect(const AVFrame*) called zm_colours_from_pixformat
without checking the return value, leaving colours/subpixelorder at
their previous values for any unsupported AVPixelFormat. Wrap the
call and on failure put the Image into an explicit invalid state
(AV_PIX_FMT_NONE plus zeroed colours/subpixelorder) so the
inconsistency surfaces immediately instead of producing wrong-format
reads downstream.
3. Drop the u_buffer = ... / v_buffer = ... assignments inside
Image::Assign()'s identity-copy path. Those members exist on the
ai_server lineage but not on master, so the PR branch did not
compile against master as-is. av_image_copy reads the planes
directly out of temp_frame->data, so the assignments were not
load-bearing — they look like leftover state-tracking that didn't
survive the upstreaming. Comment notes why the lines were removed.
Tests pass: 76 cases, 778 assertions.
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>
Some V4L2 drivers return ENOTTY (rather than EINVAL) when VIDIOC_G_JPEGCOMP
is unsupported. Treat both as "feature unavailable" and log at debug level
instead of warning, and include the errno string for clarity.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move MJPEG/JPEG ahead of YUYV/UYVY in the preferred format arrays
for all color spaces. MJPEG uses far less USB bandwidth than raw YUV
while the decode cost is minimal (sw MJPEG decoder).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
AutoSelectFormat() opens the video device to enumerate formats, but
non-capture processes like zms (QUERY mode) should never need to access
the device. Guard the call with the existing capture flag so zms no
longer fails with "No such file or directory" errors when running under
Apache with systemd PrivateDevices=yes.
Also downgrade the fallback message from Error to Warning since YUYV
fallback is a recoverable condition, and add a hint about PrivateDevices
to the ENOENT error in AutoSelectFormat for cases where capture mode
legitimately can't open the device.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Switch all owning AVFrame* variables to av_frame_ptr for
automatic cleanup.
Use AVBufferRef to store frame data in AVFrame where appropriate
so that it can be freed automatically with it's AVFrame.
Handle allocation errors.
Remove all uses of deprecated av_init_packet() and switch any stack
based AVPackets to unique_ptrs allocated with av_packet_alloc().
Ensure that all code paths call av_packet_unref() after use to reset
before next usage.
FFmpeg is an integral component of ZM. Promote the appropriate libraries to required dependencies.
This reduces the possible build configurations greatly and thus maintenance burden.