mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-10-08 02:12:17 -04:00
SWScale::Convert chose av_image_fill_arrays alignment by heuristic (width % 32 ? 1 : 32) for both buffers. Image buffers are always laid out align-32, so any width not divisible by 32 made Convert read luma rows 16 bytes short and chroma planes from packed offsets: diagonal shear plus garbage chroma. Rotating a monitor is what produces such widths (1280x720 ROTATE_270 -> 720 wide, 3840x2160 ROTATE_90 -> 2160 wide, both % 32 == 16), so every scaled view and every re-encode of a rotated monitor was corrupted while unrotated monitors (1280/2688/3840 all % 32 == 0) were untouched. The rotate/flip segfault fix exposed this: before it, rotated planar frames crashed zmc before reaching Scale. Alignment is a fact about how a buffer was laid out, not something derivable from dimensions. Convert now takes explicit in/out alignment: Image::Scale passes 32/32, the videostore encode path mirrors get_out_frame's allocation choice, libvnc passes 1 (packed VNC framebuffer) and 32 (Image WriteBuffer). Remove the unused SetDefaults/ConvertDefaults API rather than threading alignments through dead code. Tests: new Scale regression case on a 720x1280 YUV420P image (column-banded luma, uniform chroma) fails before the fix exactly as observed live (sheared rows, V plane reading 0) and passes after. Full suite 84/84. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
227 lines
7.4 KiB
C++
227 lines
7.4 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 format = fix_deprecated_pix_fmt((AVPixelFormat)in_frame->format);
|
|
/* Get the context */
|
|
swscale_ctx = sws_getCachedContext(swscale_ctx,
|
|
in_frame->width, in_frame->height, format,
|
|
out_frame->width, out_frame->height, (AVPixelFormat)out_frame->format,
|
|
SWS_FAST_BILINEAR, NULL, NULL, NULL);
|
|
if ( swscale_ctx == NULL ) {
|
|
Error("Failed getting swscale context");
|
|
return -6;
|
|
}
|
|
/* 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;
|
|
}
|
|
|
|
in_pf = fix_deprecated_pix_fmt(in_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;
|
|
}
|
|
|
|
/* 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);
|
|
}
|