[PR] avcodec/hevc/filter: take the deblocking offsets from the q0,0 slice (PR #23916)

charlymp via ffmpeg-devel <[email protected]> Sat, 25 Jul 2026 17:48:44 -0000
Newsgroups gmane.comp.video.ffmpeg.devel
Message-ID <178500172646.59.9149773776290846700@29965ddac10e>
PR #23916 opened by charlymp
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23916
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23916.patch

Equations 8-348 and 8-350 (luma) and 8-383 (chroma) all read slice_beta_offset_div2 and slice_tc_offset_div2 from the slice that contains the q0,0 sample of the edge being filtered.
That is the current CTB for a vertical edge, and the CTB that contains x for a horizontal one, both of its 4 sample groups included.

deblocking_filter_CTB() does not derive them: it reuses whatever the loop that ran before left in the shared tc_offset/beta_offset variables.
As a result the vertical edges of a CTB can be filtered with the left neighbour's offsets, the first sample group of a horizontal chroma edge uses them everywhere, and Cb and Cr can even end up with different offsets on the first row of a CTB.

This only changes the output when a CTB and its left neighbour disagree on the slice deblocking offsets, which needs a slice or tile boundary inside a CTB row with different slice_beta_offset_div2 / slice_tc_offset_div2 on either side.


>From 0a7d0bd8a80f1e08a68072c97a68abafdf380e41 Mon Sep 17 00:00:00 2001
From: Charly Morgand-Poyac <[email protected]>
Date: Sat, 25 Jul 2026 19:37:30 +0200
Subject: [PATCH] avcodec/hevc/filter: take the deblocking offsets from the
 q0,0 slice

8.7.2.5.3 and 8.7.2.5.5 read slice_beta_offset_div2 and slice_tc_offset_div2
from the slice that contains the q0,0 sample: the current CTB for a vertical
edge, the CTB that contains x for a horizontal one. deblocking_filter_CTB()
reused whatever the previous loop had left in tc_offset instead, so vertical
edges could take the left neighbour's offsets, and the first sample group of a
horizontal chroma edge took them everywhere.

Signed-off-by: Charly Morgand-Poyac <[email protected]>
---
 libavcodec/hevc/filter.c | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/libavcodec/hevc/filter.c b/libavcodec/hevc/filter.c
index e897ad5d58..6753e4a528 100644
--- a/libavcodec/hevc/filter.c
+++ b/libavcodec/hevc/filter.c
@@ -548,6 +548,8 @@ static void deblocking_filter_CTB(const HEVCContext *s, const HEVCLayerContext *
         x_end2 -= 8;
     for (y = y0; y < y_end; y += 8) {
         // vertical filtering luma
+        tc_offset   = cur_tc_offset;
+        beta_offset = cur_beta_offset;
         for (x = x0 ? x0 : 8; x < x_end; x += 8) {
             const int bs0 = l->vertical_bs[(x +  y      * l->bs_width) >> 2];
             const int bs1 = l->vertical_bs[(x + (y + 4) * l->bs_width) >> 2];
@@ -622,8 +624,8 @@ static void deblocking_filter_CTB(const HEVCContext *s, const HEVCLayerContext *
                         const int qp1 = (get_qPy(sps, l->qp_y_tab, x - 1, y + (4 * v)) +
                                          get_qPy(sps, l->qp_y_tab, x,     y + (4 * v)) + 1) >> 1;
 
-                        c_tc[0] = (bs0 == 2) ? chroma_tc(pps, sps, qp0, chroma, tc_offset) : 0;
-                        c_tc[1] = (bs1 == 2) ? chroma_tc(pps, sps, qp1, chroma, tc_offset) : 0;
+                        c_tc[0] = (bs0 == 2) ? chroma_tc(pps, sps, qp0, chroma, cur_tc_offset) : 0;
+                        c_tc[1] = (bs1 == 2) ? chroma_tc(pps, sps, qp1, chroma, cur_tc_offset) : 0;
                         src       = &data[chroma][(y >> sps->vshift[chroma]) * linesize[chroma] + ((x >> sps->hshift[chroma]) << sps->pixel_shift)];
                         if (pcmf) {
                             no_p[0] = get_pcm(sps, l->is_pcm, x - 1, y);
@@ -642,7 +644,6 @@ static void deblocking_filter_CTB(const HEVCContext *s, const HEVCLayerContext *
                     continue;
 
                 // horizontal filtering chroma
-                tc_offset = x0 ? left_tc_offset : cur_tc_offset;
                 x_end2 = x_end;
                 if (x_end != sps->width)
                     x_end2 = x_end - 8 * h;
@@ -650,13 +651,14 @@ static void deblocking_filter_CTB(const HEVCContext *s, const HEVCLayerContext *
                     const int bs0 = l->horizontal_bs[( x          + y * l->bs_width) >> 2];
                     const int bs1 = l->horizontal_bs[((x + 4 * h) + y * l->bs_width) >> 2];
                     if ((bs0 == 2) || (bs1 == 2)) {
+                        const int off = x >= x0 ? cur_tc_offset : left_tc_offset;
                         const int qp0 = bs0 == 2 ? (get_qPy(sps, l->qp_y_tab, x,           y - 1) +
                                                     get_qPy(sps, l->qp_y_tab, x,           y)     + 1) >> 1 : 0;
                         const int qp1 = bs1 == 2 ? (get_qPy(sps, l->qp_y_tab, x + (4 * h), y - 1) +
                                                     get_qPy(sps, l->qp_y_tab, x + (4 * h), y)     + 1) >> 1 : 0;
 
-                        c_tc[0]   = bs0 == 2 ? chroma_tc(pps, sps, qp0, chroma, tc_offset)     : 0;
-                        c_tc[1]   = bs1 == 2 ? chroma_tc(pps, sps, qp1, chroma, cur_tc_offset) : 0;
+                        c_tc[0]   = bs0 == 2 ? chroma_tc(pps, sps, qp0, chroma, off) : 0;
+                        c_tc[1]   = bs1 == 2 ? chroma_tc(pps, sps, qp1, chroma, off) : 0;
                         src       = &data[chroma][(y >> sps->vshift[1]) * linesize[chroma] + ((x >> sps->hshift[1]) << sps->pixel_shift)];
                         if (pcmf) {
                             no_p[0] = get_pcm(sps, l->is_pcm, x,           y - 1);
-- 
2.52.0

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