[PR] avcodec/hevc: NAL slice threading (PR #23873)

ifb via ffmpeg-devel <[email protected]> Wed, 22 Jul 2026 21:28:20 -0000
Newsgroups gmane.comp.video.ffmpeg.devel
Message-ID <178475570126.59.12668450271140669771@29965ddac10e>
PR #23873 opened by ifb
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23873
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23873.patch

# Summary of changes

This PR adds within-frame, slice-level multithreading to the HEVC decoder for streams that carry more than one independent slice segment per picture. Such streams are common from certain hardware encoders (e.g. Haivision Makito X4 / Allegro VCU), which split each frame into several independent NAL slices. Today those can only be frame-threaded, which adds output latency and does nothing for single-frame or low-delay decoding.

An independent slice resets CABAC and blocks intra prediction across its boundary, so per-slice CABAC and reconstruction are embarrassingly parallel. The decoder dispatches one job per slice, each with its own HEVCLocalContext, SliceHeader and refPicList (resolved per CTB through rpl_tab). Deblocking and SAO run in a serial post-pass after all workers finish: the target streams set slice_loop_filter_across_slices_enabled_flag, so SAO reads neighboring slices' reconstructed pixels and cannot run inline.

The parallel path is entered only for the case it supports: slice threading, more than one base-layer independent slice in a single access unit, no hwaccel, plain PPS (no WPP, tiles or dependent slices), non-SCC profile, and skip_frame not dropping slices. WPP/tiles/dependent/SCC are not known until the active PPS is selected by the first slice header (its NAL may be in the same packet), so they are re-checked against the real PPS before any state is committed; anything unsupported, plus discarded frames (skip_frame, post-seek RASL), falls back to the existing serial decode. The WPP and frame-threading paths are unchanged.

- FATE: all hevc tests pass at THREADS=1/4/8 with THREAD_TYPE=slice.
- framecrc bit-exact vs single-thread decode on the multi-slice conformance streams (SLICES_A, SLIST_A..D, DELTAQP_A..C, CIP_C, RPLM_B, ...) and on 8-slice 1080p60 captures, at -threads {4,8} x {slice,frame}.
- ASan+UBSan and TSan clean at -threads {4,8}.

Performance (AMD EPYC 9004, 8-slice 1080p60): ~2.7x at -threads 8 -thread_type slice; single-slice streams show no regression.

<!--
If this PR requires new FATE test samples, attach them to the PR and
list their target paths below (relative to the fate-suite root).

Attached filenames must match the sample's filename:

```fate-samples
# e.g. vorbis/new-sample.ogg
```
-->



>From 56b239dd1dd902507770a994110ca3e2f20b3d71 Mon Sep 17 00:00:00 2001
From: Phillip Blucas <[email protected]>
Date: Fri, 22 May 2026 23:31:43 -0500
Subject: [PATCH 1/2] avcodec/hevc: route sh access through lc->sh pointer

Add const SliceHeader *sh to HEVCLocalContext and set it to &s->sh.
Change all s->sh.* accesses in the CTB decode hot path (hevcdec.c,
cabac.c, mvs.c, filter.c) to lc->sh->* so each worker reads its own
slice header.

No functional change. lc->sh still points to s->sh throughout.

Signed-off-by: Phillip Blucas <[email protected]>
---
 libavcodec/hevc/cabac.c   |  18 +++---
 libavcodec/hevc/filter.c  |  10 +--
 libavcodec/hevc/hevcdec.c | 130 +++++++++++++++++++-------------------
 libavcodec/hevc/hevcdec.h |   2 +
 libavcodec/hevc/mvs.c     |  22 +++----
 5 files changed, 92 insertions(+), 90 deletions(-)

diff --git a/libavcodec/hevc/cabac.c b/libavcodec/hevc/cabac.c
index 55d5741f87..e5e9d395db 100644
--- a/libavcodec/hevc/cabac.c
+++ b/libavcodec/hevc/cabac.c
@@ -429,17 +429,17 @@ static int cabac_reinit(HEVCLocalContext *lc)
 
 static void cabac_init_state(HEVCLocalContext *lc, const HEVCContext *s)
 {
-    int init_type = 2 - s->sh.slice_type;
+    int init_type = 2 - lc->sh->slice_type;
     int i;
 
-    if (s->sh.cabac_init_flag && s->sh.slice_type != HEVC_SLICE_I)
+    if (lc->sh->cabac_init_flag && lc->sh->slice_type != HEVC_SLICE_I)
         init_type ^= 3;
 
     for (i = 0; i < HEVC_CONTEXTS; i++) {
         int init_value = init_values[init_type][i];
         int m = (init_value >> 4) * 5 - 45;
         int n = ((init_value & 15) << 3) - 16;
-        int pre = 2 * (((m * av_clip(s->sh.slice_qp, 0, 51)) >> 4) + n) - 127;
+        int pre = 2 * (((m * av_clip(lc->sh->slice_qp, 0, 51)) >> 4) + n) - 127;
 
         pre ^= pre >> 31;
         if (pre > 124)
@@ -458,21 +458,21 @@ int ff_hevc_cabac_init(HEVCLocalContext *lc, const HEVCPPS *pps,
     const HEVCContext *const s = lc->parent;
     const HEVCSPS   *const sps = pps->sps;
 
-    if (ctb_addr_ts == pps->ctb_addr_rs_to_ts[s->sh.slice_ctb_addr_rs]) {
+    if (ctb_addr_ts == pps->ctb_addr_rs_to_ts[lc->sh->slice_ctb_addr_rs]) {
         int ret = ff_init_cabac_decoder(&lc->cc, data, size);
         if (ret < 0)
             return ret;
-        if (s->sh.dependent_slice_segment_flag == 0 ||
+        if (lc->sh->dependent_slice_segment_flag == 0 ||
             (pps->tiles_enabled_flag &&
              pps->tile_id[ctb_addr_ts] != pps->tile_id[ctb_addr_ts - 1]))
             cabac_init_state(lc, s);
 
-        if (!s->sh.first_slice_in_pic_flag &&
+        if (!lc->sh->first_slice_in_pic_flag &&
             pps->entropy_coding_sync_enabled_flag) {
             if (ctb_addr_ts % sps->ctb_width == 0) {
                 if (sps->ctb_width == 1)
                     cabac_init_state(lc, s);
-                else if (s->sh.dependent_slice_segment_flag == 1)
+                else if (lc->sh->dependent_slice_segment_flag == 1)
                     load_states(lc, sps);
             }
         }
@@ -1053,10 +1053,10 @@ void ff_hevc_hls_residual_coding(HEVCLocalContext *lc, const HEVCPPS *pps,
             int qp_i, offset;
 
             if (c_idx == 1)
-                offset = pps->cb_qp_offset + s->sh.slice_cb_qp_offset +
+                offset = pps->cb_qp_offset + lc->sh->slice_cb_qp_offset +
                          lc->tu.cu_qp_offset_cb;
             else
-                offset = pps->cr_qp_offset + s->sh.slice_cr_qp_offset +
+                offset = pps->cr_qp_offset + lc->sh->slice_cr_qp_offset +
                          lc->tu.cu_qp_offset_cr;
 
             qp_i = av_clip(qp_y + offset, - sps->qp_bd_offset, 57);
diff --git a/libavcodec/hevc/filter.c b/libavcodec/hevc/filter.c
index 68ae0e9ef6..f4eed0dd3f 100644
--- a/libavcodec/hevc/filter.c
+++ b/libavcodec/hevc/filter.c
@@ -96,7 +96,7 @@ static int get_qPy_pred(HEVCLocalContext *lc, const HEVCContext *s,
     // qPy_pred
     if (lc->first_qp_group || (!xQgBase && !yQgBase)) {
         lc->first_qp_group = !lc->tu.is_cu_qp_delta_coded;
-        qPy_pred = s->sh.slice_qp;
+        qPy_pred = lc->sh->slice_qp;
     } else {
         qPy_pred = lc->qPy_pred;
     }
@@ -757,7 +757,7 @@ void ff_hevc_deblocking_boundary_strengths(HEVCLocalContext *lc, const HEVCLayer
 
     boundary_upper = y0 > 0 && !(y0 & 7);
     if (boundary_upper &&
-        ((!s->sh.slice_loop_filter_across_slices_enabled_flag &&
+        ((!lc->sh->slice_loop_filter_across_slices_enabled_flag &&
           lc->boundary_flags & BOUNDARY_UPPER_SLICE &&
           (y0 % (1 << sps->log2_ctb_size)) == 0) ||
          (!pps->loop_filter_across_tiles_enabled_flag &&
@@ -795,7 +795,7 @@ void ff_hevc_deblocking_boundary_strengths(HEVCLocalContext *lc, const HEVCLayer
     // bs for vertical TU boundaries
     boundary_left = x0 > 0 && !(x0 & 7);
     if (boundary_left &&
-        ((!s->sh.slice_loop_filter_across_slices_enabled_flag &&
+        ((!lc->sh->slice_loop_filter_across_slices_enabled_flag &&
           lc->boundary_flags & BOUNDARY_LEFT_SLICE &&
           (x0 % (1 << sps->log2_ctb_size)) == 0) ||
          (!pps->loop_filter_across_tiles_enabled_flag &&
@@ -880,9 +880,9 @@ void ff_hevc_hls_filter(HEVCLocalContext *lc, const HEVCLayerContext *l,
     if (s->avctx->skip_loop_filter >= AVDISCARD_ALL ||
         (s->avctx->skip_loop_filter >= AVDISCARD_NONKEY && !IS_IDR(s)) ||
         (s->avctx->skip_loop_filter >= AVDISCARD_NONINTRA &&
-         s->sh.slice_type != HEVC_SLICE_I) ||
+         lc->sh->slice_type != HEVC_SLICE_I) ||
         (s->avctx->skip_loop_filter >= AVDISCARD_BIDIR &&
-         s->sh.slice_type == HEVC_SLICE_B) ||
+         lc->sh->slice_type == HEVC_SLICE_B) ||
         (s->avctx->skip_loop_filter >= AVDISCARD_NONREF &&
         ff_hevc_nal_is_nonref(s->nal_unit_type)))
         skip = 1;
diff --git a/libavcodec/hevc/hevcdec.c b/libavcodec/hevc/hevcdec.c
index b576b09b42..ec310700a2 100644
--- a/libavcodec/hevc/hevcdec.c
+++ b/libavcodec/hevc/hevcdec.c
@@ -1210,14 +1210,13 @@ static void hls_sao_param(HEVCLocalContext *lc, const HEVCLayerContext *l,
                           const HEVCPPS *pps, const HEVCSPS *sps,
                           int rx, int ry)
 {
-    const HEVCContext *const s = lc->parent;
     int sao_merge_left_flag = 0;
     int sao_merge_up_flag   = 0;
     SAOParams *sao          = &CTB(l->sao, rx, ry);
     int c_idx, i;
 
-    if (s->sh.slice_sample_adaptive_offset_flag[0] ||
-        s->sh.slice_sample_adaptive_offset_flag[1]) {
+    if (lc->sh->slice_sample_adaptive_offset_flag[0] ||
+        lc->sh->slice_sample_adaptive_offset_flag[1]) {
         if (rx > 0) {
             if (lc->ctb_left_flag)
                 sao_merge_left_flag = ff_hevc_sao_merge_flag_decode(lc);
@@ -1232,7 +1231,7 @@ static void hls_sao_param(HEVCLocalContext *lc, const HEVCLayerContext *l,
         int log2_sao_offset_scale = c_idx == 0 ? pps->log2_sao_offset_scale_luma :
                                                  pps->log2_sao_offset_scale_chroma;
 
-        if (!s->sh.slice_sample_adaptive_offset_flag[c_idx]) {
+        if (!lc->sh->slice_sample_adaptive_offset_flag[c_idx]) {
             sao->type_idx[c_idx] = SAO_NOT_APPLIED;
             continue;
         }
@@ -1346,7 +1345,7 @@ static int hls_transform_unit(HEVCLocalContext *lc,
             ff_hevc_set_qPy(lc, l, pps, cb_xBase, cb_yBase, log2_cb_size);
         }
 
-        if (s->sh.cu_chroma_qp_offset_enabled_flag && cbf_chroma &&
+        if (lc->sh->cu_chroma_qp_offset_enabled_flag && cbf_chroma &&
             !lc->cu.cu_transquant_bypass_flag  &&  !lc->tu.is_cu_chroma_qp_offset_coded) {
             int cu_chroma_qp_offset_flag = ff_hevc_cu_chroma_qp_offset_flag(lc);
             if (cu_chroma_qp_offset_flag) {
@@ -1535,7 +1534,6 @@ static int hls_transform_tree(HEVCLocalContext *lc,
                               int trafo_depth, int blk_idx,
                               const int *base_cbf_cb, const int *base_cbf_cr)
 {
-    const HEVCContext *const s = lc->parent;
     uint8_t split_transform_flag;
     int cbf_cb[2];
     int cbf_cr[2];
@@ -1644,7 +1642,7 @@ do {
                     l->cbf_luma[y_tu * min_tu_width + x_tu] = 1;
                 }
         }
-        if (!s->sh.disable_deblocking_filter_flag) {
+        if (!lc->sh->disable_deblocking_filter_flag) {
             ff_hevc_deblocking_boundary_strengths(lc, l, pps, x0, y0, log2_trafo_size);
             if (pps->transquant_bypass_enable_flag &&
                 lc->cu.cu_transquant_bypass_flag)
@@ -1671,7 +1669,7 @@ static int hls_pcm_sample(HEVCLocalContext *lc, const HEVCLayerContext *l,
     const uint8_t *pcm = skip_bytes(&lc->cc, (length + 7) >> 3);
     int ret;
 
-    if (!s->sh.disable_deblocking_filter_flag)
+    if (!lc->sh->disable_deblocking_filter_flag)
         ff_hevc_deblocking_boundary_strengths(lc, l, pps, x0, y0, log2_cb_size);
 
     ret = init_get_bits(&gb, pcm, length);
@@ -1727,8 +1725,8 @@ static void luma_mc_uni(HEVCLocalContext *lc,
     int pic_height       = sps->height;
     int mx               = mv->x & 3;
     int my               = mv->y & 3;
-    int weight_flag      = (s->sh.slice_type == HEVC_SLICE_P && pps->weighted_pred_flag) ||
-                           (s->sh.slice_type == HEVC_SLICE_B && pps->weighted_bipred_flag);
+    int weight_flag      = (lc->sh->slice_type == HEVC_SLICE_P && pps->weighted_pred_flag) ||
+                           (lc->sh->slice_type == HEVC_SLICE_B && pps->weighted_bipred_flag);
     int idx              = hevc_pel_weight[block_w];
 
     x_off += mv->x >> 2;
@@ -1758,7 +1756,7 @@ static void luma_mc_uni(HEVCLocalContext *lc,
                                                       block_h, mx, my, block_w);
     else
         s->hevcdsp.put_hevc_qpel_uni_w[idx][!!my][!!mx](dst, dststride, src, srcstride,
-                                                        block_h, s->sh.luma_log2_weight_denom,
+                                                        block_h, lc->sh->luma_log2_weight_denom,
                                                         luma_weight, luma_offset, mx, my, block_w);
 }
 
@@ -1794,8 +1792,8 @@ static void luma_mc_bi(HEVCLocalContext *lc,
     int my0              = mv0->y & 3;
     int mx1              = mv1->x & 3;
     int my1              = mv1->y & 3;
-    int weight_flag      = (s->sh.slice_type == HEVC_SLICE_P && pps->weighted_pred_flag) ||
-                           (s->sh.slice_type == HEVC_SLICE_B && pps->weighted_bipred_flag);
+    int weight_flag      = (lc->sh->slice_type == HEVC_SLICE_P && pps->weighted_pred_flag) ||
+                           (lc->sh->slice_type == HEVC_SLICE_B && pps->weighted_bipred_flag);
     int x_off0           = x_off + (mv0->x >> 2);
     int y_off0           = y_off + (mv0->y >> 2);
     int x_off1           = x_off + (mv1->x >> 2);
@@ -1846,11 +1844,11 @@ static void luma_mc_bi(HEVCLocalContext *lc,
                                                        block_h, mx1, my1, block_w);
     else
         s->hevcdsp.put_hevc_qpel_bi_w[idx][!!my1][!!mx1](dst, dststride, src1, src1stride, lc->tmp,
-                                                         block_h, s->sh.luma_log2_weight_denom,
-                                                         s->sh.luma_weight_l0[current_mv->ref_idx[0]],
-                                                         s->sh.luma_weight_l1[current_mv->ref_idx[1]],
-                                                         s->sh.luma_offset_l0[current_mv->ref_idx[0]],
-                                                         s->sh.luma_offset_l1[current_mv->ref_idx[1]],
+                                                         block_h, lc->sh->luma_log2_weight_denom,
+                                                         lc->sh->luma_weight_l0[current_mv->ref_idx[0]],
+                                                         lc->sh->luma_weight_l1[current_mv->ref_idx[1]],
+                                                         lc->sh->luma_offset_l0[current_mv->ref_idx[0]],
+                                                         lc->sh->luma_offset_l1[current_mv->ref_idx[1]],
                                                          mx1, my1, block_w);
 
 }
@@ -1883,8 +1881,8 @@ static void chroma_mc_uni(HEVCLocalContext *lc,
     int pic_width        = sps->width >> sps->hshift[1];
     int pic_height       = sps->height >> sps->vshift[1];
     const Mv *mv         = &current_mv->mv[reflist];
-    int weight_flag      = (s->sh.slice_type == HEVC_SLICE_P && pps->weighted_pred_flag) ||
-                           (s->sh.slice_type == HEVC_SLICE_B && pps->weighted_bipred_flag);
+    int weight_flag      = (lc->sh->slice_type == HEVC_SLICE_P && pps->weighted_pred_flag) ||
+                           (lc->sh->slice_type == HEVC_SLICE_B && pps->weighted_bipred_flag);
     int idx              = hevc_pel_weight[block_w];
     int hshift           = sps->hshift[1];
     int vshift           = sps->vshift[1];
@@ -1921,7 +1919,7 @@ static void chroma_mc_uni(HEVCLocalContext *lc,
                                                   block_h, _mx, _my, block_w);
     else
         s->hevcdsp.put_hevc_epel_uni_w[idx][!!my][!!mx](dst0, dststride, src0, srcstride,
-                                                        block_h, s->sh.chroma_log2_weight_denom,
+                                                        block_h, lc->sh->chroma_log2_weight_denom,
                                                         chroma_weight, chroma_offset, _mx, _my, block_w);
 }
 
@@ -1953,8 +1951,8 @@ static void chroma_mc_bi(HEVCLocalContext *lc,
     const uint8_t *src2  = ref1->data[cidx+1];
     ptrdiff_t src1stride = ref0->linesize[cidx+1];
     ptrdiff_t src2stride = ref1->linesize[cidx+1];
-    int weight_flag      = (s->sh.slice_type == HEVC_SLICE_P && pps->weighted_pred_flag) ||
-                           (s->sh.slice_type == HEVC_SLICE_B && pps->weighted_bipred_flag);
+    int weight_flag      = (lc->sh->slice_type == HEVC_SLICE_P && pps->weighted_pred_flag) ||
+                           (lc->sh->slice_type == HEVC_SLICE_B && pps->weighted_bipred_flag);
     int pic_width        = sps->width >> sps->hshift[1];
     int pic_height       = sps->height >> sps->vshift[1];
     const Mv *const mv0  = &current_mv->mv[0];
@@ -2027,11 +2025,11 @@ static void chroma_mc_bi(HEVCLocalContext *lc,
         s->hevcdsp.put_hevc_epel_bi_w[idx][!!my1][!!mx1](dst0, s->cur_frame->f->linesize[cidx+1],
                                                          src2, src2stride, lc->tmp,
                                                          block_h,
-                                                         s->sh.chroma_log2_weight_denom,
-                                                         s->sh.chroma_weight_l0[current_mv->ref_idx[0]][cidx],
-                                                         s->sh.chroma_weight_l1[current_mv->ref_idx[1]][cidx],
-                                                         s->sh.chroma_offset_l0[current_mv->ref_idx[0]][cidx],
-                                                         s->sh.chroma_offset_l1[current_mv->ref_idx[1]][cidx],
+                                                         lc->sh->chroma_log2_weight_denom,
+                                                         lc->sh->chroma_weight_l0[current_mv->ref_idx[0]][cidx],
+                                                         lc->sh->chroma_weight_l1[current_mv->ref_idx[1]][cidx],
+                                                         lc->sh->chroma_offset_l0[current_mv->ref_idx[0]][cidx],
+                                                         lc->sh->chroma_offset_l1[current_mv->ref_idx[1]][cidx],
                                                          _mx1, _my1, block_w);
 }
 
@@ -2051,18 +2049,17 @@ static void hevc_luma_mv_mvp_mode(HEVCLocalContext *lc,
                                   int nPbH, int log2_cb_size, int part_idx,
                                   int merge_idx, MvField *mv)
 {
-    const HEVCContext *const s = lc->parent;
     enum InterPredIdc inter_pred_idc = PRED_L0;
     int mvp_flag;
 
     ff_hevc_set_neighbour_available(lc, x0, y0, nPbW, nPbH, sps->log2_ctb_size);
     mv->pred_flag = 0;
-    if (s->sh.slice_type == HEVC_SLICE_B)
+    if (lc->sh->slice_type == HEVC_SLICE_B)
         inter_pred_idc = ff_hevc_inter_pred_idc_decode(lc, nPbW, nPbH);
 
     if (inter_pred_idc != PRED_L1) {
-        if (s->sh.nb_refs[L0])
-            mv->ref_idx[0]= ff_hevc_ref_idx_lx_decode(lc, s->sh.nb_refs[L0]);
+        if (lc->sh->nb_refs[L0])
+            mv->ref_idx[0]= ff_hevc_ref_idx_lx_decode(lc, lc->sh->nb_refs[L0]);
 
         mv->pred_flag = PF_L0;
         ff_hevc_hls_mvd_coding(lc, x0, y0, 0);
@@ -2074,10 +2071,10 @@ static void hevc_luma_mv_mvp_mode(HEVCLocalContext *lc,
     }
 
     if (inter_pred_idc != PRED_L0) {
-        if (s->sh.nb_refs[L1])
-            mv->ref_idx[1]= ff_hevc_ref_idx_lx_decode(lc, s->sh.nb_refs[L1]);
+        if (lc->sh->nb_refs[L1])
+            mv->ref_idx[1]= ff_hevc_ref_idx_lx_decode(lc, lc->sh->nb_refs[L1]);
 
-        if (s->sh.mvd_l1_zero_flag == 1 && inter_pred_idc == PRED_BI) {
+        if (lc->sh->mvd_l1_zero_flag == 1 && inter_pred_idc == PRED_BI) {
             AV_ZERO32(&lc->pu.mvd);
         } else {
             ff_hevc_hls_mvd_coding(lc, x0, y0, 1);
@@ -2128,7 +2125,7 @@ static void hls_prediction_unit(HEVCLocalContext *lc,
         lc->pu.merge_flag = ff_hevc_merge_flag_decode(lc);
 
     if (skip_flag || lc->pu.merge_flag) {
-        if (s->sh.max_num_merge_cand > 1)
+        if (lc->sh->max_num_merge_cand > 1)
             merge_idx = ff_hevc_merge_idx_decode(lc);
         else
             merge_idx = 0;
@@ -2168,16 +2165,16 @@ static void hls_prediction_unit(HEVCLocalContext *lc,
 
         luma_mc_uni(lc, pps, sps, dst0, linesize[0], ref0->f,
                     &current_mv.mv[0], x0, y0, nPbW, nPbH,
-                    s->sh.luma_weight_l0[current_mv.ref_idx[0]],
-                    s->sh.luma_offset_l0[current_mv.ref_idx[0]]);
+                    lc->sh->luma_weight_l0[current_mv.ref_idx[0]],
+                    lc->sh->luma_offset_l0[current_mv.ref_idx[0]]);
 
         if (sps->chroma_format_idc) {
             chroma_mc_uni(lc, pps, sps, dst1, linesize[1], ref0->f->data[1], ref0->f->linesize[1],
                           0, x0_c, y0_c, nPbW_c, nPbH_c, &current_mv,
-                          s->sh.chroma_weight_l0[current_mv.ref_idx[0]][0], s->sh.chroma_offset_l0[current_mv.ref_idx[0]][0]);
+                          lc->sh->chroma_weight_l0[current_mv.ref_idx[0]][0], lc->sh->chroma_offset_l0[current_mv.ref_idx[0]][0]);
             chroma_mc_uni(lc, pps, sps, dst2, linesize[2], ref0->f->data[2], ref0->f->linesize[2],
                           0, x0_c, y0_c, nPbW_c, nPbH_c, &current_mv,
-                          s->sh.chroma_weight_l0[current_mv.ref_idx[0]][1], s->sh.chroma_offset_l0[current_mv.ref_idx[0]][1]);
+                          lc->sh->chroma_weight_l0[current_mv.ref_idx[0]][1], lc->sh->chroma_offset_l0[current_mv.ref_idx[0]][1]);
         }
     } else if (current_mv.pred_flag == PF_L1) {
         int x0_c = x0 >> sps->hshift[1];
@@ -2187,17 +2184,17 @@ static void hls_prediction_unit(HEVCLocalContext *lc,
 
         luma_mc_uni(lc, pps, sps, dst0, linesize[0], ref1->f,
                     &current_mv.mv[1], x0, y0, nPbW, nPbH,
-                    s->sh.luma_weight_l1[current_mv.ref_idx[1]],
-                    s->sh.luma_offset_l1[current_mv.ref_idx[1]]);
+                    lc->sh->luma_weight_l1[current_mv.ref_idx[1]],
+                    lc->sh->luma_offset_l1[current_mv.ref_idx[1]]);
 
         if (sps->chroma_format_idc) {
             chroma_mc_uni(lc, pps, sps, dst1, linesize[1], ref1->f->data[1], ref1->f->linesize[1],
                           1, x0_c, y0_c, nPbW_c, nPbH_c, &current_mv,
-                          s->sh.chroma_weight_l1[current_mv.ref_idx[1]][0], s->sh.chroma_offset_l1[current_mv.ref_idx[1]][0]);
+                          lc->sh->chroma_weight_l1[current_mv.ref_idx[1]][0], lc->sh->chroma_offset_l1[current_mv.ref_idx[1]][0]);
 
             chroma_mc_uni(lc, pps, sps, dst2, linesize[2], ref1->f->data[2], ref1->f->linesize[2],
                           1, x0_c, y0_c, nPbW_c, nPbH_c, &current_mv,
-                          s->sh.chroma_weight_l1[current_mv.ref_idx[1]][1], s->sh.chroma_offset_l1[current_mv.ref_idx[1]][1]);
+                          lc->sh->chroma_weight_l1[current_mv.ref_idx[1]][1], lc->sh->chroma_offset_l1[current_mv.ref_idx[1]][1]);
         }
     } else if (current_mv.pred_flag == PF_BI) {
         int x0_c = x0 >> sps->hshift[1];
@@ -2448,7 +2445,7 @@ static int hls_coding_unit(HEVCLocalContext *lc, const HEVCContext *s,
     } else
         lc->cu.cu_transquant_bypass_flag = 0;
 
-    if (s->sh.slice_type != HEVC_SLICE_I) {
+    if (lc->sh->slice_type != HEVC_SLICE_I) {
         const int x0b = av_zero_extend(x0, sps->log2_ctb_size);
         const int y0b = av_zero_extend(y0, sps->log2_ctb_size);
         uint8_t skip_flag = ff_hevc_skip_flag_decode(lc, l->skip_flag,
@@ -2474,12 +2471,12 @@ static int hls_coding_unit(HEVCLocalContext *lc, const HEVCContext *s,
                             x0, y0, cb_size, cb_size, log2_cb_size, 0, idx);
         intra_prediction_unit_default_value(lc, l, sps, x0, y0, log2_cb_size);
 
-        if (!s->sh.disable_deblocking_filter_flag)
+        if (!lc->sh->disable_deblocking_filter_flag)
             ff_hevc_deblocking_boundary_strengths(lc, l, pps, x0, y0, log2_cb_size);
     } else {
         int pcm_flag = 0;
 
-        if (s->sh.slice_type != HEVC_SLICE_I)
+        if (lc->sh->slice_type != HEVC_SLICE_I)
             lc->cu.pred_mode = ff_hevc_pred_mode_decode(lc);
         if (lc->cu.pred_mode != MODE_INTRA ||
             log2_cb_size == sps->log2_min_cb_size) {
@@ -2579,7 +2576,7 @@ static int hls_coding_unit(HEVCLocalContext *lc, const HEVCContext *s,
                 if (ret < 0)
                     return ret;
             } else {
-                if (!s->sh.disable_deblocking_filter_flag)
+                if (!lc->sh->disable_deblocking_filter_flag)
                     ff_hevc_deblocking_boundary_strengths(lc, l, pps, x0, y0, log2_cb_size);
             }
         }
@@ -2630,7 +2627,7 @@ static int hls_coding_quadtree(HEVCLocalContext *lc,
         lc->tu.cu_qp_delta          = 0;
     }
 
-    if (s->sh.cu_chroma_qp_offset_enabled_flag &&
+    if (lc->sh->cu_chroma_qp_offset_enabled_flag &&
         log2_cb_size >= sps->log2_ctb_size - pps->diff_cu_chroma_qp_offset_depth) {
         lc->tu.is_cu_chroma_qp_offset_coded = 0;
     }
@@ -2702,12 +2699,11 @@ static void hls_decode_neighbour(HEVCLocalContext *lc,
                                  const HEVCPPS *pps, const HEVCSPS *sps,
                                  int x_ctb, int y_ctb, int ctb_addr_ts)
 {
-    const HEVCContext *const s = lc->parent;
     int ctb_size          = 1 << sps->log2_ctb_size;
     int ctb_addr_rs       = pps->ctb_addr_ts_to_rs[ctb_addr_ts];
-    int ctb_addr_in_slice = ctb_addr_rs - s->sh.slice_addr;
+    int ctb_addr_in_slice = ctb_addr_rs - lc->sh->slice_addr;
 
-    l->tab_slice_address[ctb_addr_rs] = s->sh.slice_addr;
+    l->tab_slice_address[ctb_addr_rs] = lc->sh->slice_addr;
 
     if (pps->entropy_coding_sync_enabled_flag) {
         if (x_ctb == 0 && (y_ctb & (ctb_size - 1)) == 0)
@@ -2754,13 +2750,13 @@ static int hls_decode_entry(HEVCContext *s, GetBitContext *gb)
     const HEVCLayerContext *const l = &s->layers[s->cur_layer];
     const HEVCPPS   *const pps = s->pps;
     const HEVCSPS   *const sps = pps->sps;
-    const uint8_t *slice_data = gb->buffer + s->sh.data_offset;
-    const size_t   slice_size = get_bits_bytesize(gb, 1) - s->sh.data_offset;
+    const uint8_t *slice_data = gb->buffer + lc->sh->data_offset;
+    const size_t   slice_size = get_bits_bytesize(gb, 1) - lc->sh->data_offset;
     int ctb_size    = 1 << sps->log2_ctb_size;
     int more_data   = 1;
     int x_ctb       = 0;
     int y_ctb       = 0;
-    int ctb_addr_ts = pps->ctb_addr_rs_to_ts[s->sh.slice_ctb_addr_rs];
+    int ctb_addr_ts = pps->ctb_addr_rs_to_ts[lc->sh->slice_ctb_addr_rs];
     int ret;
 
     while (more_data && ctb_addr_ts < sps->ctb_size) {
@@ -2779,9 +2775,9 @@ static int hls_decode_entry(HEVCContext *s, GetBitContext *gb)
         hls_sao_param(lc, l, pps, sps,
                       x_ctb >> sps->log2_ctb_size, y_ctb >> sps->log2_ctb_size);
 
-        l->deblock[ctb_addr_rs].beta_offset = s->sh.beta_offset;
-        l->deblock[ctb_addr_rs].tc_offset   = s->sh.tc_offset;
-        l->filter_slice_edges[ctb_addr_rs]  = s->sh.slice_loop_filter_across_slices_enabled_flag;
+        l->deblock[ctb_addr_rs].beta_offset = lc->sh->beta_offset;
+        l->deblock[ctb_addr_rs].tc_offset   = lc->sh->tc_offset;
+        l->filter_slice_edges[ctb_addr_rs]  = lc->sh->slice_loop_filter_across_slices_enabled_flag;
 
         more_data = hls_coding_quadtree(lc, l, pps, sps, x_ctb, y_ctb, sps->log2_ctb_size, 0);
         if (more_data < 0) {
@@ -2813,11 +2809,11 @@ static int hls_decode_entry_wpp(AVCodecContext *avctx, void *hevc_lclist,
     int ctb_size    = 1 << sps->log2_ctb_size;
     int more_data   = 1;
     int ctb_row = job;
-    int ctb_addr_rs = s->sh.slice_ctb_addr_rs + ctb_row * ((sps->width + ctb_size - 1) >> sps->log2_ctb_size);
+    int ctb_addr_rs = lc->sh->slice_ctb_addr_rs + ctb_row * ((sps->width + ctb_size - 1) >> sps->log2_ctb_size);
     int ctb_addr_ts = pps->ctb_addr_rs_to_ts[ctb_addr_rs];
 
-    const uint8_t *data      = s->data + s->sh.offset[ctb_row];
-    const size_t   data_size = s->sh.size[ctb_row];
+    const uint8_t *data      = s->data + lc->sh->offset[ctb_row];
+    const size_t   data_size = lc->sh->size[ctb_row];
 
     int progress = 0;
 
@@ -2850,9 +2846,9 @@ static int hls_decode_entry_wpp(AVCodecContext *avctx, void *hevc_lclist,
         hls_sao_param(lc, l, pps, sps,
                       x_ctb >> sps->log2_ctb_size, y_ctb >> sps->log2_ctb_size);
 
-        l->deblock[ctb_addr_rs].beta_offset = s->sh.beta_offset;
-        l->deblock[ctb_addr_rs].tc_offset   = s->sh.tc_offset;
-        l->filter_slice_edges[ctb_addr_rs]  = s->sh.slice_loop_filter_across_slices_enabled_flag;
+        l->deblock[ctb_addr_rs].beta_offset = lc->sh->beta_offset;
+        l->deblock[ctb_addr_rs].tc_offset   = lc->sh->tc_offset;
+        l->filter_slice_edges[ctb_addr_rs]  = lc->sh->slice_loop_filter_across_slices_enabled_flag;
 
         more_data = hls_coding_quadtree(lc, l, pps, sps, x_ctb, y_ctb, sps->log2_ctb_size, 0);
 
@@ -2867,7 +2863,7 @@ static int hls_decode_entry_wpp(AVCodecContext *avctx, void *hevc_lclist,
         ff_thread_progress_report(&s->wpp_progress[ctb_row], ++progress);
         ff_hevc_hls_filters(lc, l, pps, x_ctb, y_ctb, ctb_size);
 
-        if (!more_data && (x_ctb+ctb_size) < sps->width && ctb_row != s->sh.num_entry_point_offsets) {
+        if (!more_data && (x_ctb+ctb_size) < sps->width && ctb_row != lc->sh->num_entry_point_offsets) {
             /* Casting const away here is safe, because it is an atomic operation. */
             atomic_store((atomic_int*)&s->wpp_err, 1);
             ff_thread_progress_report(&s->wpp_progress[ctb_row], INT_MAX);
@@ -3001,6 +2997,9 @@ static int hls_slice_data_wpp(HEVCContext *s, const H2645NAL *nal)
 
     s->data = data;
 
+    for (unsigned i = 0; i < s->nb_local_ctx; i++)
+        s->local_ctx[i].sh = &s->sh;
+
     for (unsigned i = 1; i < s->nb_local_ctx; i++) {
         s->local_ctx[i].first_qp_group = 1;
         s->local_ctx[i].qp_y = s->local_ctx[0].qp_y;
@@ -3063,6 +3062,7 @@ static int decode_slice_data(HEVCContext *s, const HEVCLayerContext *l,
         }
     }
 
+    s->local_ctx[0].sh             = &s->sh;
     s->local_ctx[0].first_qp_group = !s->sh.dependent_slice_segment_flag;
 
     if (!pps->cu_qp_delta_enabled_flag)
diff --git a/libavcodec/hevc/hevcdec.h b/libavcodec/hevc/hevcdec.h
index 8394740c4b..f9feaf91da 100644
--- a/libavcodec/hevc/hevcdec.h
+++ b/libavcodec/hevc/hevcdec.h
@@ -412,6 +412,8 @@ typedef struct HEVCLocalContext {
      */
     HEVCCABACState *common_cabac_state;
 
+    const SliceHeader *sh;
+
     int8_t qp_y;
     int8_t curr_qp_y;
 
diff --git a/libavcodec/hevc/mvs.c b/libavcodec/hevc/mvs.c
index 55f115ad0c..50e6fd69cd 100644
--- a/libavcodec/hevc/mvs.c
+++ b/libavcodec/hevc/mvs.c
@@ -319,8 +319,8 @@ static void derive_spatial_merge_candidates(HEVCLocalContext *lc, const HEVCCont
     const int xB2    = x0 - 1;
     const int yB2    = y0 - 1;
 
-    const int nb_refs = (s->sh.slice_type == HEVC_SLICE_P) ?
-                        s->sh.nb_refs[0] : FFMIN(s->sh.nb_refs[0], s->sh.nb_refs[1]);
+    const int nb_refs = (lc->sh->slice_type == HEVC_SLICE_P) ?
+                        lc->sh->nb_refs[0] : FFMIN(lc->sh->nb_refs[0], lc->sh->nb_refs[1]);
 
     int zero_idx = 0;
 
@@ -410,12 +410,12 @@ static void derive_spatial_merge_candidates(HEVCLocalContext *lc, const HEVCCont
     }
 
     // temporal motion vector candidate
-    if (s->sh.slice_temporal_mvp_enabled_flag &&
-        nb_merge_cand < s->sh.max_num_merge_cand) {
+    if (lc->sh->slice_temporal_mvp_enabled_flag &&
+        nb_merge_cand < lc->sh->max_num_merge_cand) {
         Mv mv_l0_col = { 0 }, mv_l1_col = { 0 };
         int available_l0 = temporal_luma_motion_vector(s, sps, x0, y0, nPbW, nPbH,
                                                        0, &mv_l0_col, 0);
-        int available_l1 = (s->sh.slice_type == HEVC_SLICE_B) ?
+        int available_l1 = (lc->sh->slice_type == HEVC_SLICE_B) ?
                            temporal_luma_motion_vector(s, sps, x0, y0, nPbW, nPbH,
                                                        0, &mv_l1_col, 1) : 0;
 
@@ -434,11 +434,11 @@ static void derive_spatial_merge_candidates(HEVCLocalContext *lc, const HEVCCont
     nb_orig_merge_cand = nb_merge_cand;
 
     // combined bi-predictive merge candidates  (applies for B slices)
-    if (s->sh.slice_type == HEVC_SLICE_B && nb_orig_merge_cand > 1 &&
-        nb_orig_merge_cand < s->sh.max_num_merge_cand) {
+    if (lc->sh->slice_type == HEVC_SLICE_B && nb_orig_merge_cand > 1 &&
+        nb_orig_merge_cand < lc->sh->max_num_merge_cand) {
         int comb_idx = 0;
 
-        for (comb_idx = 0; nb_merge_cand < s->sh.max_num_merge_cand &&
+        for (comb_idx = 0; nb_merge_cand < lc->sh->max_num_merge_cand &&
                            comb_idx < nb_orig_merge_cand * (nb_orig_merge_cand - 1); comb_idx++) {
             int l0_cand_idx = l0_l1_cand_idx[comb_idx][0];
             int l1_cand_idx = l0_l1_cand_idx[comb_idx][1];
@@ -462,8 +462,8 @@ static void derive_spatial_merge_candidates(HEVCLocalContext *lc, const HEVCCont
     }
 
     // append Zero motion vector candidates
-    while (nb_merge_cand < s->sh.max_num_merge_cand) {
-        mergecandlist[nb_merge_cand].pred_flag    = PF_L0 + ((s->sh.slice_type == HEVC_SLICE_B) << 1);
+    while (nb_merge_cand < lc->sh->max_num_merge_cand) {
+        mergecandlist[nb_merge_cand].pred_flag    = PF_L0 + ((lc->sh->slice_type == HEVC_SLICE_B) << 1);
         AV_ZERO32(mergecandlist[nb_merge_cand].mv + 0);
         AV_ZERO32(mergecandlist[nb_merge_cand].mv + 1);
         mergecandlist[nb_merge_cand].ref_idx[0]   = zero_idx < nb_refs ? zero_idx : 0;
@@ -769,7 +769,7 @@ scalef:
         mvpcand_list[numMVPCandLX++] = mxB;
 
     //temporal motion vector prediction candidate
-    if (numMVPCandLX < 2 && s->sh.slice_temporal_mvp_enabled_flag &&
+    if (numMVPCandLX < 2 && lc->sh->slice_temporal_mvp_enabled_flag &&
         mvp_lx_flag == numMVPCandLX) {
         Mv mv_col;
         int available_col = temporal_luma_motion_vector(s, sps, x0, y0, nPbW,
-- 
2.52.0


>From c361da9bb3b245d8f47080f63da16d800a4f5fc0 Mon Sep 17 00:00:00 2001
From: Phillip Blucas <[email protected]>
Date: Wed, 22 Jul 2026 15:44:05 -0500
Subject: [PATCH 2/2] avcodec/hevc: add NAL slice threading

Pre-scan decode_nal_units() to count independent slice NALs per frame
(nb_slices_in_frame, slice_nal_idx[]).  The parallel path is guarded
against WPP, tiles, and dependent slices.  decode_slices_parallel()
copies each parsed SliceHeader into slice_hdrs[] and dispatches all
slices via avctx->execute2().  Each worker gets its own
HEVCLocalContext with lc->sh pointing to the per-slice header and
lc->refPicList from rpl_tab[first_ctb_in_slice].

Deblocking and SAO run in a serial sweep after all workers complete.
When slice_loop_filter_across_slices_enabled_flag is set, SAO reads
reconstructed pixels from neighbouring slices that may still be
decoding, so filtering cannot run inline.  The defer_slice_bs flag on
HEVCLocalContext defers cross-slice boundary-strength reads to the
post-filter pass.

Pass sh explicitly to derive_temporal_colocated_mvs() to remove the
s->sh access from parallel workers.  Add const SliceHeader *sh to
ff_hevc_hls_filter() and ff_hevc_hls_filters() so the post-filter
resolves sh per CTB from tab_slice_address.

Signed-off-by: Phillip Blucas <[email protected]>
---
 libavcodec/hevc/cabac.c   |   2 +-
 libavcodec/hevc/filter.c  | 128 ++++++++--
 libavcodec/hevc/hevcdec.c | 493 +++++++++++++++++++++++++++++++++++---
 libavcodec/hevc/hevcdec.h |  19 +-
 libavcodec/hevc/mvs.c     |  50 ++--
 libavcodec/hevc/refs.c    |   5 -
 6 files changed, 606 insertions(+), 91 deletions(-)

diff --git a/libavcodec/hevc/cabac.c b/libavcodec/hevc/cabac.c
index e5e9d395db..1c1c5392ac 100644
--- a/libavcodec/hevc/cabac.c
+++ b/libavcodec/hevc/cabac.c
@@ -738,7 +738,7 @@ int ff_hevc_merge_idx_decode(HEVCLocalContext *lc)
     int i = GET_CABAC(MERGE_IDX_OFFSET);
 
     if (i != 0) {
-        while (i < lc->parent->sh.max_num_merge_cand-1 && get_cabac_bypass(&lc->cc))
+        while (i < lc->sh->max_num_merge_cand-1 && get_cabac_bypass(&lc->cc))
             i++;
     }
     return i;
diff --git a/libavcodec/hevc/filter.c b/libavcodec/hevc/filter.c
index f4eed0dd3f..be1f5b8dde 100644
--- a/libavcodec/hevc/filter.c
+++ b/libavcodec/hevc/filter.c
@@ -676,12 +676,13 @@ static void deblocking_filter_CTB(const HEVCContext *s, const HEVCLayerContext *
 }
 
 static int boundary_strength(const HEVCContext *s, const MvField *curr, const MvField *neigh,
+                             const RefPicList *refPicList,
                              const RefPicList *neigh_refPicList)
 {
     if (curr->pred_flag == PF_BI &&  neigh->pred_flag == PF_BI) {
         // same L0 and L1
-        if (s->cur_frame->refPicList[0].list[curr->ref_idx[0]] == neigh_refPicList[0].list[neigh->ref_idx[0]]  &&
-            s->cur_frame->refPicList[0].list[curr->ref_idx[0]] == s->cur_frame->refPicList[1].list[curr->ref_idx[1]] &&
+        if (refPicList[0].list[curr->ref_idx[0]] == neigh_refPicList[0].list[neigh->ref_idx[0]]  &&
+            refPicList[0].list[curr->ref_idx[0]] == refPicList[1].list[curr->ref_idx[1]] &&
             neigh_refPicList[0].list[neigh->ref_idx[0]] == neigh_refPicList[1].list[neigh->ref_idx[1]]) {
             if ((FFABS(neigh->mv[0].x - curr->mv[0].x) >= 4 || FFABS(neigh->mv[0].y - curr->mv[0].y) >= 4 ||
                  FFABS(neigh->mv[1].x - curr->mv[1].x) >= 4 || FFABS(neigh->mv[1].y - curr->mv[1].y) >= 4) &&
@@ -690,15 +691,15 @@ static int boundary_strength(const HEVCContext *s, const MvField *curr, const Mv
                 return 1;
             else
                 return 0;
-        } else if (neigh_refPicList[0].list[neigh->ref_idx[0]] == s->cur_frame->refPicList[0].list[curr->ref_idx[0]] &&
-                   neigh_refPicList[1].list[neigh->ref_idx[1]] == s->cur_frame->refPicList[1].list[curr->ref_idx[1]]) {
+        } else if (neigh_refPicList[0].list[neigh->ref_idx[0]] == refPicList[0].list[curr->ref_idx[0]] &&
+                   neigh_refPicList[1].list[neigh->ref_idx[1]] == refPicList[1].list[curr->ref_idx[1]]) {
             if (FFABS(neigh->mv[0].x - curr->mv[0].x) >= 4 || FFABS(neigh->mv[0].y - curr->mv[0].y) >= 4 ||
                 FFABS(neigh->mv[1].x - curr->mv[1].x) >= 4 || FFABS(neigh->mv[1].y - curr->mv[1].y) >= 4)
                 return 1;
             else
                 return 0;
-        } else if (neigh_refPicList[1].list[neigh->ref_idx[1]] == s->cur_frame->refPicList[0].list[curr->ref_idx[0]] &&
-                   neigh_refPicList[0].list[neigh->ref_idx[0]] == s->cur_frame->refPicList[1].list[curr->ref_idx[1]]) {
+        } else if (neigh_refPicList[1].list[neigh->ref_idx[1]] == refPicList[0].list[curr->ref_idx[0]] &&
+                   neigh_refPicList[0].list[neigh->ref_idx[0]] == refPicList[1].list[curr->ref_idx[1]]) {
             if (FFABS(neigh->mv[1].x - curr->mv[0].x) >= 4 || FFABS(neigh->mv[1].y - curr->mv[0].y) >= 4 ||
                 FFABS(neigh->mv[0].x - curr->mv[1].x) >= 4 || FFABS(neigh->mv[0].y - curr->mv[1].y) >= 4)
                 return 1;
@@ -713,10 +714,10 @@ static int boundary_strength(const HEVCContext *s, const MvField *curr, const Mv
 
         if (curr->pred_flag & 1) {
             A     = curr->mv[0];
-            ref_A = s->cur_frame->refPicList[0].list[curr->ref_idx[0]];
+            ref_A = refPicList[0].list[curr->ref_idx[0]];
         } else {
             A     = curr->mv[1];
-            ref_A = s->cur_frame->refPicList[1].list[curr->ref_idx[1]];
+            ref_A = refPicList[1].list[curr->ref_idx[1]];
         }
 
         if (neigh->pred_flag & 1) {
@@ -739,6 +740,82 @@ static int boundary_strength(const HEVCContext *s, const MvField *curr, const Mv
     return 1;
 }
 
+void ff_hevc_deblocking_bs_left_edge(HEVCLocalContext *lc, const HEVCLayerContext *l,
+                                     const HEVCPPS *pps,
+                                     int x0, int y0, int log2_size)
+{
+    const HEVCSPS *const sps = pps->sps;
+    const HEVCContext *const s = lc->parent;
+    const MvField *tab_mvf = s->cur_frame->tab_mvf;
+    int log2_min_pu_size = sps->log2_min_pu_size;
+    int log2_min_tu_size = sps->log2_min_tb_size;
+    int min_pu_width     = sps->min_pu_width;
+    int min_tu_width     = sps->min_tb_width;
+    const RefPicList *rpl_left = (lc->boundary_flags & BOUNDARY_LEFT_SLICE) ?
+                                 ff_hevc_get_ref_list(s->cur_frame, x0 - 1, y0) :
+                                 lc->refPicList;
+    int xp_pu = (x0 - 1) >> log2_min_pu_size;
+    int xq_pu =  x0      >> log2_min_pu_size;
+    int xp_tu = (x0 - 1) >> log2_min_tu_size;
+    int xq_tu =  x0      >> log2_min_tu_size;
+    int i, bs;
+
+    for (i = 0; i < FFMIN(1 << log2_size, sps->height - y0); i += 4) {
+        int y_pu = (y0 + i) >> log2_min_pu_size;
+        int y_tu = (y0 + i) >> log2_min_tu_size;
+        const MvField *left = &tab_mvf[y_pu * min_pu_width + xp_pu];
+        const MvField *curr = &tab_mvf[y_pu * min_pu_width + xq_pu];
+        uint8_t left_cbf_luma = l->cbf_luma[y_tu * min_tu_width + xp_tu];
+        uint8_t curr_cbf_luma = l->cbf_luma[y_tu * min_tu_width + xq_tu];
+
+        if (curr->pred_flag == PF_INTRA || left->pred_flag == PF_INTRA)
+            bs = 2;
+        else if (curr_cbf_luma || left_cbf_luma)
+            bs = 1;
+        else
+            bs = boundary_strength(s, curr, left, lc->refPicList, rpl_left);
+        l->vertical_bs[(x0 + (y0 + i) * l->bs_width) >> 2] = bs;
+    }
+}
+
+void ff_hevc_deblocking_bs_top_edge(HEVCLocalContext *lc, const HEVCLayerContext *l,
+                                    const HEVCPPS *pps,
+                                    int x0, int y0, int log2_size)
+{
+    const HEVCSPS *const sps = pps->sps;
+    const HEVCContext *const s = lc->parent;
+    const MvField *tab_mvf = s->cur_frame->tab_mvf;
+    int log2_min_pu_size = sps->log2_min_pu_size;
+    int log2_min_tu_size = sps->log2_min_tb_size;
+    int min_pu_width     = sps->min_pu_width;
+    int min_tu_width     = sps->min_tb_width;
+    const RefPicList *rpl_top = (lc->boundary_flags & BOUNDARY_UPPER_SLICE) ?
+                                ff_hevc_get_ref_list(s->cur_frame, x0, y0 - 1) :
+                                lc->refPicList;
+    int yp_pu = (y0 - 1) >> log2_min_pu_size;
+    int yq_pu =  y0      >> log2_min_pu_size;
+    int yp_tu = (y0 - 1) >> log2_min_tu_size;
+    int yq_tu =  y0      >> log2_min_tu_size;
+    int i, bs;
+
+    for (i = 0; i < FFMIN(1 << log2_size, sps->width - x0); i += 4) {
+        int x_pu = (x0 + i) >> log2_min_pu_size;
+        int x_tu = (x0 + i) >> log2_min_tu_size;
+        const MvField *top  = &tab_mvf[yp_pu * min_pu_width + x_pu];
+        const MvField *curr = &tab_mvf[yq_pu * min_pu_width + x_pu];
+        uint8_t top_cbf_luma  = l->cbf_luma[yp_tu * min_tu_width + x_tu];
+        uint8_t curr_cbf_luma = l->cbf_luma[yq_tu * min_tu_width + x_tu];
+
+        if (curr->pred_flag == PF_INTRA || top->pred_flag == PF_INTRA)
+            bs = 2;
+        else if (curr_cbf_luma || top_cbf_luma)
+            bs = 1;
+        else
+            bs = boundary_strength(s, curr, top, lc->refPicList, rpl_top);
+        l->horizontal_bs[((x0 + i) + y0 * l->bs_width) >> 2] = bs;
+    }
+}
+
 void ff_hevc_deblocking_boundary_strengths(HEVCLocalContext *lc, const HEVCLayerContext *l,
                                            const HEVCPPS *pps,
                                            int x0, int y0, int log2_trafo_size)
@@ -764,11 +841,15 @@ void ff_hevc_deblocking_boundary_strengths(HEVCLocalContext *lc, const HEVCLayer
           lc->boundary_flags & BOUNDARY_UPPER_TILE &&
           (y0 % (1 << sps->log2_ctb_size)) == 0)))
         boundary_upper = 0;
+    /* defer cross-slice top-edge BS to the post-filter in parallel path. */
+    if (boundary_upper && (lc->boundary_flags & BOUNDARY_UPPER_SLICE) &&
+        (y0 % (1 << sps->log2_ctb_size)) == 0 && lc->defer_slice_bs)
+        boundary_upper = 0;
 
     if (boundary_upper) {
         const RefPicList *rpl_top = (lc->boundary_flags & BOUNDARY_UPPER_SLICE) ?
                                     ff_hevc_get_ref_list(s->cur_frame, x0, y0 - 1) :
-                                    s->cur_frame->refPicList;
+                                    lc->refPicList;
         int yp_pu = (y0 - 1) >> log2_min_pu_size;
         int yq_pu =  y0      >> log2_min_pu_size;
         int yp_tu = (y0 - 1) >> log2_min_tu_size;
@@ -787,7 +868,7 @@ void ff_hevc_deblocking_boundary_strengths(HEVCLocalContext *lc, const HEVCLayer
                 else if (curr_cbf_luma || top_cbf_luma)
                     bs = 1;
                 else
-                    bs = boundary_strength(s, curr, top, rpl_top);
+                    bs = boundary_strength(s, curr, top, lc->refPicList, rpl_top);
                 l->horizontal_bs[((x0 + i) + y0 * l->bs_width) >> 2] = bs;
             }
     }
@@ -802,11 +883,14 @@ void ff_hevc_deblocking_boundary_strengths(HEVCLocalContext *lc, const HEVCLayer
           lc->boundary_flags & BOUNDARY_LEFT_TILE &&
           (x0 % (1 << sps->log2_ctb_size)) == 0)))
         boundary_left = 0;
+    if (boundary_left && (lc->boundary_flags & BOUNDARY_LEFT_SLICE) &&
+        (x0 % (1 << sps->log2_ctb_size)) == 0 && lc->defer_slice_bs)
+        boundary_left = 0;
 
     if (boundary_left) {
         const RefPicList *rpl_left = (lc->boundary_flags & BOUNDARY_LEFT_SLICE) ?
                                      ff_hevc_get_ref_list(s->cur_frame, x0 - 1, y0) :
-                                     s->cur_frame->refPicList;
+                                     lc->refPicList;
         int xp_pu = (x0 - 1) >> log2_min_pu_size;
         int xq_pu =  x0      >> log2_min_pu_size;
         int xp_tu = (x0 - 1) >> log2_min_tu_size;
@@ -825,13 +909,13 @@ void ff_hevc_deblocking_boundary_strengths(HEVCLocalContext *lc, const HEVCLayer
                 else if (curr_cbf_luma || left_cbf_luma)
                     bs = 1;
                 else
-                    bs = boundary_strength(s, curr, left, rpl_left);
+                    bs = boundary_strength(s, curr, left, lc->refPicList, rpl_left);
                 l->vertical_bs[(x0 + (y0 + i) * l->bs_width) >> 2] = bs;
             }
     }
 
     if (log2_trafo_size > log2_min_pu_size && !is_intra) {
-        const RefPicList *rpl = s->cur_frame->refPicList;
+        const RefPicList *rpl = lc->refPicList;
 
         // bs for TU internal horizontal PU boundaries
         for (j = 8; j < (1 << log2_trafo_size); j += 8) {
@@ -843,7 +927,7 @@ void ff_hevc_deblocking_boundary_strengths(HEVCLocalContext *lc, const HEVCLayer
                 const MvField *top  = &tab_mvf[yp_pu * min_pu_width + x_pu];
                 const MvField *curr = &tab_mvf[yq_pu * min_pu_width + x_pu];
 
-                bs = boundary_strength(s, curr, top, rpl);
+                bs = boundary_strength(s, curr, top, lc->refPicList, rpl);
                 l->horizontal_bs[((x0 + i) + (y0 + j) * l->bs_width) >> 2] = bs;
             }
         }
@@ -858,7 +942,7 @@ void ff_hevc_deblocking_boundary_strengths(HEVCLocalContext *lc, const HEVCLayer
                 const MvField *left = &tab_mvf[y_pu * min_pu_width + xp_pu];
                 const MvField *curr = &tab_mvf[y_pu * min_pu_width + xq_pu];
 
-                bs = boundary_strength(s, curr, left, rpl);
+                bs = boundary_strength(s, curr, left, lc->refPicList, rpl);
                 l->vertical_bs[((x0 + i) + (y0 + j) * l->bs_width) >> 2] = bs;
             }
         }
@@ -870,7 +954,7 @@ void ff_hevc_deblocking_boundary_strengths(HEVCLocalContext *lc, const HEVCLayer
 #undef CR
 
 void ff_hevc_hls_filter(HEVCLocalContext *lc, const HEVCLayerContext *l,
-                        const HEVCPPS *pps,
+                        const HEVCPPS *pps, const SliceHeader *sh,
                         int x, int y, int ctb_size)
 {
     const HEVCSPS   *const sps = pps->sps;
@@ -880,9 +964,9 @@ void ff_hevc_hls_filter(HEVCLocalContext *lc, const HEVCLayerContext *l,
     if (s->avctx->skip_loop_filter >= AVDISCARD_ALL ||
         (s->avctx->skip_loop_filter >= AVDISCARD_NONKEY && !IS_IDR(s)) ||
         (s->avctx->skip_loop_filter >= AVDISCARD_NONINTRA &&
-         lc->sh->slice_type != HEVC_SLICE_I) ||
+         sh->slice_type != HEVC_SLICE_I) ||
         (s->avctx->skip_loop_filter >= AVDISCARD_BIDIR &&
-         lc->sh->slice_type == HEVC_SLICE_B) ||
+         sh->slice_type == HEVC_SLICE_B) ||
         (s->avctx->skip_loop_filter >= AVDISCARD_NONREF &&
         ff_hevc_nal_is_nonref(s->nal_unit_type)))
         skip = 1;
@@ -910,15 +994,15 @@ void ff_hevc_hls_filter(HEVCLocalContext *lc, const HEVCLayerContext *l,
 }
 
 void ff_hevc_hls_filters(HEVCLocalContext *lc, const HEVCLayerContext *l,
-                         const HEVCPPS *pps,
+                         const HEVCPPS *pps, const SliceHeader *sh,
                          int x_ctb, int y_ctb, int ctb_size)
 {
     int x_end = x_ctb >= pps->sps->width  - ctb_size;
     int y_end = y_ctb >= pps->sps->height - ctb_size;
     if (y_ctb && x_ctb)
-        ff_hevc_hls_filter(lc, l, pps, x_ctb - ctb_size, y_ctb - ctb_size, ctb_size);
+        ff_hevc_hls_filter(lc, l, pps, sh, x_ctb - ctb_size, y_ctb - ctb_size, ctb_size);
     if (y_ctb && x_end)
-        ff_hevc_hls_filter(lc, l, pps, x_ctb, y_ctb - ctb_size, ctb_size);
+        ff_hevc_hls_filter(lc, l, pps, sh, x_ctb, y_ctb - ctb_size, ctb_size);
     if (x_ctb && y_end)
-        ff_hevc_hls_filter(lc, l, pps, x_ctb - ctb_size, y_ctb, ctb_size);
+        ff_hevc_hls_filter(lc, l, pps, sh, x_ctb - ctb_size, y_ctb, ctb_size);
 }
diff --git a/libavcodec/hevc/hevcdec.c b/libavcodec/hevc/hevcdec.c
index ec310700a2..f5e135aa63 100644
--- a/libavcodec/hevc/hevcdec.c
+++ b/libavcodec/hevc/hevcdec.c
@@ -2106,7 +2106,7 @@ static void hls_prediction_unit(HEVCLocalContext *lc,
     int min_pu_width = sps->min_pu_width;
 
     MvField *tab_mvf = s->cur_frame->tab_mvf;
-    const RefPicList *refPicList = s->cur_frame->refPicList;
+    const RefPicList *refPicList = lc->refPicList;
     const HEVCFrame *ref0 = NULL, *ref1 = NULL;
     const int *linesize = s->cur_frame->f->linesize;
     uint8_t *dst0 = s->cur_frame->f->data[0] + y0 * linesize[0] + (x0 << sps->pixel_shift);
@@ -2744,29 +2744,26 @@ static void hls_decode_neighbour(HEVCLocalContext *lc,
     lc->ctb_up_left_flag = ((x_ctb > 0) && (y_ctb > 0)  && (ctb_addr_in_slice-1 >= sps->ctb_width) && (pps->tile_id[ctb_addr_ts] == pps->tile_id[pps->ctb_addr_rs_to_ts[ctb_addr_rs-1 - sps->ctb_width]]));
 }
 
-static int hls_decode_entry(HEVCContext *s, GetBitContext *gb)
+static int hls_decode_slice_ctbs(HEVCLocalContext *lc,
+                                 const HEVCLayerContext *l,
+                                 const HEVCPPS *pps, const HEVCSPS *sps,
+                                 const uint8_t *cabac_data, size_t cabac_size,
+                                 int end_ctb_ts, int filter)
 {
-    HEVCLocalContext *const lc = &s->local_ctx[0];
-    const HEVCLayerContext *const l = &s->layers[s->cur_layer];
-    const HEVCPPS   *const pps = s->pps;
-    const HEVCSPS   *const sps = pps->sps;
-    const uint8_t *slice_data = gb->buffer + lc->sh->data_offset;
-    const size_t   slice_size = get_bits_bytesize(gb, 1) - lc->sh->data_offset;
-    int ctb_size    = 1 << sps->log2_ctb_size;
-    int more_data   = 1;
-    int x_ctb       = 0;
-    int y_ctb       = 0;
+    int ctb_size  = 1 << sps->log2_ctb_size;
+    int more_data = 1;
+    int x_ctb     = 0;
+    int y_ctb     = 0;
     int ctb_addr_ts = pps->ctb_addr_rs_to_ts[lc->sh->slice_ctb_addr_rs];
-    int ret;
 
-    while (more_data && ctb_addr_ts < sps->ctb_size) {
+    while (more_data && ctb_addr_ts < end_ctb_ts) {
         int ctb_addr_rs = pps->ctb_addr_ts_to_rs[ctb_addr_ts];
 
         x_ctb = (ctb_addr_rs % ((sps->width + ctb_size - 1) >> sps->log2_ctb_size)) << sps->log2_ctb_size;
         y_ctb = (ctb_addr_rs / ((sps->width + ctb_size - 1) >> sps->log2_ctb_size)) << sps->log2_ctb_size;
         hls_decode_neighbour(lc, l, pps, sps, x_ctb, y_ctb, ctb_addr_ts);
-
-        ret = ff_hevc_cabac_init(lc, pps, ctb_addr_ts, slice_data, slice_size, 0);
+        int ret = ff_hevc_cabac_init(lc, pps, ctb_addr_ts,
+                                     cabac_data, cabac_size, 0);
         if (ret < 0) {
             l->tab_slice_address[ctb_addr_rs] = -1;
             return ret;
@@ -2778,26 +2775,194 @@ static int hls_decode_entry(HEVCContext *s, GetBitContext *gb)
         l->deblock[ctb_addr_rs].beta_offset = lc->sh->beta_offset;
         l->deblock[ctb_addr_rs].tc_offset   = lc->sh->tc_offset;
         l->filter_slice_edges[ctb_addr_rs]  = lc->sh->slice_loop_filter_across_slices_enabled_flag;
-
-        more_data = hls_coding_quadtree(lc, l, pps, sps, x_ctb, y_ctb, sps->log2_ctb_size, 0);
+        more_data = hls_coding_quadtree(lc, l, pps, sps, x_ctb, y_ctb,
+                                        sps->log2_ctb_size, 0);
         if (more_data < 0) {
             l->tab_slice_address[ctb_addr_rs] = -1;
             return more_data;
         }
 
-
         ctb_addr_ts++;
         ff_hevc_save_states(lc, pps, ctb_addr_ts);
-        ff_hevc_hls_filters(lc, l, pps, x_ctb, y_ctb, ctb_size);
+
+        /* parallel workers pass filter=0 and defer to the post-filter pass */
+        if (filter)
+            ff_hevc_hls_filters(lc, l, pps, lc->sh, x_ctb, y_ctb, ctb_size);
     }
 
-    if (x_ctb + ctb_size >= sps->width &&
+    if (filter &&
+        x_ctb + ctb_size >= sps->width &&
         y_ctb + ctb_size >= sps->height)
-        ff_hevc_hls_filter(lc, l, pps, x_ctb, y_ctb, ctb_size);
+        ff_hevc_hls_filter(lc, l, pps, lc->sh, x_ctb, y_ctb, ctb_size);
 
     return ctb_addr_ts;
 }
 
+static int hls_decode_entry_slice(AVCodecContext *avctx, void *hevc_lclist,
+                                  int job, int thread)
+{
+    HEVCLocalContext *lc = &((HEVCLocalContext *)hevc_lclist)[thread];
+    const HEVCContext *const s = lc->parent;
+    const HEVCLayerContext *const l = &s->layers[s->cur_layer];
+    const HEVCPPS *const pps = s->pps;
+    const HEVCSPS *const sps = pps->sps;
+    const H2645NAL *nal = &s->pkt.nals[s->slice_nal_idx[job]];
+    int end_ctb_ts;
+
+    lc->sh = &s->slice_hdrs[job];
+    lc->refPicList =
+        (const RefPicList *)s->cur_frame->rpl_tab[pps->ctb_addr_rs_to_ts[
+            lc->sh->slice_ctb_addr_rs]];
+    lc->first_qp_group = 1;
+    lc->qPy_pred       = lc->sh->slice_qp;
+    if (!pps->cu_qp_delta_enabled_flag)
+        lc->qp_y = lc->sh->slice_qp;
+    lc->tu.cu_qp_offset_cb = 0;
+    lc->tu.cu_qp_offset_cr = 0;
+
+    lc->defer_slice_bs = 1;
+
+    /* stop at the next slice so a corrupt end_of_slice cannot cross into it */
+    end_ctb_ts = (job + 1 < s->nb_slices_in_frame)
+        ? pps->ctb_addr_rs_to_ts[s->slice_hdrs[job + 1].slice_ctb_addr_rs]
+        : sps->ctb_size;
+
+    return hls_decode_slice_ctbs(lc, l, pps, sps,
+                                 nal->data + lc->sh->data_offset,
+                                 nal->size - lc->sh->data_offset,
+                                 end_ctb_ts, 0);
+}
+
+static int hevc_decode_slices_mt(HEVCContext *s)
+{
+    const HEVCPPS *const pps = s->pps;
+    const HEVCSPS *const sps = pps->sps;
+    HEVCLayerContext *const l = &s->layers[s->cur_layer];
+    int ctb_size = 1 << sps->log2_ctb_size;
+    unsigned nb_slices = s->nb_slices_in_frame;
+    int ret;
+
+    {
+        unsigned grow_to = FFMAX(nb_slices, s->avctx->thread_count);
+
+        if (grow_to > s->nb_local_ctx) {
+            HEVCLocalContext *tmp = av_malloc_array(grow_to,
+                                                    sizeof(*s->local_ctx));
+
+            if (!tmp)
+                return AVERROR(ENOMEM);
+
+            memcpy(tmp, s->local_ctx,
+                   sizeof(*s->local_ctx) * s->nb_local_ctx);
+            av_free(s->local_ctx);
+            s->local_ctx = tmp;
+
+            for (unsigned i = s->nb_local_ctx; i < grow_to; i++) {
+                memset(&s->local_ctx[i], 0, sizeof(s->local_ctx[i]));
+                s->local_ctx[i].logctx             = s->avctx;
+                s->local_ctx[i].parent             = s;
+                s->local_ctx[i].common_cabac_state = &s->cabac;
+            }
+
+            s->nb_local_ctx = grow_to;
+        }
+    }
+
+    /* Pre-fill tab_slice_address for every CTB so that cross-slice spatial
+     * availability checks in motion-vector derivation see the correct slice
+     * address before the owning worker has decoded that CTB.  Without this,
+     * workers for slice 0 (slice_addr==0) see the zero-initialised array as
+     * "same slice" and read unwritten tab_mvf entries from other slices. */
+    for (unsigned si = 0; si < nb_slices; si++) {
+        int start_ts = pps->ctb_addr_rs_to_ts[s->slice_hdrs[si].slice_ctb_addr_rs];
+        int end_ts   = (si + 1 < nb_slices)
+                       ? pps->ctb_addr_rs_to_ts[s->slice_hdrs[si + 1].slice_ctb_addr_rs]
+                       : sps->ctb_size;
+        int slice_addr = s->slice_hdrs[si].slice_addr;
+        for (int ts = start_ts; ts < end_ts; ts++)
+            l->tab_slice_address[pps->ctb_addr_ts_to_rs[ts]] = slice_addr;
+    }
+
+    {
+        int *rets = av_malloc_array(nb_slices, sizeof(*rets));
+        if (!rets)
+            return AVERROR(ENOMEM);
+
+        ret = s->avctx->execute2(s->avctx, hls_decode_entry_slice,
+                                 s->local_ctx, rets, nb_slices);
+        for (unsigned i = 0; ret >= 0 && i < nb_slices; i++)
+            if (rets[i] < 0)
+                ret = rets[i];
+        av_free(rets);
+        if (ret < 0)
+            return ret;
+    }
+
+    /* post-filter: deblocking + SAO, run serially after all slice workers
+     * have written their reconstructed pixels */
+    for (int y = 0; y < sps->height; y += ctb_size) {
+        for (int x = 0; x < sps->width; x += ctb_size) {
+            int ctb_addr_rs = (y >> sps->log2_ctb_size) * sps->ctb_width +
+                              (x >> sps->log2_ctb_size);
+            int slice_addr  = l->tab_slice_address[ctb_addr_rs];
+            HEVCLocalContext *lc = &s->local_ctx[0];
+            const SliceHeader *sh;
+            int i;
+
+            for (i = 0; i < (int)nb_slices; i++) {
+                if (s->slice_hdrs[i].slice_addr == slice_addr)
+                    break;
+            }
+            sh = (i < (int)nb_slices) ? &s->slice_hdrs[i]
+                                      : &s->slice_hdrs[0];
+            lc->refPicList =
+                (const RefPicList *)s->cur_frame->rpl_tab[
+                    pps->ctb_addr_rs_to_ts[ctb_addr_rs]];
+
+            lc->boundary_flags = 0;
+            if (y > 0 &&
+                l->tab_slice_address[ctb_addr_rs] !=
+                l->tab_slice_address[ctb_addr_rs - sps->ctb_width])
+                lc->boundary_flags |= BOUNDARY_UPPER_SLICE;
+            if (x > 0 &&
+                l->tab_slice_address[ctb_addr_rs] !=
+                l->tab_slice_address[ctb_addr_rs - 1])
+                lc->boundary_flags |= BOUNDARY_LEFT_SLICE;
+
+            if (!sh->disable_deblocking_filter_flag &&
+                sh->slice_loop_filter_across_slices_enabled_flag) {
+                if (lc->boundary_flags & BOUNDARY_UPPER_SLICE)
+                    ff_hevc_deblocking_bs_top_edge(lc, l, pps, x, y,
+                                                   sps->log2_ctb_size);
+                if (lc->boundary_flags & BOUNDARY_LEFT_SLICE)
+                    ff_hevc_deblocking_bs_left_edge(lc, l, pps, x, y,
+                                                    sps->log2_ctb_size);
+            }
+
+            ff_hevc_hls_filter(lc, l, pps, sh, x, y, ctb_size);
+        }
+    }
+
+    return 0;
+}
+
+static int hls_decode_entry(HEVCContext *s, GetBitContext *gb)
+{
+    HEVCLocalContext *const lc = &s->local_ctx[0];
+    const HEVCLayerContext *const l = &s->layers[s->cur_layer];
+    const HEVCPPS   *const pps = s->pps;
+    const HEVCSPS   *const sps = pps->sps;
+    const uint8_t *slice_data = gb->buffer + lc->sh->data_offset;
+    const size_t   slice_size = get_bits_bytesize(gb, 1) - lc->sh->data_offset;
+    int ret;
+
+    lc->defer_slice_bs = 0;
+
+    ret = hls_decode_slice_ctbs(lc, l, pps, sps, slice_data, slice_size,
+                                sps->ctb_size, 1);
+    return ret < 0 ? ret : 0;
+}
+
 static int hls_decode_entry_wpp(AVCodecContext *avctx, void *hevc_lclist,
                                 int job, int thread)
 {
@@ -2861,7 +3026,7 @@ static int hls_decode_entry_wpp(AVCodecContext *avctx, void *hevc_lclist,
 
         ff_hevc_save_states(lc, pps, ctb_addr_ts);
         ff_thread_progress_report(&s->wpp_progress[ctb_row], ++progress);
-        ff_hevc_hls_filters(lc, l, pps, x_ctb, y_ctb, ctb_size);
+        ff_hevc_hls_filters(lc, l, pps, lc->sh, x_ctb, y_ctb, ctb_size);
 
         if (!more_data && (x_ctb+ctb_size) < sps->width && ctb_row != lc->sh->num_entry_point_offsets) {
             /* Casting const away here is safe, because it is an atomic operation. */
@@ -2871,7 +3036,7 @@ static int hls_decode_entry_wpp(AVCodecContext *avctx, void *hevc_lclist,
         }
 
         if ((x_ctb+ctb_size) >= sps->width && (y_ctb+ctb_size) >= sps->height ) {
-            ff_hevc_hls_filter(lc, l, pps, x_ctb, y_ctb, ctb_size);
+            ff_hevc_hls_filter(lc, l, pps, lc->sh, x_ctb, y_ctb, ctb_size);
             ff_thread_progress_report(&s->wpp_progress[ctb_row], INT_MAX);
             return ctb_addr_ts;
         }
@@ -2997,8 +3162,11 @@ static int hls_slice_data_wpp(HEVCContext *s, const H2645NAL *nal)
 
     s->data = data;
 
-    for (unsigned i = 0; i < s->nb_local_ctx; i++)
-        s->local_ctx[i].sh = &s->sh;
+    for (unsigned i = 0; i < s->nb_local_ctx; i++) {
+        s->local_ctx[i].sh              = &s->sh;
+        s->local_ctx[i].refPicList      = s->cur_frame->refPicList;
+        s->local_ctx[i].defer_slice_bs  = 0;
+    }
 
     for (unsigned i = 1; i < s->nb_local_ctx; i++) {
         s->local_ctx[i].first_qp_group = 1;
@@ -3063,6 +3231,7 @@ static int decode_slice_data(HEVCContext *s, const HEVCLayerContext *l,
     }
 
     s->local_ctx[0].sh             = &s->sh;
+    s->local_ctx[0].refPicList     = s->cur_frame->refPicList;
     s->local_ctx[0].first_qp_group = !s->sh.dependent_slice_segment_flag;
 
     if (!pps->cu_qp_delta_enabled_flag)
@@ -3412,7 +3581,7 @@ fail:
     if (l->cur_frame)
         ff_hevc_unref_frame(l->cur_frame, ~0);
     l->cur_frame = NULL;
-    s->cur_frame = s->collocated_ref = NULL;
+    s->cur_frame = NULL;
     return ret;
 }
 
@@ -3692,13 +3861,124 @@ static void decode_reset_recovery_point(HEVCContext *s)
     s->sei.recovery_point.has_recovery_poc = 0;
 }
 
+static int decode_slices_parallel(HEVCContext *s, int *fell_back)
+{
+    int nb_slices = s->nb_slices_in_frame;
+    SliceHeader *tmp;
+    int ret;
+
+    *fell_back = 0;
+
+    /* grow slice_hdrs[]; zero-init so hls_slice_header's av_freep is safe */
+    if (nb_slices > (int)s->nb_slice_hdrs) {
+        tmp = av_realloc_array(s->slice_hdrs, nb_slices, sizeof(*s->slice_hdrs));
+        if (!tmp)
+            return AVERROR(ENOMEM);
+        memset(tmp + s->nb_slice_hdrs, 0,
+               (nb_slices - s->nb_slice_hdrs) * sizeof(*tmp));
+        s->slice_hdrs    = tmp;
+        s->nb_slice_hdrs = nb_slices;
+    }
+
+    for (int i = 0; i < nb_slices; i++) {
+        unsigned nal_idx = s->slice_nal_idx[i];
+        H2645NAL *nal    = &s->pkt.nals[nal_idx];
+        GetBitContext gb  = nal->gb;
+        const int layer_idx = s->vps ? s->vps->layer_idx[nal->nuh_layer_id] : 0;
+        HEVCLayerContext *l;
+
+        s->nal_unit_type = nal->type;
+        s->nuh_layer_id  = nal->nuh_layer_id;
+        s->temporal_id   = nal->temporal_id;
+
+        ret = hls_slice_header(&s->sh, s, &gb);
+        s->slice_initialized = 0;
+        if (ret < 0)
+            return ret;
+
+        if (i == 0) {
+            /* s->pps is not set until hevc_frame_start(); the parallel path
+             * cannot handle these, so fall back before anything is committed */
+            const HEVCPPS *const slice_pps = s->ps.pps_list[s->sh.pps_id];
+            if (layer_idx < 0 ||
+                slice_pps->tiles_enabled_flag ||
+                slice_pps->entropy_coding_sync_enabled_flag ||
+                slice_pps->dependent_slice_segments_enabled_flag ||
+                s->avctx->profile == AV_PROFILE_HEVC_SCC) {
+                *fell_back = 1;
+                return 0;
+            }
+
+            /* picture-level discard (skip_frame / RASL), same for every slice */
+            if ((s->avctx->skip_frame >= AVDISCARD_BIDIR &&
+                 s->sh.slice_type == HEVC_SLICE_B) ||
+                (s->avctx->skip_frame >= AVDISCARD_NONINTRA &&
+                 s->sh.slice_type != HEVC_SLICE_I) ||
+                (s->avctx->skip_frame >= AVDISCARD_NONKEY && !IS_IRAP(s)) ||
+                ((s->nal_unit_type == HEVC_NAL_RASL_R ||
+                  s->nal_unit_type == HEVC_NAL_RASL_N) &&
+                 s->no_rasl_output_flag))
+                return 0;
+        }
+
+        s->cur_layer = layer_idx;
+        l = &s->layers[s->cur_layer];
+
+        if (s->sh.first_slice_in_pic_flag) {
+            if (l->cur_frame) {
+                av_log(s->avctx, AV_LOG_ERROR,
+                       "Two slices reporting being the first in the same frame.\n");
+                return AVERROR_INVALIDDATA;
+            }
+            ret = hevc_frame_start(s, l, nal_idx);
+            if (ret < 0)
+                return ret;
+        } else if (!l->cur_frame) {
+            av_log(s->avctx, AV_LOG_ERROR, "First slice in a frame missing.\n");
+            return AVERROR_INVALIDDATA;
+        }
+
+        if (s->nal_unit_type != s->first_nal_type) {
+            av_log(s->avctx, AV_LOG_ERROR,
+                   "Non-matching NAL types of the VCL NALUs: %d %d\n",
+                   s->first_nal_type, s->nal_unit_type);
+            return AVERROR_INVALIDDATA;
+        }
+
+        if (!s->sh.first_slice_in_pic_flag)
+            s->slice_idx += !s->sh.dependent_slice_segment_flag;
+
+        if (!s->sh.dependent_slice_segment_flag &&
+            s->sh.slice_type != HEVC_SLICE_I) {
+            ret = ff_hevc_slice_rpl(s);
+            if (ret < 0) {
+                av_log(s->avctx, AV_LOG_WARNING,
+                       "Error constructing the reference lists for the current slice.\n");
+                return ret;
+            }
+        }
+
+        s->slice_initialized = 1;
+
+        /* null s->sh's heap pointers so the next hls_slice_header call
+         * does not free the worker's copy. */
+        s->slice_hdrs[i] = s->sh;
+        s->sh.entry_point_offset     = NULL;
+        s->sh.offset                 = NULL;
+        s->sh.size                   = NULL;
+        s->sh.num_entry_point_offsets = 0;
+    }
+
+    return hevc_decode_slices_mt(s);
+}
+
 static int decode_nal_units(HEVCContext *s, const uint8_t *buf, int length)
 {
     int ret = 0;
     int eos_at_start = 1;
     int flags = (H2645_FLAG_IS_NALFF * !!s->is_nalff) | H2645_FLAG_SMALL_PADDING;
 
-    s->cur_frame = s->collocated_ref = NULL;
+    s->cur_frame = NULL;
     s->last_eos = s->eos;
     s->eos = 0;
     s->slice_initialized = 0;
@@ -3767,19 +4047,145 @@ static int decode_nal_units(HEVCContext *s, const uint8_t *buf, int length)
         }
     }
 
-    /* decode the NAL units */
-    for (int i = 0; i < s->pkt.nb_nals; i++) {
-        H2645NAL *nal = &s->pkt.nals[i];
+    /* pre-scan: count slice NALs belonging to the current frame only.
+     * Stop at the second first_slice_in_pic_flag=1 (next AU boundary).
+     * Flag enhancement-layer slices and a second AU; both force serial. */
+    int saw_enhancement_layer = 0;
+    int saw_second_au         = 0;
+    s->nb_slices_in_frame = 0;
+    if (s->avctx->skip_frame < AVDISCARD_ALL) {
+        int saw_first_slice = 0;
+        for (int i = 0; i < s->pkt.nb_nals; i++) {
+            const H2645NAL *nal = &s->pkt.nals[i];
+            int *tmp;
+            int first_in_pic;
 
-        if (s->avctx->skip_frame >= AVDISCARD_ALL ||
-            (s->avctx->skip_frame >= AVDISCARD_NONREF && ff_hevc_nal_is_nonref(nal->type)))
-            continue;
+            if (s->avctx->skip_frame >= AVDISCARD_NONREF &&
+                ff_hevc_nal_is_nonref(nal->type))
+                continue;
 
-        ret = decode_nal_unit(s, i);
-        if (ret < 0) {
-            av_log(s->avctx, AV_LOG_WARNING,
-                   "Error parsing NAL unit #%d.\n", i);
-            goto fail;
+            switch (nal->type) {
+            case HEVC_NAL_TRAIL_R:
+            case HEVC_NAL_TRAIL_N:
+            case HEVC_NAL_TSA_N:
+            case HEVC_NAL_TSA_R:
+            case HEVC_NAL_STSA_N:
+            case HEVC_NAL_STSA_R:
+            case HEVC_NAL_BLA_W_LP:
+            case HEVC_NAL_BLA_W_RADL:
+            case HEVC_NAL_BLA_N_LP:
+            case HEVC_NAL_IDR_W_RADL:
+            case HEVC_NAL_IDR_N_LP:
+            case HEVC_NAL_CRA_NUT:
+            case HEVC_NAL_RADL_N:
+            case HEVC_NAL_RADL_R:
+            case HEVC_NAL_RASL_N:
+            case HEVC_NAL_RASL_R:
+                /* non-base-layer slices force the serial (multi-layer) path */
+                if (nal->nuh_layer_id != 0) {
+                    saw_enhancement_layer = 1;
+                    continue;
+                }
+                /* peek first_slice_segment_in_pic_flag (first RBSP bit) */
+                {
+                    GetBitContext gb_peek = nal->gb;
+                    first_in_pic = get_bits1(&gb_peek);
+                }
+                if (first_in_pic) {
+                    if (saw_first_slice) {
+                        saw_second_au = 1;
+                        goto prescan_done; /* next AU: stop counting */
+                    }
+                    saw_first_slice = 1;
+                }
+                tmp = av_realloc_array(s->slice_nal_idx,
+                                       s->nb_slices_in_frame + 1,
+                                       sizeof(*s->slice_nal_idx));
+                if (!tmp) {
+                    ret = AVERROR(ENOMEM);
+                    goto fail;
+                }
+                s->slice_nal_idx = tmp;
+                s->slice_nal_idx[s->nb_slices_in_frame++] = i;
+                break;
+            default:
+                break;
+            }
+        }
+    }
+prescan_done:;
+
+    /* parallel path: slice threads, >1 base-layer slice, single AU, no
+     * hwaccel; WPP/tiles/dependent/SCC are re-checked against the real PPS
+     * in decode_slices_parallel() since its NAL may be in this packet. */
+    {
+        const int use_parallel_slices =
+            s->avctx->active_thread_type == FF_THREAD_SLICE &&
+            s->nb_slices_in_frame > 1 &&
+            !s->avctx->hwaccel &&
+            s->avctx->skip_frame <= AVDISCARD_DEFAULT &&
+            !saw_enhancement_layer &&
+            !saw_second_au;
+
+        /* decode the NAL units; skip slice NALs in parallel mode */
+        for (int i = 0; i < s->pkt.nb_nals; i++) {
+            H2645NAL *nal = &s->pkt.nals[i];
+
+            if (s->avctx->skip_frame >= AVDISCARD_ALL ||
+                (s->avctx->skip_frame >= AVDISCARD_NONREF &&
+                 ff_hevc_nal_is_nonref(nal->type)))
+                continue;
+
+            if (use_parallel_slices) {
+                switch (nal->type) {
+                case HEVC_NAL_TRAIL_R:
+                case HEVC_NAL_TRAIL_N:
+                case HEVC_NAL_TSA_N:
+                case HEVC_NAL_TSA_R:
+                case HEVC_NAL_STSA_N:
+                case HEVC_NAL_STSA_R:
+                case HEVC_NAL_BLA_W_LP:
+                case HEVC_NAL_BLA_W_RADL:
+                case HEVC_NAL_BLA_N_LP:
+                case HEVC_NAL_IDR_W_RADL:
+                case HEVC_NAL_IDR_N_LP:
+                case HEVC_NAL_CRA_NUT:
+                case HEVC_NAL_RADL_N:
+                case HEVC_NAL_RADL_R:
+                case HEVC_NAL_RASL_N:
+                case HEVC_NAL_RASL_R:
+                    continue;
+                default:
+                    break;
+                }
+            }
+
+            ret = decode_nal_unit(s, i);
+            if (ret < 0) {
+                av_log(s->avctx, AV_LOG_WARNING,
+                       "Error parsing NAL unit #%d.\n", i);
+                goto fail;
+            }
+        }
+
+        if (use_parallel_slices) {
+            int fell_back = 0;
+            ret = decode_slices_parallel(s, &fell_back);
+            if (ret < 0)
+                goto fail;
+
+            /* PPS/profile unsupported by the parallel path; decode serially */
+            if (fell_back) {
+                for (int i = 0; i < s->nb_slices_in_frame; i++) {
+                    ret = decode_nal_unit(s, s->slice_nal_idx[i]);
+                    if (ret < 0) {
+                        av_log(s->avctx, AV_LOG_WARNING,
+                               "Error parsing NAL unit #%d.\n",
+                               s->slice_nal_idx[i]);
+                        goto fail;
+                    }
+                }
+            }
         }
     }
 
@@ -3962,6 +4368,15 @@ static av_cold int hevc_decode_free(AVCodecContext *avctx)
     av_freep(&s->sh.offset);
     av_freep(&s->sh.size);
 
+    for (unsigned i = 0; i < s->nb_slice_hdrs; i++) {
+        av_freep(&s->slice_hdrs[i].entry_point_offset);
+        av_freep(&s->slice_hdrs[i].offset);
+        av_freep(&s->slice_hdrs[i].size);
+    }
+    av_freep(&s->slice_hdrs);
+    s->nb_slice_hdrs = 0;
+    av_freep(&s->slice_nal_idx);
+
     av_freep(&s->local_ctx);
 
     ff_h2645_packet_uninit(&s->pkt);
diff --git a/libavcodec/hevc/hevcdec.h b/libavcodec/hevc/hevcdec.h
index f9feaf91da..0065315633 100644
--- a/libavcodec/hevc/hevcdec.h
+++ b/libavcodec/hevc/hevcdec.h
@@ -395,6 +395,8 @@ typedef struct HEVCLocalContext {
 
     uint8_t first_qp_group;
 
+    int defer_slice_bs;
+
     void *logctx;
     const struct HEVCContext *parent;
 
@@ -413,6 +415,7 @@ typedef struct HEVCLocalContext {
     HEVCCABACState *common_cabac_state;
 
     const SliceHeader *sh;
+    const RefPicList *refPicList;
 
     int8_t qp_y;
     int8_t curr_qp_y;
@@ -519,10 +522,14 @@ typedef struct HEVCContext {
     const HEVCVPS *vps; ///< RefStruct reference
     const HEVCPPS *pps; ///< RefStruct reference
     SliceHeader sh;
+    int nb_slices_in_frame;
+    int *slice_nal_idx;
+    SliceHeader *slice_hdrs;
+    unsigned nb_slice_hdrs;
+
     enum HEVCNALUnitType nal_unit_type;
     int temporal_id;  ///< temporal_id_plus1 - 1
     HEVCFrame *cur_frame;
-    HEVCFrame *collocated_ref;
     int poc;
     int poc_tid0;
     int slice_idx; ///< number of the slice being currently decoded
@@ -697,10 +704,10 @@ void ff_hevc_luma_mv_mvp_mode(HEVCLocalContext *lc, const HEVCPPS *pps,
                               int part_idx, int merge_idx,
                               MvField *mv, int mvp_lx_flag, int LX);
 void ff_hevc_hls_filter(HEVCLocalContext *lc, const HEVCLayerContext *l,
-                        const HEVCPPS *pps,
+                        const HEVCPPS *pps, const SliceHeader *sh,
                         int x, int y, int ctb_size);
 void ff_hevc_hls_filters(HEVCLocalContext *lc, const HEVCLayerContext *l,
-                         const HEVCPPS *pps,
+                         const HEVCPPS *pps, const SliceHeader *sh,
                          int x_ctb, int y_ctb, int ctb_size);
 void ff_hevc_set_qPy(HEVCLocalContext *lc,
                      const HEVCLayerContext *l, const HEVCPPS *pps,
@@ -708,6 +715,12 @@ void ff_hevc_set_qPy(HEVCLocalContext *lc,
 void ff_hevc_deblocking_boundary_strengths(HEVCLocalContext *lc, const HEVCLayerContext *l,
                                            const HEVCPPS *pps,
                                            int x0, int y0, int log2_trafo_size);
+void ff_hevc_deblocking_bs_top_edge(HEVCLocalContext *lc, const HEVCLayerContext *l,
+                                    const HEVCPPS *pps,
+                                    int x0, int y0, int log2_size);
+void ff_hevc_deblocking_bs_left_edge(HEVCLocalContext *lc, const HEVCLayerContext *l,
+                                     const HEVCPPS *pps,
+                                     int x0, int y0, int log2_size);
 int ff_hevc_cu_qp_delta_sign_flag(HEVCLocalContext *lc);
 int ff_hevc_cu_qp_delta_abs(HEVCLocalContext *lc);
 int ff_hevc_cu_chroma_qp_offset_flag(HEVCLocalContext *lc);
diff --git a/libavcodec/hevc/mvs.c b/libavcodec/hevc/mvs.c
index 50e6fd69cd..bd6de5d397 100644
--- a/libavcodec/hevc/mvs.c
+++ b/libavcodec/hevc/mvs.c
@@ -161,11 +161,13 @@ static int check_mvset(Mv *mvLXCol, const Mv *mvCol,
                 refPicList_col, L ## l, temp_col.ref_idx[l])
 
 // derive the motion vectors section 8.5.3.1.8
-static int derive_temporal_colocated_mvs(const HEVCContext *s, MvField temp_col,
+static int derive_temporal_colocated_mvs(const HEVCContext *s,
+                                         const SliceHeader *sh,
+                                         const RefPicList *refPicList,
+                                         MvField temp_col,
                                          int refIdxLx, Mv *mvLXCol, int X,
                                          int colPic, const RefPicList *refPicList_col)
 {
-    const RefPicList *refPicList = s->cur_frame->refPicList;
 
     if (temp_col.pred_flag == PF_INTRA)
         return 0;
@@ -191,7 +193,7 @@ static int derive_temporal_colocated_mvs(const HEVCContext *s, MvField temp_col,
             else
                 return CHECK_MVSET(1);
         } else {
-            if (s->sh.collocated_list == L1)
+            if (sh->collocated_list == L1)
                 return CHECK_MVSET(0);
             else
                 return CHECK_MVSET(1);
@@ -209,14 +211,16 @@ static int derive_temporal_colocated_mvs(const HEVCContext *s, MvField temp_col,
             ((y ## v) >> sps->log2_min_pu_size))
 
 #define DERIVE_TEMPORAL_COLOCATED_MVS                                   \
-    derive_temporal_colocated_mvs(s, temp_col,                          \
+    derive_temporal_colocated_mvs(s, sh, refPicList, temp_col,          \
                                   refIdxLx, mvLXCol, X, colPic,         \
                                   ff_hevc_get_ref_list(ref, x, y))
 
 /*
  * 8.5.3.1.7  temporal luma motion vector prediction
  */
-static int temporal_luma_motion_vector(const HEVCContext *s, const HEVCSPS *sps,
+static int temporal_luma_motion_vector(const HEVCContext *s, const SliceHeader *sh,
+                                       const RefPicList *refPicList,
+                                       const HEVCSPS *sps,
                                        int x0, int y0,
                                        int nPbW, int nPbH, int refIdxLx,
                                        Mv *mvLXCol, int X)
@@ -228,7 +232,9 @@ static int temporal_luma_motion_vector(const HEVCContext *s, const HEVCSPS *sps,
     int availableFlagLXCol = 0;
     int colPic;
 
-    const HEVCFrame *ref = s->collocated_ref;
+    const HEVCFrame *ref = NULL;
+    if (sh->collocated_ref_idx < refPicList[sh->collocated_list].nb_refs)
+        ref = refPicList[sh->collocated_list].ref[sh->collocated_ref_idx];
 
     if (!ref) {
         memset(mvLXCol, 0, sizeof(*mvLXCol));
@@ -293,7 +299,7 @@ static void derive_spatial_merge_candidates(HEVCLocalContext *lc, const HEVCCont
                                             int merge_idx,
                                             struct MvField mergecandlist[])
 {
-    const RefPicList *refPicList = s->cur_frame->refPicList;
+    const RefPicList *refPicList = lc->refPicList;
     const MvField *tab_mvf       = s->cur_frame->tab_mvf;
 
     const int min_pu_width = sps->min_pu_width;
@@ -413,11 +419,13 @@ static void derive_spatial_merge_candidates(HEVCLocalContext *lc, const HEVCCont
     if (lc->sh->slice_temporal_mvp_enabled_flag &&
         nb_merge_cand < lc->sh->max_num_merge_cand) {
         Mv mv_l0_col = { 0 }, mv_l1_col = { 0 };
-        int available_l0 = temporal_luma_motion_vector(s, sps, x0, y0, nPbW, nPbH,
-                                                       0, &mv_l0_col, 0);
+        int available_l0 = temporal_luma_motion_vector(s, lc->sh, lc->refPicList,
+                                                       sps, x0, y0,
+                                                       nPbW, nPbH, 0, &mv_l0_col, 0);
         int available_l1 = (lc->sh->slice_type == HEVC_SLICE_B) ?
-                           temporal_luma_motion_vector(s, sps, x0, y0, nPbW, nPbH,
-                                                       0, &mv_l1_col, 1) : 0;
+                           temporal_luma_motion_vector(s, lc->sh, lc->refPicList,
+                                                       sps, x0, y0,
+                                                       nPbW, nPbH, 0, &mv_l1_col, 1) : 0;
 
         if (available_l0 || available_l1) {
             mergecandlist[nb_merge_cand].pred_flag = available_l0 + (available_l1 << 1);
@@ -515,10 +523,10 @@ void ff_hevc_luma_mv_merge_mode(HEVCLocalContext *lc, const HEVCPPS *pps,
 }
 
 static av_always_inline void dist_scale(const HEVCContext *s, Mv *mv,
+                                        const RefPicList *refPicList,
                                         int min_pu_width, int x, int y,
                                         int elist, int ref_idx_curr, int ref_idx)
 {
-    const RefPicList *refPicList = s->cur_frame->refPicList;
     const MvField *tab_mvf       = s->cur_frame->tab_mvf;
     int ref_pic_elist      = refPicList[elist].list[TAB_MVF(x, y).ref_idx[elist]];
     int ref_pic_curr       = refPicList[ref_idx_curr].list[ref_idx];
@@ -532,14 +540,13 @@ static av_always_inline void dist_scale(const HEVCContext *s, Mv *mv,
 }
 
 static int mv_mp_mode_mx(const HEVCContext *s, const HEVCSPS *sps,
+                         const RefPicList *refPicList,
                          int x, int y, int pred_flag_index,
                          Mv *mv, int ref_idx_curr, int ref_idx)
 {
     const MvField *tab_mvf = s->cur_frame->tab_mvf;
     int min_pu_width = sps->min_pu_width;
 
-    const RefPicList *refPicList = s->cur_frame->refPicList;
-
     if (((TAB_MVF(x, y).pred_flag) & (1 << pred_flag_index)) &&
         refPicList[pred_flag_index].list[TAB_MVF(x, y).ref_idx[pred_flag_index]] == refPicList[ref_idx_curr].list[ref_idx]) {
         *mv = TAB_MVF(x, y).mv[pred_flag_index];
@@ -549,14 +556,13 @@ static int mv_mp_mode_mx(const HEVCContext *s, const HEVCSPS *sps,
 }
 
 static int mv_mp_mode_mx_lt(const HEVCContext *s, const HEVCSPS *sps,
+                            const RefPicList *refPicList,
                             int x, int y, int pred_flag_index,
                             Mv *mv, int ref_idx_curr, int ref_idx)
 {
     const MvField *tab_mvf = s->cur_frame->tab_mvf;
     int min_pu_width = sps->min_pu_width;
 
-    const RefPicList *refPicList = s->cur_frame->refPicList;
-
     if ((TAB_MVF(x, y).pred_flag) & (1 << pred_flag_index)) {
         int currIsLongTerm     = refPicList[ref_idx_curr].isLongTerm[ref_idx];
 
@@ -566,7 +572,7 @@ static int mv_mp_mode_mx_lt(const HEVCContext *s, const HEVCSPS *sps,
         if (colIsLongTerm == currIsLongTerm) {
             *mv = TAB_MVF(x, y).mv[pred_flag_index];
             if (!currIsLongTerm)
-                dist_scale(s, mv, min_pu_width, x, y,
+                dist_scale(s, mv, refPicList, min_pu_width, x, y,
                            pred_flag_index, ref_idx_curr, ref_idx);
             return 1;
         }
@@ -575,13 +581,13 @@ static int mv_mp_mode_mx_lt(const HEVCContext *s, const HEVCSPS *sps,
 }
 
 #define MP_MX(v, pred, mx)                                      \
-    mv_mp_mode_mx(s, sps,                                       \
+    mv_mp_mode_mx(s, sps, refPicList,                           \
                   (x ## v) >> sps->log2_min_pu_size,            \
                   (y ## v) >> sps->log2_min_pu_size,            \
                   pred, &mx, ref_idx_curr, ref_idx)
 
 #define MP_MX_LT(v, pred, mx)                                   \
-    mv_mp_mode_mx_lt(s, sps,                                    \
+    mv_mp_mode_mx_lt(s, sps, refPicList,                        \
                      (x ## v) >> sps->log2_min_pu_size,         \
                      (y ## v) >> sps->log2_min_pu_size,         \
                      pred, &mx, ref_idx_curr, ref_idx)
@@ -594,6 +600,7 @@ void ff_hevc_luma_mv_mvp_mode(HEVCLocalContext *lc, const HEVCPPS *pps,
 {
     const HEVCSPS *const  sps = pps->sps;
     const HEVCContext *const s = lc->parent;
+    const RefPicList *refPicList = lc->refPicList;
     const MvField *const tab_mvf = s->cur_frame->tab_mvf;
     int isScaledFlag_L0 = 0;
     int availableFlagLXA0 = 1;
@@ -772,8 +779,9 @@ scalef:
     if (numMVPCandLX < 2 && lc->sh->slice_temporal_mvp_enabled_flag &&
         mvp_lx_flag == numMVPCandLX) {
         Mv mv_col;
-        int available_col = temporal_luma_motion_vector(s, sps, x0, y0, nPbW,
-                                                        nPbH, ref_idx,
+        int available_col = temporal_luma_motion_vector(s, lc->sh, lc->refPicList,
+                                                        sps, x0, y0,
+                                                        nPbW, nPbH, ref_idx,
                                                         &mv_col, LX);
         if (available_col)
             mvpcand_list[numMVPCandLX++] = mv_col;
diff --git a/libavcodec/hevc/refs.c b/libavcodec/hevc/refs.c
index 55db706a8a..b513aa1198 100644
--- a/libavcodec/hevc/refs.c
+++ b/libavcodec/hevc/refs.c
@@ -232,7 +232,6 @@ int ff_hevc_set_new_ref(HEVCContext *s, HEVCLayerContext *l, int poc)
 
     s->cur_frame = ref;
     l->cur_frame = ref;
-    s->collocated_ref = NULL;
 
     ref->base_layer_frame = (l != &s->layers[0] && s->layers[0].cur_frame) ?
                             s->layers[0].cur_frame - s->layers[0].DPB : -1;
@@ -428,10 +427,6 @@ int ff_hevc_slice_rpl(HEVCContext *s)
             rpl->list[sh->nb_refs[L0] - 1] = s->cur_frame->poc;
             rpl->ref[sh->nb_refs[L0] - 1]  = s->cur_frame;
         }
-
-        if (sh->collocated_list == list_idx &&
-            sh->collocated_ref_idx < rpl->nb_refs)
-            s->collocated_ref = rpl->ref[sh->collocated_ref_idx];
     }
 
     return 0;
-- 
2.52.0

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