PR #24911 opened by Kyle Swanson (kylophone) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24911 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24911.patch
Signed-off-by: Kyle Swanson <[email protected]> # Summary of changes This is obviously non-normative, but the way prores coefficients are ordered makes it seem like this is sort of thing is by design. Quick benchmark with a 4K 12-bit 4444 source below. **A:** decode at full resolution, then downscale with swscale lanczos. **B:** decode with `-lowres <level>`. | lowres | size | time A | time B | speedup | |---|---|---|---|---| | 1 | 1920x1080 | 2.588 s | 1.492 s | 1.7x | | 2 | 960x540 | 2.592 s | 0.472 s | 5.5x | | 3 | 480x270 | 2.589 s | 0.182 s | 14.2x | For psnr measurements, outputs are scaled to source resolution with swscale bicubic and compared against the full res decode. | lowres | psnr-y (A/B) | psnr-u (A/B) | psnr-b (A/B) | |---|---|---|---| | 1 | 54.70 / 54.68 | 61.50 / 61.63 | 64.72 / 64.98 | | 2 | 50.13 / 49.88 | 56.06 / 55.94 | 59.00 / 58.90 | | 3 | 45.11 / 44.81 | 52.93 / 52.74 | 56.46 / 56.30 | >From 76cf1e34122458c334c200e81b15cd44e6d246f4 Mon Sep 17 00:00:00 2001 From: Kyle Swanson <[email protected]> Date: Tue, 6 Oct 2026 22:08:05 +0000 Subject: [PATCH] avcodec/proresdec: add lowres decoding support Signed-off-by: Kyle Swanson <[email protected]> --- libavcodec/prores_raw.c | 2 +- libavcodec/proresdec.c | 85 ++++++++++++++++++-------- libavcodec/proresdsp.c | 120 ++++++++++++++++++++++++++++++++++--- libavcodec/proresdsp.h | 8 ++- tests/checkasm/proresdsp.c | 2 +- 5 files changed, 180 insertions(+), 37 deletions(-) diff --git a/libavcodec/prores_raw.c b/libavcodec/prores_raw.c index 36c3879f73..ece0d491c8 100644 --- a/libavcodec/prores_raw.c +++ b/libavcodec/prores_raw.c @@ -60,7 +60,7 @@ static av_cold int decode_init(AVCodecContext *avctx) ff_blockdsp_init(&s->bdsp); /* Coefficients and the iDCT are 12-bit, the linearization curve then * expands the result to the 16-bit linear output range. */ - ff_proresdsp_init(&s->prodsp, 12); + ff_proresdsp_init(&s->prodsp, 12, 0); ff_permute_scantable(s->scan, ff_prores_interlaced_scan, s->prodsp.idct_permutation); diff --git a/libavcodec/proresdec.c b/libavcodec/proresdec.c index cfcc866885..6070a68317 100644 --- a/libavcodec/proresdec.c +++ b/libavcodec/proresdec.c @@ -171,7 +171,7 @@ static av_cold int decode_init(AVCodecContext *avctx) avctx->bits_per_raw_sample); ff_blockdsp_init(&ctx->bdsp); - ff_proresdsp_init(&ctx->prodsp, avctx->bits_per_raw_sample); + ff_proresdsp_init(&ctx->prodsp, avctx->bits_per_raw_sample, avctx->lowres); ff_permute_scantable(ctx->progressive_scan, ff_prores_progressive_scan, ctx->prodsp.idct_permutation); @@ -209,11 +209,11 @@ static int decode_frame_header(ProresContext *ctx, const uint8_t *buf, width = AV_RB16(buf + 8); height = AV_RB16(buf + 10); - if (width != avctx->width || height != avctx->height) { + if (width != avctx->coded_width || height != avctx->coded_height) { int ret; av_log(avctx, AV_LOG_WARNING, "picture resolution change: %dx%d -> %dx%d\n", - avctx->width, avctx->height, width, height); + avctx->coded_width, avctx->coded_height, width, height); if ((ret = ff_set_dimensions(avctx, width, height)) < 0) return ret; dimensions_changed = 1; @@ -263,10 +263,14 @@ static int decode_frame_header(ProresContext *ctx, const uint8_t *buf, ctx->pix_fmt = pix_fmt; #if CONFIG_PRORES_VIDEOTOOLBOX_HWACCEL - *fmtp++ = AV_PIX_FMT_VIDEOTOOLBOX; + if (!avctx->lowres) { + *fmtp++ = AV_PIX_FMT_VIDEOTOOLBOX; + } #endif #if CONFIG_PRORES_VULKAN_HWACCEL - *fmtp++ = AV_PIX_FMT_VULKAN; + if (!avctx->lowres) { + *fmtp++ = AV_PIX_FMT_VULKAN; + } #endif *fmtp++ = ctx->pix_fmt; *fmtp = AV_PIX_FMT_NONE; @@ -345,11 +349,11 @@ static int decode_picture_header(AVCodecContext *avctx, const uint8_t *buf, cons ctx->slice_mb_width = 1 << log2_slice_mb_width; ctx->slice_mb_height = 1 << log2_slice_mb_height; - ctx->mb_width = (avctx->width + 15) >> 4; + ctx->mb_width = (avctx->coded_width + 15) >> 4; if (ctx->frame_type) - ctx->mb_height = (avctx->height + 31) >> 5; + ctx->mb_height = (avctx->coded_height + 31) >> 5; else - ctx->mb_height = (avctx->height + 15) >> 4; + ctx->mb_height = (avctx->coded_height + 15) >> 4; // QT ignores the written value // slice_count = AV_RB16(buf + 5); @@ -493,13 +497,14 @@ static av_always_inline int decode_ac_coeffs(AVCodecContext *avctx, GetBitContex const ProresContext *ctx = avctx->priv_data; int block_mask, sign; unsigned pos, run, level; - int max_coeffs, i, left_bits; + int max_coeffs, used_coeffs, i, left_bits; int log2_block_count = av_log2(blocks_per_slice); run = 4; level = 2; max_coeffs = 64 << log2_block_count; + used_coeffs = (64 >> (2 * avctx->lowres)) << log2_block_count; block_mask = blocks_per_slice - 1; for (pos = block_mask;;) { @@ -509,9 +514,12 @@ static av_always_inline int decode_ac_coeffs(AVCodecContext *avctx, GetBitContex DECODE_CODEWORD(run, run_to_cb[FFMIN(run, 15)], gb); pos += run + 1; - if (pos >= max_coeffs) { - av_log(avctx, AV_LOG_ERROR, "ac tex damaged %d, %d\n", pos, max_coeffs); - return AVERROR_INVALIDDATA; + if (pos >= used_coeffs) { + if (pos >= max_coeffs) { + av_log(avctx, AV_LOG_ERROR, "ac tex damaged %d, %d\n", pos, max_coeffs); + return AVERROR_INVALIDDATA; + } + break; } DECODE_CODEWORD(level, lev_to_cb[FFMIN(level, 9)], gb); @@ -536,6 +544,8 @@ static int decode_slice_luma(AVCodecContext *avctx, SliceContext *slice, int16_t *block; GetBitContext gb; int i, blocks_per_slice = slice->mb_count<<2; + const int block_size = 8 >> ctx->prodsp.lowres; + const int block_row = (block_size * dst_stride) >> 1; int ret; for (i = 0; i < blocks_per_slice; i++) @@ -550,12 +560,12 @@ static int decode_slice_luma(AVCodecContext *avctx, SliceContext *slice, block = blocks; for (i = 0; i < slice->mb_count; i++) { - ctx->prodsp.idct_put(dst, dst_stride, block+(0<<6), qmat); - ctx->prodsp.idct_put(dst +8, dst_stride, block+(1<<6), qmat); - ctx->prodsp.idct_put(dst+4*dst_stride , dst_stride, block+(2<<6), qmat); - ctx->prodsp.idct_put(dst+4*dst_stride+8, dst_stride, block+(3<<6), qmat); + ctx->prodsp.idct_put(dst, dst_stride, block+(0<<6), qmat); + ctx->prodsp.idct_put(dst +block_size, dst_stride, block+(1<<6), qmat); + ctx->prodsp.idct_put(dst+block_row, dst_stride, block+(2<<6), qmat); + ctx->prodsp.idct_put(dst+block_row +block_size, dst_stride, block+(3<<6), qmat); block += 4*64; - dst += 16; + dst += block_size << 1; } return 0; } @@ -570,6 +580,8 @@ static int decode_slice_chroma(AVCodecContext *avctx, SliceContext *slice, int16_t *block; GetBitContext gb; int i, j, blocks_per_slice = slice->mb_count << log2_blocks_per_mb; + const int block_size = 8 >> ctx->prodsp.lowres; + const int block_row = (block_size * dst_stride) >> 1; int ret; for (i = 0; i < blocks_per_slice; i++) @@ -588,10 +600,10 @@ static int decode_slice_chroma(AVCodecContext *avctx, SliceContext *slice, block = blocks; for (i = 0; i < slice->mb_count; i++) { for (j = 0; j < log2_blocks_per_mb; j++) { - ctx->prodsp.idct_put(dst, dst_stride, block+(0<<6), qmat); - ctx->prodsp.idct_put(dst+4*dst_stride, dst_stride, block+(1<<6), qmat); + ctx->prodsp.idct_put(dst, dst_stride, block+(0<<6), qmat); + ctx->prodsp.idct_put(dst+block_row, dst_stride, block+(1<<6), qmat); block += 2*64; - dst += 8; + dst += block_size; } } return 0; @@ -623,6 +635,25 @@ static void decode_slice_alpha(const ProresContext *ctx, block = blocks; + if (ctx->prodsp.lowres) { + const int lowres = ctx->prodsp.lowres; + const int stride = 16 * blocks_per_slice; + + for (i = 0; i < 16 >> lowres; i++) { + for (int x = 0; x < stride >> lowres; x++) { + int sum = 1 << (2 * lowres - 1); + + for (int by = 0; by < 1 << lowres; by++) + for (int bx = 0; bx < 1 << lowres; bx++) + sum += block[by * stride + (x << lowres) + bx]; + dst[x] = sum >> (2 * lowres); + } + dst += dst_stride >> 1; + block += stride << lowres; + } + return; + } + for (i = 0; i < 16; i++) { memcpy(dst, block, 16 * blocks_per_slice * sizeof(*dst)); dst += dst_stride >> 1; @@ -638,7 +669,7 @@ static int decode_slice_thread(AVCodecContext *avctx, void *arg, int jobnr, int AVFrame *pic = ctx->frame; int i, hdr_size, qscale, log2_chroma_blocks_per_mb; int luma_stride, chroma_stride; - int y_data_size, u_data_size, v_data_size, a_data_size, offset; + int y_data_size, u_data_size, v_data_size, a_data_size, offset, mb_y_offset; uint8_t *dest_y, *dest_u, *dest_v; LOCAL_ALIGNED_16(int16_t, qmat_luma_scaled, [64]); LOCAL_ALIGNED_16(int16_t, qmat_chroma_scaled,[64]); @@ -683,17 +714,18 @@ static int decode_slice_thread(AVCodecContext *avctx, void *arg, int jobnr, int if (avctx->pix_fmt == AV_PIX_FMT_YUV444P10 || avctx->pix_fmt == AV_PIX_FMT_YUVA444P10 || avctx->pix_fmt == AV_PIX_FMT_YUV444P12 || avctx->pix_fmt == AV_PIX_FMT_YUVA444P12) { - mb_x_shift = 5; + mb_x_shift = 5 - avctx->lowres; log2_chroma_blocks_per_mb = 2; } else { - mb_x_shift = 4; + mb_x_shift = 4 - avctx->lowres; log2_chroma_blocks_per_mb = 1; } - offset = (slice->mb_y << 4) * luma_stride + (slice->mb_x << 5); + mb_y_offset = (slice->mb_y << 4) >> avctx->lowres; + offset = mb_y_offset * luma_stride + (slice->mb_x << (5 - avctx->lowres)); dest_y = pic->data[0] + offset; - dest_u = pic->data[1] + (slice->mb_y << 4) * chroma_stride + (slice->mb_x << mb_x_shift); - dest_v = pic->data[2] + (slice->mb_y << 4) * chroma_stride + (slice->mb_x << mb_x_shift); + dest_u = pic->data[1] + mb_y_offset * chroma_stride + (slice->mb_x << mb_x_shift); + dest_v = pic->data[2] + mb_y_offset * chroma_stride + (slice->mb_x << mb_x_shift); if (ctx->frame_type && ctx->first_field ^ !!(ctx->frame->flags & AV_FRAME_FLAG_TOP_FIELD_FIRST)) { dest_y += pic->linesize[0]; @@ -869,6 +901,7 @@ const FFCodec ff_prores_decoder = { FF_CODEC_DECODE_CB(decode_frame), UPDATE_THREAD_CONTEXT(update_thread_context), .p.capabilities = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_SLICE_THREADS | AV_CODEC_CAP_FRAME_THREADS, + .p.max_lowres = 3, .caps_internal = FF_CODEC_CAP_SKIP_FRAME_FILL_PARAM, .p.profiles = NULL_IF_CONFIG_SMALL(ff_prores_profiles), #if HWACCEL_MAX diff --git a/libavcodec/proresdsp.c b/libavcodec/proresdsp.c index 919a5d6797..fa75e2d5fe 100644 --- a/libavcodec/proresdsp.c +++ b/libavcodec/proresdsp.c @@ -108,9 +108,10 @@ static void prores_idct_bayer_16(int32_t *restrict block, const int16_t *restric * Add bias value, clamp and output pixels of a slice */ -static inline void put_pixel(uint16_t *dst, ptrdiff_t linesize, const int16_t *in, int bits_per_raw_sample) { - for (int y = 0; y < 8; y++, dst += linesize) { - for (int x = 0; x < 8; x++) { +static av_always_inline void put_pixel(uint16_t *dst, ptrdiff_t linesize, const int16_t *in, + int bits_per_raw_sample, int block_size) { + for (int y = 0; y < block_size; y++, dst += linesize) { + for (int x = 0; x < block_size; x++) { int src_offset = (y << 3) + x; if (bits_per_raw_sample == 10) { @@ -141,12 +142,12 @@ static inline void put_pixel_bayer_lin_curve_12(uint16_t *dst, ptrdiff_t linesiz static void put_pixels_10(uint16_t *dst, ptrdiff_t linesize, const int16_t *in) { - put_pixel(dst, linesize, in, 10); + put_pixel(dst, linesize, in, 10, 8); } static void put_pixels_12(uint16_t *dst, ptrdiff_t linesize, const int16_t *in) { - put_pixel(dst, linesize, in, 12); + put_pixel(dst, linesize, in, 12, 8); } static void prores_idct_put_10_c(uint16_t *out, ptrdiff_t linesize, int16_t *block, const int16_t *qmat) @@ -169,8 +170,107 @@ static void prores_idct_put_bayer_12_c(uint16_t *out, ptrdiff_t linesize, put_pixel_bayer_lin_curve_12(out, linesize << 1, block, lin_curve); } -av_cold void ff_proresdsp_init(ProresDSPContext *dsp, int bits_per_raw_sample) +#define LOWRES_CDC 8192 +#define LOWRES4_C01 10703 +#define LOWRES4_C02 8192 +#define LOWRES4_C03 4433 +#define LOWRES4_C11 4433 +#define LOWRES4_C13 10703 +#define LOWRES2_C01 8192 + +#define LOWRES_IDCT4(line, stride, rnd, shift) do { \ + int16_t *const p = (line); \ + SUINT dc = (SUINT)LOWRES_CDC * p[0] + (rnd); \ + SUINT e2 = (SUINT)LOWRES4_C02 * p[2 * (stride)]; \ + SUINT a0 = dc + e2; \ + SUINT a1 = dc - e2; \ + SUINT b0 = (SUINT)LOWRES4_C01 * p[ (stride)] \ + + (SUINT)LOWRES4_C03 * p[3 * (stride)]; \ + SUINT b1 = (SUINT)LOWRES4_C11 * p[ (stride)] \ + - (SUINT)LOWRES4_C13 * p[3 * (stride)]; \ + \ + p[0] = (int)(a0 + b0) >> (shift); \ + p[ (stride)] = (int)(a1 + b1) >> (shift); \ + p[2 * (stride)] = (int)(a1 - b1) >> (shift); \ + p[3 * (stride)] = (int)(a0 - b0) >> (shift); \ +} while (0) + +#define LOWRES_IDCT2(line, stride, rnd, shift) do { \ + int16_t *const p = (line); \ + SUINT dc = (SUINT)LOWRES_CDC * p[0] + (rnd); \ + SUINT o = (SUINT)LOWRES2_C01 * p[(stride)]; \ + \ + p[0] = (int)(dc + o) >> (shift); \ + p[(stride)] = (int)(dc - o) >> (shift); \ +} while (0) + +#define LOWRES_IDCT1(line, rnd, shift) do { \ + int16_t *const p = (line); \ + \ + p[0] = (int)((SUINT)LOWRES_CDC * p[0] + (rnd)) >> (shift); \ +} while (0) + +#define LOWRES_ROW_SHIFT 14 +#define LOWRES_TOTAL_SHIFT(bits) ((bits) == 10 ? 31 : 29) + +static av_always_inline void prores_idct_lowres(int16_t *restrict block, + const int16_t *restrict qmat, + int bits_per_raw_sample, + int lowres) { + const int block_size = 8 >> lowres; + const int col_shift = LOWRES_TOTAL_SHIFT(bits_per_raw_sample) + - LOWRES_ROW_SHIFT; + const int bias = bits_per_raw_sample == 10 ? 512 : 2048; + const SUINT row_rnd = 1 << (LOWRES_ROW_SHIFT - 1); + const SUINT col_rnd = (1 << (col_shift - 1)) - (bits_per_raw_sample == 12) + + ((SUINT)bias << col_shift); + + for (int v = 0; v < block_size; v++) + for (int u = 0; u < block_size; u++) + block[(v << 3) + u] *= qmat[(v << 3) + u]; + + if (lowres == 1) { + for (int v = 0; v < 4; v++) + LOWRES_IDCT4(block + (v << 3), 1, row_rnd, LOWRES_ROW_SHIFT); + for (int t = 0; t < 4; t++) + LOWRES_IDCT4(block + t, 8, col_rnd, col_shift); + } else if (lowres == 2) { + for (int v = 0; v < 2; v++) + LOWRES_IDCT2(block + (v << 3), 1, row_rnd, LOWRES_ROW_SHIFT); + for (int t = 0; t < 2; t++) + LOWRES_IDCT2(block + t, 8, col_rnd, col_shift); + } else { + LOWRES_IDCT1(block, row_rnd, LOWRES_ROW_SHIFT); + LOWRES_IDCT1(block, col_rnd, col_shift); + } +} + +#define PRORES_IDCT_PUT_LOWRES(bits, lowres) \ +static void prores_idct_put_ ## bits ## _lowres ## lowres ## _c(uint16_t *out, \ + ptrdiff_t linesize, int16_t *block, const int16_t *qmat) \ +{ \ + prores_idct_lowres(block, qmat, bits, lowres); \ + put_pixel(out, linesize >> 1, block, bits, 8 >> lowres); \ +} + +PRORES_IDCT_PUT_LOWRES(10, 1) +PRORES_IDCT_PUT_LOWRES(10, 2) +PRORES_IDCT_PUT_LOWRES(10, 3) +PRORES_IDCT_PUT_LOWRES(12, 1) +PRORES_IDCT_PUT_LOWRES(12, 2) +PRORES_IDCT_PUT_LOWRES(12, 3) + +static const prores_idct_put_func idct_put_lowres[2][3] = { + { prores_idct_put_10_lowres1_c, prores_idct_put_10_lowres2_c, prores_idct_put_10_lowres3_c }, + { prores_idct_put_12_lowres1_c, prores_idct_put_12_lowres2_c, prores_idct_put_12_lowres3_c }, +}; + +av_cold void ff_proresdsp_init(ProresDSPContext *dsp, int bits_per_raw_sample, + int lowres) +{ + dsp->lowres = lowres; + if (bits_per_raw_sample == 10) { dsp->idct_put = prores_idct_put_10_c; dsp->idct_permutation_type = FF_IDCT_PERM_NONE; @@ -181,8 +281,14 @@ av_cold void ff_proresdsp_init(ProresDSPContext *dsp, int bits_per_raw_sample) dsp->idct_permutation_type = FF_IDCT_PERM_NONE; } + if (lowres) { + av_assert1(lowres <= 3); + dsp->idct_put = idct_put_lowres[bits_per_raw_sample != 10][lowres - 1]; + } #if ARCH_X86 && HAVE_X86ASM - ff_proresdsp_init_x86(dsp, bits_per_raw_sample); + if (!lowres) { + ff_proresdsp_init_x86(dsp, bits_per_raw_sample); + } #endif ff_init_scantable_permutation(dsp->idct_permutation, diff --git a/libavcodec/proresdsp.h b/libavcodec/proresdsp.h index 75c782fb56..6f1aebc19c 100644 --- a/libavcodec/proresdsp.h +++ b/libavcodec/proresdsp.h @@ -26,15 +26,19 @@ #include <stddef.h> #include <stdint.h> +typedef void (*prores_idct_put_func)(uint16_t *out, ptrdiff_t linesize, + int16_t *block, const int16_t *qmat); + typedef struct ProresDSPContext { int idct_permutation_type; uint8_t idct_permutation[64]; - void (*idct_put)(uint16_t *out, ptrdiff_t linesize, int16_t *block, const int16_t *qmat); + prores_idct_put_func idct_put; void (*idct_put_bayer)(uint16_t *out, ptrdiff_t linesize, int32_t *block, const int16_t *qmat, const uint16_t *lin_curve); + int lowres; } ProresDSPContext; -void ff_proresdsp_init(ProresDSPContext *dsp, int bits_per_raw_sample); +void ff_proresdsp_init(ProresDSPContext *dsp, int bits_per_raw_sample, int lowres); void ff_proresdsp_init_x86(ProresDSPContext *dsp, int bits_per_raw_sample); diff --git a/tests/checkasm/proresdsp.c b/tests/checkasm/proresdsp.c index 284bd2c540..134dc04231 100644 --- a/tests/checkasm/proresdsp.c +++ b/tests/checkasm/proresdsp.c @@ -76,7 +76,7 @@ void checkasm_check_proresdsp(void) int16_t src_qmat[BLOCK_SIZE]; ProresDSPContext dsp = { 0 }; - ff_proresdsp_init(&dsp, 12); + ff_proresdsp_init(&dsp, 12, 0); if (check_func(dsp.idct_put, "prores_idct_put_12")) { declare_func(void, uint16_t *dst, ptrdiff_t linesize, int16_t *block, const int16_t *qmat); -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
