mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-10-02 23:45:08 -04:00
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
242 lines
8.3 KiB
C++
242 lines
8.3 KiB
C++
/*
|
|
* ZoneMinder FFMPEG implementation, $Date$, $Revision$
|
|
* Copyright (C) 2001-2008 Philip Coombes
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
#include "zm_swscale.h"
|
|
|
|
#include "zm_image.h"
|
|
#include "zm_logger.h"
|
|
|
|
SWScale::SWScale() :
|
|
swscale_ctx(nullptr) {
|
|
Debug(4, "SWScale object created");
|
|
}
|
|
|
|
bool SWScale::init() {
|
|
input_avframe = av_frame_ptr{av_frame_alloc()};
|
|
if (!input_avframe) {
|
|
Error("Failed allocating AVFrame for the input");
|
|
return false;
|
|
}
|
|
|
|
output_avframe = av_frame_ptr{av_frame_alloc()};
|
|
if (!output_avframe) {
|
|
Error("Failed allocating AVFrame for the output");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
SWScale::~SWScale() {
|
|
|
|
/* Free up everything */
|
|
if ( swscale_ctx ) {
|
|
sws_freeContext(swscale_ctx);
|
|
swscale_ctx = nullptr;
|
|
}
|
|
|
|
Debug(4, "SWScale object destroyed");
|
|
}
|
|
|
|
int SWScale::Convert(
|
|
AVFrame *in_frame,
|
|
AVFrame *out_frame
|
|
) {
|
|
|
|
AVPixelFormat orig_format = (AVPixelFormat)in_frame->format;
|
|
AVPixelFormat format = fix_deprecated_pix_fmt(orig_format);
|
|
AVPixelFormat orig_out_format = (AVPixelFormat)out_frame->format;
|
|
AVPixelFormat out_format = fix_deprecated_pix_fmt(orig_out_format);
|
|
/* Get the context */
|
|
swscale_ctx = sws_getCachedContext(swscale_ctx,
|
|
in_frame->width, in_frame->height, format,
|
|
out_frame->width, out_frame->height, out_format,
|
|
SWS_FAST_BILINEAR, NULL, NULL, NULL);
|
|
if ( swscale_ctx == NULL ) {
|
|
Error("Failed getting swscale context");
|
|
return -6;
|
|
}
|
|
zm_sws_set_ranges(swscale_ctx, orig_format, orig_out_format);
|
|
/* Do the conversion */
|
|
if (!sws_scale(swscale_ctx,
|
|
in_frame->data, in_frame->linesize, 0, in_frame->height,
|
|
out_frame->data, out_frame->linesize)) {
|
|
Error("swscale conversion failed");
|
|
return -10;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int SWScale::Convert(
|
|
const uint8_t* in_buffer,
|
|
const size_t in_buffer_size,
|
|
uint8_t* out_buffer,
|
|
const size_t out_buffer_size,
|
|
enum _AVPIXELFORMAT in_pf,
|
|
enum _AVPIXELFORMAT out_pf,
|
|
unsigned int width,
|
|
unsigned int height,
|
|
unsigned int new_width,
|
|
unsigned int new_height,
|
|
int in_alignment,
|
|
int out_alignment
|
|
) {
|
|
Debug(1, "Convert: in_buffer %p in_buffer_size %zu out_buffer %p size %zu width %d height %d width %d height %d %d %d align %d/%d",
|
|
in_buffer, in_buffer_size, out_buffer, out_buffer_size, width, height, new_width, new_height,
|
|
in_pf, out_pf, in_alignment, out_alignment);
|
|
/* Parameter checking */
|
|
if (in_buffer == nullptr) {
|
|
Error("NULL Input buffer");
|
|
return -1;
|
|
}
|
|
if (out_buffer == nullptr) {
|
|
Error("NULL output buffer");
|
|
return -1;
|
|
}
|
|
if (!width || !height || !new_height || !new_width) {
|
|
Error("Invalid width or height");
|
|
return -3;
|
|
}
|
|
if (in_alignment <= 0 || out_alignment <= 0) {
|
|
Error("Invalid buffer alignment %d/%d", in_alignment, out_alignment);
|
|
return -4;
|
|
}
|
|
|
|
// Map deprecated YUVJ* formats on BOTH sides. Handing swscale a YUVJ format
|
|
// makes it emit "deprecated pixel format used, make sure you did set range
|
|
// correctly" on every context creation — that is what floods the apache
|
|
// error log from nph-zms when montage/console scale full-range h264 streams.
|
|
// The J and non-J variants share an identical memory layout, so the buffer
|
|
// size/fill maths below is unaffected; only the range flags matter, and
|
|
// zm_sws_set_ranges() restores those.
|
|
const enum _AVPIXELFORMAT orig_in_pf = in_pf;
|
|
const enum _AVPIXELFORMAT orig_out_pf = out_pf;
|
|
in_pf = fix_deprecated_pix_fmt(in_pf);
|
|
out_pf = fix_deprecated_pix_fmt(out_pf);
|
|
|
|
/* Warn if the input or output pixelformat is not supported */
|
|
if (!sws_isSupportedInput(in_pf)) {
|
|
Warning("swscale does not support the input format: %c%c%c%c",
|
|
(in_pf)&0xff,((in_pf>>8)&0xff),((in_pf>>16)&0xff),((in_pf>>24)&0xff));
|
|
}
|
|
if (!sws_isSupportedOutput(out_pf)) {
|
|
Warning("swscale does not support the output format: %c%c%c%c",
|
|
(out_pf)&0xff,((out_pf>>8)&0xff),((out_pf>>16)&0xff),((out_pf>>24)&0xff));
|
|
}
|
|
|
|
/* Check the buffer sizes against the layout each alignment implies */
|
|
size_t needed_insize = GetBufferSize(in_pf, width, height, in_alignment);
|
|
if (needed_insize > in_buffer_size) {
|
|
Warning(
|
|
"The input buffer size does not match the expected size for the input format. Required: %zu for %dx%d %d align %d Available: %zu",
|
|
needed_insize,
|
|
width,
|
|
height,
|
|
in_pf,
|
|
in_alignment,
|
|
in_buffer_size);
|
|
}
|
|
size_t needed_outsize = GetBufferSize(out_pf, new_width, new_height, out_alignment);
|
|
if (needed_outsize > out_buffer_size) {
|
|
Error("The output buffer is undersized for the output format. Required: %zu Available: %zu",
|
|
needed_outsize,
|
|
out_buffer_size);
|
|
return -5;
|
|
}
|
|
|
|
/* Get the context */
|
|
swscale_ctx = sws_getCachedContext(swscale_ctx,
|
|
width, height, in_pf,
|
|
new_width, new_height, out_pf,
|
|
SWS_FAST_BILINEAR, nullptr, nullptr, nullptr);
|
|
if (swscale_ctx == nullptr) {
|
|
Error("Failed getting swscale context");
|
|
return -6;
|
|
}
|
|
zm_sws_set_ranges(swscale_ctx, orig_in_pf, orig_out_pf);
|
|
|
|
/* Fill in the buffers. The alignments describe how the caller's buffers
|
|
* are actually laid out — they are facts about the buffers, not tuning
|
|
* knobs. Guessing them from the dimensions (the old `width % 32 ? 1 : 32`
|
|
* heuristic) misread every align-32 Image whose width was not a multiple
|
|
* of 32, e.g. the 720- or 2160-wide output of a rotated monitor. */
|
|
if (av_image_fill_arrays(input_avframe->data, input_avframe->linesize,
|
|
(uint8_t*) in_buffer, in_pf, width, height, in_alignment) <= 0) {
|
|
Error("Failed filling input frame with input buffer");
|
|
return -7;
|
|
}
|
|
if (av_image_fill_arrays(output_avframe->data, output_avframe->linesize,
|
|
out_buffer, out_pf, new_width, new_height, out_alignment) <= 0) {
|
|
Error("Failed filling output frame with output buffer");
|
|
return -8;
|
|
}
|
|
|
|
/* Do the conversion */
|
|
if ( !sws_scale(swscale_ctx,
|
|
input_avframe->data, input_avframe->linesize,
|
|
0, height,
|
|
output_avframe->data, output_avframe->linesize) ) {
|
|
Error("swscale conversion failed");
|
|
return -10;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int SWScale::Convert(
|
|
const uint8_t* in_buffer,
|
|
const size_t in_buffer_size,
|
|
uint8_t* out_buffer,
|
|
const size_t out_buffer_size,
|
|
enum _AVPIXELFORMAT in_pf,
|
|
enum _AVPIXELFORMAT out_pf,
|
|
unsigned int width,
|
|
unsigned int height,
|
|
int in_alignment,
|
|
int out_alignment) {
|
|
return Convert(in_buffer, in_buffer_size, out_buffer, out_buffer_size, in_pf, out_pf, width, height, width, height, in_alignment, out_alignment);
|
|
}
|
|
|
|
int SWScale::Convert(
|
|
const Image* img,
|
|
uint8_t* out_buffer,
|
|
const size_t out_buffer_size,
|
|
enum _AVPIXELFORMAT in_pf,
|
|
enum _AVPIXELFORMAT out_pf,
|
|
unsigned int width,
|
|
unsigned int height,
|
|
int out_alignment) {
|
|
if ( img->Width() != width ) {
|
|
Error("Source image width differs. Source: %d Output: %d", img->Width(), width);
|
|
return -12;
|
|
}
|
|
|
|
if ( img->Height() != height ) {
|
|
Error("Source image height differs. Source: %d Output: %d", img->Height(), height);
|
|
return -13;
|
|
}
|
|
|
|
// Image buffers are always laid out by av_image_* with align=32
|
|
return Convert(img->Buffer(), img->Size(), out_buffer, out_buffer_size, in_pf, out_pf, width, height, 32, out_alignment);
|
|
}
|
|
|
|
size_t SWScale::GetBufferSize(enum _AVPIXELFORMAT pf, unsigned int width, unsigned int height, int alignment) {
|
|
return av_image_get_buffer_size(pf, width, height, alignment);
|
|
}
|