PR #24915 opened by Jun Zhao (mypopydev)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24915
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24915.patch

When CtbSizeY is 16 and chroma is horizontally subsampled (4:2:0 or 4:2:2),
horizontal chroma deblocking is deferred for the whole CTB width
(8 * (1 << hshift) = 16 luma pixels).

In the current pipelined loop filter implementation, ff_hevc_hls_filter()
ran sao_filter_CTB() for CTB (x - ctb_size, ...) immediately after
deblocking_filter_CTB() at x. This caused SAO edge offset classes 0 and 2
on the rightmost column of the 8x8 chroma CTB to fetch unfiltered chroma
pixels from the right neighboring CTB, leading to incorrect offsets and
MD5 mismatch.

Fix this by delaying SAO execution by 2 * ctb_size horizontally when
ctb_size == 16 and sps->hshift[1] != 0. The remaining columns are caught up
at the end of each CTB row.

Fixes: ticket #24870
Signed-off-by: Jun Zhao <[email protected]>


>From e5a08f7c0e45e6d278a394ef19c97f0b8ad3ed45 Mon Sep 17 00:00:00 2001
From: Jun Zhao <[email protected]>
Date: Wed, 7 Oct 2026 09:28:59 +0800
Subject: [PATCH] avcodec/hevc: fix chroma SAO filtering with 16x16 CTB

When CtbSizeY is 16 and chroma is horizontally subsampled (4:2:0 or 4:2:2),
horizontal chroma deblocking is deferred for the whole CTB width
(8 * (1 << hshift) = 16 luma pixels).

In the current pipelined loop filter implementation, ff_hevc_hls_filter()
ran sao_filter_CTB() for CTB (x - ctb_size, ...) immediately after
deblocking_filter_CTB() at x. This caused SAO edge offset classes 0 and 2
on the rightmost column of the 8x8 chroma CTB to fetch unfiltered chroma
pixels from the right neighboring CTB, leading to incorrect offsets and
MD5 mismatch.

Fix this by delaying SAO execution by 2 * ctb_size horizontally when
ctb_size == 16 and sps->hshift[1] != 0. The remaining columns are caught up
at the end of each CTB row.

Fixes: ticket #24870
Signed-off-by: Jun Zhao <[email protected]>
---
 libavcodec/hevc/filter.c | 34 +++++++++++++++++++++++++---------
 1 file changed, 25 insertions(+), 9 deletions(-)

diff --git a/libavcodec/hevc/filter.c b/libavcodec/hevc/filter.c
index c8e9fa8bd6..4efe751ea4 100644
--- a/libavcodec/hevc/filter.c
+++ b/libavcodec/hevc/filter.c
@@ -952,19 +952,35 @@ void ff_hevc_hls_filter(HEVCLocalContext *lc, const 
HEVCLayerContext *l,
     if (!skip)
         deblocking_filter_CTB(s, l, pps, sps, x, y);
     if (sps->sao_enabled && !skip) {
-        int y_end = y >= sps->height - ctb_size;
-        if (y && x)
-            sao_filter_CTB(lc, l, s, pps, sps, x - ctb_size, y - ctb_size);
-        if (x && y_end)
-            sao_filter_CTB(lc, l, s, pps, sps, x - ctb_size, y);
+        int y_end  = y >= sps->height - ctb_size;
+        int x0, x_sao, x_delay = ctb_size;
+
+        /* Horizontal chroma deblocking leaves the last 8<<hshift luma columns
+         * of a CTB to the next CTB. With a 16x16 CTB and horizontally
+         * subsampled chroma that span is the whole CTB, so those edges are
+         * filtered only while the CTB to the right is deblocked. EO classes
+         * 0 and 2 on the rightmost chroma column read that region and have
+         * to run one CTB later. Every other CTB size, and 4:4:4, keep the
+         * original one-CTB delay. The total number of SAO invocations is
+         * unchanged; row-end catch-up covers the columns this delay skips. */
+        if (ctb_size == 16 && sps->hshift[1])
+            x_delay = 2 * ctb_size;
+        x_sao = x - x_delay;
+
+        if (y && x_sao >= 0)
+            sao_filter_CTB(lc, l, s, pps, sps, x_sao, y - ctb_size);
+        if (x_sao >= 0 && y_end)
+            sao_filter_CTB(lc, l, s, pps, sps, x_sao, y);
         if (y && x_end) {
-            sao_filter_CTB(lc, l, s, pps, sps, x, y - ctb_size);
-            if (s->avctx->active_thread_type & FF_THREAD_FRAME )
+            for (x0 = FFMAX(x_sao + ctb_size, 0); x0 <= x; x0 += ctb_size)
+                sao_filter_CTB(lc, l, s, pps, sps, x0, y - ctb_size);
+            if (s->avctx->active_thread_type & FF_THREAD_FRAME)
                 ff_progress_frame_report(&s->cur_frame->tf, y);
         }
         if (x_end && y_end) {
-            sao_filter_CTB(lc, l, s, pps, sps, x , y);
-            if (s->avctx->active_thread_type & FF_THREAD_FRAME )
+            for (x0 = FFMAX(x_sao + ctb_size, 0); x0 <= x; x0 += ctb_size)
+                sao_filter_CTB(lc, l, s, pps, sps, x0, y);
+            if (s->avctx->active_thread_type & FF_THREAD_FRAME)
                 ff_progress_frame_report(&s->cur_frame->tf, y + ctb_size);
         }
     } else if (s->avctx->active_thread_type & FF_THREAD_FRAME && x_end)
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to