[PR] Dolby E Encoder (clean room implementation) (PR #24058)

OrenJackMaurice via ffmpeg-devel <[email protected]>
Newsgroups gmane.comp.video.ffmpeg.devel
Message-ID <178628337763.59.603657033334005506@29965ddac10e>
PR #24058 opened by OrenJackMaurice
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24058
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24058.patch

  avcodec/dolby_e, avformat: add Dolby E encoder, SMPTE 337M muxer, and carriage in WAV/MPEG-TS/IEC 61937

  Adds a Dolby E encoder (libavcodec/dolby_e_enc.c) built on bit-allocation
  and MDCT/IMDCT helpers factored out of the existing decoder, plus the
  supporting infrastructure to actually get Dolby E out of FFmpeg:

   - avformat/s337m: SMPTE 337M muxer (write side of the existing demuxer)
   - avformat/spdif: Dolby E over IEC 61937 (data type 0x1C)
   - avformat/wavenc: Dolby E carried as disguised 16-bit PCM in WAV,
     matching the existing AC-3/DTS "receiver test" WAV convention
   - avformat/mpegts, mpegtsenc: Dolby E carried over SMPTE 302M (AES3)
     in MPEG-TS
   - avformat/mxf: enable the existing (previously commented-out) Dolby E
     SoundEssenceCompression codec UL
   - fate: encoder round-trip smoke test against synthetic audio

  Development-method: this series was developed with AI assistance
  (Claude Code), under my direction and review, including having it dig
  into and fix a real bug it introduced (a stream-info-probing issue in
  the MPEG-TS carriage path — see that commit's message for detail) before
  this was posted. I'm flagging this explicitly given recent list
  discussion about AI-assisted contributions; every commit carries a
  Development-method trailer, and I've reviewed and tested all of it
  directly (see below).

  Known limitations, so reviewers know where to focus:
   - Only the 25fps/23.98,24,29.97,30fps frame-rate and 16/20/24-bit
     word-depth combinations share the same already-generic decoder-side
     tables, but only 25fps/20-bit has been cross-checked against a real
     Dolby-authored sample; the rest are round-trip self-verified against
     this codebase's own decoder only.
   - metadata_crc is emitted as 0, matching every real sample measured so
     far, but its exact bit range was not independently derived (Dolby's
     own encoder documentation is not available to us).
   - s337m muxer padding is sized to round-trip through FFmpeg's own
     demuxer frame-accurately; third-party-encoded SMPTE 337M streams are
     not guaranteed to demux frame-accurately through this same demuxer
     for unrelated pre-existing reasons (see that commit's message).

  Testing:
   - Full local build, clean.
   - fate-dolby-e, fate-s337m-demux (existing tests, using the real
     Dolby-authored sample from the fate-suite) both still pass after the
     decoder refactor in the first commit.
   - New fate-dolby-e-encode (synthetic round-trip) passes.
   - fate-spdif (13 sub-tests, real samples: AAC/AC-3/DTS/E-AC-3/MLP/
     TrueHD/MP2/MP3) and fate-sub-dvb, fate-mpegts-probe-* (real broadcast
     captures) all still pass, confirming the existing codepaths these
     files touch are unaffected.
   - Manually verified the Dolby E encode -> mpegts mux -> demux -> decode
     round trip end to end.

  Cc'ing @foo86 as the existing dolby_e*/s337m.c maintainer — I've added
  myself as a co-maintainer in this series for the new files; happy to
  adjust if you'd rather review under different terms.


From 08d06a7a8ae783036ce8734f90fea347eb54dbaa Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Fri, 26 Jun 2026 03:06:39 +0200
Subject: [PATCH 001/180] Update for release/9.0 branch start

Signed-off-by: Michael Niedermayer <[email protected]>
---
 Changelog    | 2 --
 RELEASE      | 2 +-
 doc/Doxyfile | 2 +-
 3 files changed, 2 insertions(+), 4 deletions(-)

diff --git a/Changelog b/Changelog
index 3268ab2cca..b1dca93516 100644
--- a/Changelog
+++ b/Changelog
@@ -1,8 +1,6 @@
 Entries are sorted chronologically from oldest to youngest within each release,
 releases are sorted from youngest to oldest.
 
-version <next>:
-
 version 9.0:
 - Extend AMF Color Converter (vf_vpp_amf) HDR capabilities
 - LCEVC track muxing support in MP4 muxer
diff --git a/RELEASE b/RELEASE
index f5facffc32..dd98ee6cb6 100644
--- a/RELEASE
+++ b/RELEASE
@@ -1 +1 @@
-8.0.git
+9.0
diff --git a/doc/Doxyfile b/doc/Doxyfile
index d0191c56a0..baa4594e6e 100644
--- a/doc/Doxyfile
+++ b/doc/Doxyfile
@@ -38,7 +38,7 @@ PROJECT_NAME           = FFmpeg
 # could be handy for archiving the generated documentation or if some version
 # control system is used.
 
-PROJECT_NUMBER         =
+PROJECT_NUMBER         = 9.0
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer a
-- 
2.52.0


From 16e59dfabf92a95a3cc99af38de54644fe995bb0 Mon Sep 17 00:00:00 2001
From: Timo Rothenpieler <[email protected]>
Date: Sat, 27 Jun 2026 00:03:34 +0200
Subject: [PATCH 002/180] forgejo/workflows: change to targeting 9.0 release
 branch

---
 .forgejo/labeler/labeler.js      | 74 --------------------------------
 .forgejo/labeler/labeler.yml     | 35 ---------------
 .forgejo/workflows/autolabel.yml | 32 --------------
 .forgejo/workflows/lint.yml      |  2 +-
 .forgejo/workflows/test.yml      |  4 +-
 5 files changed, 3 insertions(+), 144 deletions(-)
 delete mode 100644 .forgejo/labeler/labeler.js
 delete mode 100644 .forgejo/labeler/labeler.yml
 delete mode 100644 .forgejo/workflows/autolabel.yml

diff --git a/.forgejo/labeler/labeler.js b/.forgejo/labeler/labeler.js
deleted file mode 100644
index 173d98080c..0000000000
--- a/.forgejo/labeler/labeler.js
+++ /dev/null
@@ -1,74 +0,0 @@
-module.exports = async ({github, context}) => {
-    const title = (context.payload.pull_request?.title || context.payload.issue?.title || '').toLowerCase();
-    const labels = [];
-    const issueNumber = context.payload.pull_request?.number || context.payload.issue?.number;
-
-    const kwmap = {
-      'avcodec': 'avcodec',
-      'avdevice': 'avdevice',
-      'avfilter': 'avfilter',
-      'avformat': 'avformat',
-      'avutil': 'avutil',
-      'swresample': 'swresample',
-      'swscale': 'swscale',
-      'fftools': 'CLI',
-      'vulkan': 'vulkan'
-    };
-
-    async function isOrgMember(username) {
-        try {
-            const response = await github.rest.orgs.checkMembershipForUser({
-                org: context.repo.owner,
-                username: username
-            });
-            return response.status === 204;
-        } catch (error) {
-            return false;
-        }
-    }
-
-    if (context.payload.action === 'closed' ||
-        (context.payload.action !== 'opened' && (
-             context.payload.action === 'assigned' ||
-             context.payload.action === 'label_updated' ||
-             context.payload.action === 'labeled' ||
-             context.payload.comment) &&
-         await isOrgMember(context.payload.sender.login))
-    ) {
-        try {
-            await github.rest.issues.removeLabel({
-                owner: context.repo.owner,
-                repo: context.repo.repo,
-                issue_number: issueNumber,
-                // this should say 'new', but forgejo deviates from GitHub API here and expects the ID
-                name: '41'
-            });
-            console.log('Removed "new" label');
-        } catch (error) {
-            if (error.status !== 404 && error.status !== 410) {
-                console.log('Could not remove "new" label');
-            }
-        }
-    } else if (context.payload.action === 'opened') {
-        labels.push('new');
-        console.log('Detected label: new');
-    }
-
-    if ((context.payload.action === 'opened' || context.payload.action === 'edited') && context.eventName !== 'issue_comment') {
-        for (const [kw, label] of Object.entries(kwmap)) {
-            if (title.includes(kw)) {
-                labels.push(label);
-                console.log('Detected label: ' + label);
-            }
-        }
-    }
-
-    if (labels.length > 0) {
-        await github.rest.issues.addLabels({
-            owner: context.repo.owner,
-            repo: context.repo.repo,
-            issue_number: issueNumber,
-            labels: labels,
-        });
-    }
-}
diff --git a/.forgejo/labeler/labeler.yml b/.forgejo/labeler/labeler.yml
deleted file mode 100644
index 446a675316..0000000000
--- a/.forgejo/labeler/labeler.yml
+++ /dev/null
@@ -1,35 +0,0 @@
-avcodec:
-  - changed-files:
-    - any-glob-to-any-file: 'libavcodec/**'
-
-avdevice:
-  - changed-files:
-    - any-glob-to-any-file: 'libavdevice/**'
-
-avfilter:
-  - changed-files:
-    - any-glob-to-any-file: 'libavfilter/**'
-
-avformat:
-  - changed-files:
-    - any-glob-to-any-file: 'libavformat/**'
-
-avutil:
-  - changed-files:
-    - any-glob-to-any-file: 'libavutil/**'
-
-swresample:
-  - changed-files:
-    - any-glob-to-any-file: 'libswresample/**'
-
-swscale:
-  - changed-files:
-    - any-glob-to-any-file: 'libswscale/**'
-
-CLI:
-  - changed-files:
-    - any-glob-to-any-file: 'fftools/**'
-
-vulkan:
-  - changed-files:
-    - any-glob-to-any-file: '**/*vulkan*'
diff --git a/.forgejo/workflows/autolabel.yml b/.forgejo/workflows/autolabel.yml
deleted file mode 100644
index 0235bdd7d4..0000000000
--- a/.forgejo/workflows/autolabel.yml
+++ /dev/null
@@ -1,32 +0,0 @@
-name: Autolabel
-
-on:
-  pull_request_target:
-    types: [opened, edited, synchronize, closed, assigned, labeled, unlabeled]
-  issues:
-    types: [opened, edited, closed, assigned, labeled, unlabeled]
-  issue_comment:
-    types: [created]
-
-jobs:
-  pr_labeler:
-    name: Labeler
-    runs-on: utilities
-    if: ${{ github.event.sender.login != 'ffmpeg-devel' }}
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v6
-      - name: Label by file-changes
-        uses: actions/labeler@v6
-        if: ${{ forge.event_name == 'pull_request_target' }}
-        with:
-          configuration-path: .forgejo/labeler/labeler.yml
-          repo-token: ${{ secrets.AUTOLABELER_TOKEN }}
-          sync-labels: true
-      - name: Label by title-match
-        uses: actions/github-script@v8
-        with:
-          script: |
-            const script = require('.forgejo/labeler/labeler.js')
-            await script({github, context})
-          github-token: ${{ secrets.AUTOLABELER_TOKEN }}
diff --git a/.forgejo/workflows/lint.yml b/.forgejo/workflows/lint.yml
index 32237eecc7..d4c025104e 100644
--- a/.forgejo/workflows/lint.yml
+++ b/.forgejo/workflows/lint.yml
@@ -3,7 +3,7 @@ name: Lint
 on:
   push:
     branches:
-      - master
+      - release/9.0
   pull_request:
 
 concurrency:
diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml
index 3af1522b88..35e1064712 100644
--- a/.forgejo/workflows/test.yml
+++ b/.forgejo/workflows/test.yml
@@ -3,7 +3,7 @@ name: Test
 on:
   push:
     branches:
-      - master
+      - release/9.0
   pull_request:
 
 concurrency:
@@ -77,7 +77,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        image: ['ghcr.io/btbn/ffmpeg-builds/win64-gpl:latest']
+        image: ['ghcr.io/btbn/ffmpeg-builds/win64-gpl-9.0:latest']
         target_exec: ['wine']
     runs-on: linux-amd64
     container: ${{ matrix.image }}
-- 
2.52.0


From 5a4b9c597654681dd9cfb9bc606d43597558b43f Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Sun, 28 Jun 2026 14:27:28 +0200
Subject: [PATCH 003/180] fftools/ffmpeg_demux: skip finished/unstarted streams
 in readrate_sleep()

This shouldn't affect the actual behavior, as the initialization of ds->dts
(implicitly zero'd) and the previous calculation of stream_ts_offset
guarantees that the `if (pts <= stream_ts_offset) continue;` branch fires.

Mainly a minor clarification of the code for the upcoming refactor.

Signed-off-by: Niklas Haas <[email protected]>
(cherry picked from commit 5c877416a56f491b0dfc3dfd1fc920aebe43ed18)
Signed-off-by: Marvin Scholz <[email protected]>
---
 fftools/ffmpeg_demux.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/fftools/ffmpeg_demux.c b/fftools/ffmpeg_demux.c
index 2d2dd5a82d..9287a31774 100644
--- a/fftools/ffmpeg_demux.c
+++ b/fftools/ffmpeg_demux.c
@@ -521,10 +521,10 @@ static void readrate_sleep(Demuxer *d)
         InputStream *ist = f->streams[i];
         DemuxStream  *ds = ds_from_ist(ist);
         int64_t stream_ts_offset, pts, now, wc_elapsed, elapsed, lag, max_pts, limit_pts;
+        if (ds->discard || ds->finished || ds->first_dts == AV_NOPTS_VALUE)
+            continue;
 
-        if (ds->discard) continue;
-
-        stream_ts_offset = FFMAX(ds->first_dts != AV_NOPTS_VALUE ? ds->first_dts : 0, file_start);
+        stream_ts_offset = FFMAX(ds->first_dts, file_start);
         pts = av_rescale(ds->dts, 1000000, AV_TIME_BASE);
         now = av_gettime_relative();
         wc_elapsed = now - d->wallclock_start;
-- 
2.52.0


From b9a83fda4130841d3e6f5d19fa663c24ce830f4d Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Sun, 28 Jun 2026 14:43:14 +0200
Subject: [PATCH 004/180] fftools/ffmpeg_demux: remove unused variable

This is a dead assignment except on a single branch, so just define it
locally.

Signed-off-by: Niklas Haas <[email protected]>
(cherry picked from commit e7be06c8bd5403185f8656c4054393694dbd0bd4)
Signed-off-by: Marvin Scholz <[email protected]>
---
 fftools/ffmpeg_demux.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/fftools/ffmpeg_demux.c b/fftools/ffmpeg_demux.c
index 9287a31774..5dbd14dc0a 100644
--- a/fftools/ffmpeg_demux.c
+++ b/fftools/ffmpeg_demux.c
@@ -520,7 +520,7 @@ static void readrate_sleep(Demuxer *d)
     for (int i = 0; i < f->nb_streams; i++) {
         InputStream *ist = f->streams[i];
         DemuxStream  *ds = ds_from_ist(ist);
-        int64_t stream_ts_offset, pts, now, wc_elapsed, elapsed, lag, max_pts, limit_pts;
+        int64_t stream_ts_offset, pts, now, wc_elapsed, lag, max_pts, limit_pts;
         if (ds->discard || ds->finished || ds->first_dts == AV_NOPTS_VALUE)
             continue;
 
@@ -544,10 +544,9 @@ static void readrate_sleep(Demuxer *d)
         if (ds->lag && !lag)
             ds->lag = ds->resume_wc = ds->resume_pts = 0;
         if (ds->resume_wc) {
-            elapsed = now - ds->resume_wc;
+            int64_t elapsed = now - ds->resume_wc;
             limit_pts = ds->resume_pts + (int64_t)(elapsed * d->readrate_catchup);
         } else {
-            elapsed = wc_elapsed;
             limit_pts = max_pts;
         }
 
-- 
2.52.0


From d9402f5d71e31c0f5c5616d27d141d88e476e9ca Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Sun, 28 Jun 2026 14:48:19 +0200
Subject: [PATCH 005/180] fftools/ffmpeg_demux: only throttle readrate on the
 slowest stream

If streams are badly interleaved, then the readrate logic can end up
accumulating an ever-growing lag. Rather than looping over each stream
and sleeping for each stream individually based on the local DTS and lag
logic, pull the sleep out of the loop and only sleep once based on the
furthest-behind stream (i.e. the stream contributing the lowest sleep
duration).

To reproduce:

$ ./ffmpeg -re -i fallbeatcaptiontest.mp4 -c copy -f null -t 10 -

Before this commit, this would run at ~0.7x and accumulate an infinitely
growing lag in one stream. After this commit, both streams run at ~1x as
expected, after an initial burst period due to the bad (1s granularity)
interleaving.

Signed-off-by: Niklas Haas <[email protected]>
(cherry picked from commit de6bcf5c05e3534ee7874a2ec8c0fc5af527137b)
Signed-off-by: Marvin Scholz <[email protected]>
---
 fftools/ffmpeg_demux.c | 78 +++++++++++++++++++++++++-----------------
 1 file changed, 46 insertions(+), 32 deletions(-)

diff --git a/fftools/ffmpeg_demux.c b/fftools/ffmpeg_demux.c
index 5dbd14dc0a..38bd580bfc 100644
--- a/fftools/ffmpeg_demux.c
+++ b/fftools/ffmpeg_demux.c
@@ -99,12 +99,6 @@ typedef struct DemuxStream {
     uint64_t                 nb_packets;
     // combined size of all the packets read
     uint64_t                 data_size;
-    // latest wallclock time at which packet reading resumed after a stall - used for readrate
-    int64_t                  resume_wc;
-    // timestamp of first packet sent after the latest stall - used for readrate
-    int64_t                  resume_pts;
-    // measure of how far behind packet reading is against spceified readrate
-    int64_t                  lag;
 } DemuxStream;
 
 typedef struct DemuxStreamGroup {
@@ -147,6 +141,13 @@ typedef struct Demuxer {
     double                readrate_initial_burst;
     float                 readrate_catchup;
 
+    // latest wallclock time at which packet reading resumed after a stall - used for readrate
+    int64_t               resume_wc;
+    // relative timestamp of first packet sent after the latest stall - used for readrate
+    int64_t               resume_progress;
+    // measure of how far behind packet reading is against spceified readrate
+    int64_t               lag;
+
     Scheduler            *sch;
 
     AVPacket             *pkt_heartbeat;
@@ -517,42 +518,55 @@ static void readrate_sleep(Demuxer *d)
     int64_t initial_burst = AV_TIME_BASE * d->readrate_initial_burst;
     int resume_warn = 0;
 
+    DemuxStream *slowest = NULL;
+    int64_t progress = INT64_MAX;
+
     for (int i = 0; i < f->nb_streams; i++) {
         InputStream *ist = f->streams[i];
         DemuxStream  *ds = ds_from_ist(ist);
-        int64_t stream_ts_offset, pts, now, wc_elapsed, lag, max_pts, limit_pts;
+        int64_t stream_ts_offset, pts, pts_diff;
         if (ds->discard || ds->finished || ds->first_dts == AV_NOPTS_VALUE)
             continue;
 
         stream_ts_offset = FFMAX(ds->first_dts, file_start);
         pts = av_rescale(ds->dts, 1000000, AV_TIME_BASE);
-        now = av_gettime_relative();
-        wc_elapsed = now - d->wallclock_start;
-
-        if (pts <= stream_ts_offset + initial_burst) continue;
-
-        max_pts = stream_ts_offset + initial_burst + (int64_t)(wc_elapsed * d->readrate);
-        lag = FFMAX(max_pts - pts, 0);
-        if ( (!ds->lag && lag > 0.3 * AV_TIME_BASE) || ( lag > ds->lag + 0.3 * AV_TIME_BASE) ) {
-            ds->lag = lag;
-            ds->resume_wc = now;
-            ds->resume_pts = pts;
-            av_log_once(ds, AV_LOG_WARNING, AV_LOG_DEBUG, &resume_warn,
-                        "Resumed reading at pts %0.3f with rate %0.3f after a lag of %0.3fs\n",
-                        (float)pts/AV_TIME_BASE, d->readrate_catchup, (float)lag/AV_TIME_BASE);
+        pts_diff = pts - stream_ts_offset;
+        if (pts_diff < progress) {
+            progress = pts_diff;
+            slowest = ds;
         }
-        if (ds->lag && !lag)
-            ds->lag = ds->resume_wc = ds->resume_pts = 0;
-        if (ds->resume_wc) {
-            int64_t elapsed = now - ds->resume_wc;
-            limit_pts = ds->resume_pts + (int64_t)(elapsed * d->readrate_catchup);
-        } else {
-            limit_pts = max_pts;
-        }
-
-        if (pts > limit_pts)
-            av_usleep(pts - limit_pts);
     }
+
+    if (!slowest || progress <= initial_burst)
+        return;
+
+    int64_t now = av_gettime_relative();
+    int64_t wc_elapsed = now - d->wallclock_start;
+    int64_t max_prog = initial_burst + (int64_t)(wc_elapsed * d->readrate);
+    int64_t lag = FFMAX(max_prog - progress, 0);
+    int64_t limit;
+
+    if ( (!d->lag && lag > 0.3 * AV_TIME_BASE) || ( lag > d->lag + 0.3 * AV_TIME_BASE) ) {
+        d->lag = lag;
+        d->resume_wc = now;
+        d->resume_progress = progress;
+
+        int64_t pts = FFMAX(slowest->first_dts, file_start) + progress;
+        av_log_once(slowest, AV_LOG_WARNING, AV_LOG_DEBUG, &resume_warn,
+                    "Resumed reading at pts %0.3f with rate %0.3f after a lag of %0.3fs\n",
+                    (float)pts/AV_TIME_BASE, d->readrate_catchup, (float)lag/AV_TIME_BASE);
+    }
+    if (d->lag && !lag)
+        d->lag = d->resume_wc = d->resume_progress = 0;
+    if (d->resume_wc) {
+        int64_t elapsed = now - d->resume_wc;
+        limit = d->resume_progress + (int64_t)(elapsed * d->readrate_catchup);
+    } else {
+        limit = max_prog;
+    }
+
+    if (progress > limit)
+        av_usleep(progress - limit);
 }
 
 static int do_send(Demuxer *d, DemuxStream *ds, AVPacket *pkt, unsigned flags,
-- 
2.52.0


From 9573519b013f8a06fa48d807ead20d1a3be0625a Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Fri, 26 Jun 2026 14:54:51 +0200
Subject: [PATCH 006/180] avfilter/vf_scale_cuda: eliminate redundant context
 push/pop

This is already done by cudascale_filter_frame().

Signed-off-by: Niklas Haas <[email protected]>
(cherry picked from commit 0c3f04a97c2b6b8473631c61682bbf738b9a9978)
Signed-off-by: Marvin Scholz <[email protected]>
---
 libavfilter/vf_scale_cuda.c | 7 -------
 1 file changed, 7 deletions(-)

diff --git a/libavfilter/vf_scale_cuda.c b/libavfilter/vf_scale_cuda.c
index 2a7dc300f5..d429f791b0 100644
--- a/libavfilter/vf_scale_cuda.c
+++ b/libavfilter/vf_scale_cuda.c
@@ -756,7 +756,6 @@ static int scalecuda_resize(AVFilterContext *ctx, int pass,
 {
     CUDAScaleContext *s = ctx->priv;
     CudaFunctions *cu = s->hwctx->internal->cuda_dl;
-    CUcontext dummy, cuda_ctx = s->hwctx->cuda_ctx;
     int i, ret;
     int mpeg_range = in->color_range != AVCOL_RANGE_JPEG;
 
@@ -772,10 +771,6 @@ static int scalecuda_resize(AVFilterContext *ctx, int pass,
     int crop_width = (in->width - in->crop_right) - in->crop_left;
     int crop_height = (in->height - in->crop_bottom) - in->crop_top;
 
-    ret = CHECK_CU(cu->cuCtxPushCurrent(cuda_ctx));
-    if (ret < 0)
-        return ret;
-
     for (i = 0; i < s->in_planes; i++) {
         CUDA_TEXTURE_DESC tex_desc = {
             .filterMode = s->interp_use_linear ?
@@ -836,8 +831,6 @@ exit:
         if (tex[i])
             CHECK_CU(cu->cuTexObjectDestroy(tex[i]));
 
-    CHECK_CU(cu->cuCtxPopCurrent(&dummy));
-
     return ret;
 }
 
-- 
2.52.0


From 480bf29af3240416565f0cfce7056f586aa88a5a Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Fri, 26 Jun 2026 15:43:45 +0200
Subject: [PATCH 007/180] avfilter/vf_scale_cuda: add fail: label (cosmetic)

Make the next commit a bit easier to review.

Signed-off-by: Niklas Haas <[email protected]>
(cherry picked from commit 61750318db8298af801098ecee5cc89884b20bb9)
Signed-off-by: Marvin Scholz <[email protected]>
---
 libavfilter/vf_scale_cuda.c | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/libavfilter/vf_scale_cuda.c b/libavfilter/vf_scale_cuda.c
index d429f791b0..d0c3f10f20 100644
--- a/libavfilter/vf_scale_cuda.c
+++ b/libavfilter/vf_scale_cuda.c
@@ -838,26 +838,25 @@ static int cudascale_scale(AVFilterContext *ctx, AVFrame *out, AVFrame *in)
 {
     CUDAScaleContext *s = ctx->priv;
     AVFilterLink *outlink = ctx->outputs[0];
-    AVFrame *src = in;
-    int ret;
+    int ret = 0;
 
+    AVFrame *src = in;
     if (s->inter_buf) {
         /* Handle first pass separately */
         s->inter_buf->color_range = in->color_range;
         ret = scalecuda_resize(ctx, FILTER_TMP, s->inter_buf, in);
         if (ret < 0)
-            return ret;
+            goto fail;
         src = s->inter_buf;
     }
 
     ret = scalecuda_resize(ctx, FILTER_OUT, s->frame, src);
     if (ret < 0)
-        return ret;
+        goto fail;
 
-    src = s->frame;
-    ret = av_hwframe_get_buffer(src->hw_frames_ctx, s->tmp_frame, 0);
+    ret = av_hwframe_get_buffer(s->frame->hw_frames_ctx, s->tmp_frame, 0);
     if (ret < 0)
-        return ret;
+        goto fail;
 
     av_frame_move_ref(out, s->frame);
     av_frame_move_ref(s->frame, s->tmp_frame);
@@ -867,14 +866,15 @@ static int cudascale_scale(AVFilterContext *ctx, AVFrame *out, AVFrame *in)
 
     ret = av_frame_copy_props(out, in);
     if (ret < 0)
-        return ret;
+        goto fail;
 
     if (out->width != in->width || out->height != in->height) {
         av_frame_side_data_remove_by_props(&out->side_data, &out->nb_side_data,
                                            AV_SIDE_DATA_PROP_SIZE_DEPENDENT);
     }
 
-    return 0;
+fail:
+    return ret;
 }
 
 static int cudascale_filter_frame(AVFilterLink *link, AVFrame *in)
-- 
2.52.0


From c5caacd845e0e461db265bc0da95907394e6b2a4 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Fri, 26 Jun 2026 15:48:44 +0200
Subject: [PATCH 008/180] avfilter/vf_scale_cuda: introduce CUDATex and mapping
 helper

I want to disentangle the internal logic from AVFrame, because some
intermediate states (e.g. for partially subsampled chroma with simultaneous
scaling) may not directly map to a valid AVPixelFormat.

Signed-off-by: Niklas Haas <[email protected]>
(cherry picked from commit fef976b1971be0b410baa031bd9abc9c6c17bb2a)
Signed-off-by: Marvin Scholz <[email protected]>
---
 libavfilter/vf_scale_cuda.c | 186 +++++++++++++++++++++++-------------
 1 file changed, 122 insertions(+), 64 deletions(-)

diff --git a/libavfilter/vf_scale_cuda.c b/libavfilter/vf_scale_cuda.c
index d0c3f10f20..540b869f7c 100644
--- a/libavfilter/vf_scale_cuda.c
+++ b/libavfilter/vf_scale_cuda.c
@@ -104,6 +104,15 @@ typedef struct CUDAScaleFilter {
     int dst_size;
 } CUDAScaleFilter;
 
+typedef struct CUDATex {
+    CUtexObject tex[4];
+    CUdeviceptr data[4];
+    int         linesize[4];
+    int         width, height;
+    int         crop_left, crop_top, crop_width, crop_height;
+    int         color_range;
+} CUDATex;
+
 typedef struct CUDAScaleContext {
     const AVClass *class;
 
@@ -175,6 +184,16 @@ static void filter_uninit(CudaFunctions *cu, CUDAScaleFilter *filter)
     memset(filter, 0, sizeof(*filter));
 }
 
+static void cuda_tex_uninit(CudaFunctions *cu, CUDATex *t)
+{
+    for (int i = 0; i < FF_ARRAY_ELEMS(t->tex); i++) {
+        if (t->tex[i])
+            cu->cuTexObjectDestroy(t->tex[i]);
+    }
+
+    memset(t, 0, sizeof(*t));
+}
+
 static av_cold void cudascale_uninit(AVFilterContext *ctx)
 {
     CUDAScaleContext *s = ctx->priv;
@@ -711,9 +730,71 @@ fail:
     return ret;
 }
 
+/* if depths/channels are NULL, only maps pointers without creating textures */
+static int cuda_tex_map_frame(AVFilterContext *ctx, const AVFrame *frame,
+                              const int depths[4], const int channels[4],
+                              CUDATex *tex)
+{
+    CUDAScaleContext *s = ctx->priv;
+    CudaFunctions *cu = s->hwctx->internal->cuda_dl;
+
+    const AVHWFramesContext *fctx = (const AVHWFramesContext*)frame->hw_frames_ctx->data;
+    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(fctx->sw_format);
+    const int planes = av_pix_fmt_count_planes(fctx->sw_format);
+
+    *tex = (CUDATex) {
+        .width       = frame->width,
+        .height      = frame->height,
+        .crop_left   = frame->crop_left,
+        .crop_top    = frame->crop_top,
+        .crop_width  = (frame->width  - frame->crop_right)  - frame->crop_left,
+        .crop_height = (frame->height - frame->crop_bottom) - frame->crop_top,
+        .color_range = frame->color_range,
+    };
+
+    for (int i = 0; i < planes; i++) {
+        tex->data[i]     = (CUdeviceptr)frame->data[i];
+        tex->linesize[i] = frame->linesize[i];
+        if (!depths || !channels)
+            continue;
+
+        CUDA_TEXTURE_DESC tex_desc = {
+            .filterMode = s->interp_use_linear ?
+                          CU_TR_FILTER_MODE_LINEAR :
+                          CU_TR_FILTER_MODE_POINT,
+            .flags = s->interp_as_integer ? CU_TRSF_READ_AS_INTEGER : 0,
+        };
+
+        const int is_chroma = i == 1 || i == 2;
+        const int sub_x = is_chroma ? desc->log2_chroma_w : 0;
+        const int sub_y = is_chroma ? desc->log2_chroma_h : 0;
+        CUDA_RESOURCE_DESC res_desc = {
+            .resType = CU_RESOURCE_TYPE_PITCH2D,
+            .res.pitch2D.format = depths[i] <= 8 ?
+                                  CU_AD_FORMAT_UNSIGNED_INT8 :
+                                  CU_AD_FORMAT_UNSIGNED_INT16,
+            .res.pitch2D.numChannels = channels[i],
+            .res.pitch2D.pitchInBytes = tex->linesize[i],
+            .res.pitch2D.devPtr = tex->data[i],
+            .res.pitch2D.width  = AV_CEIL_RSHIFT(frame->width,  sub_x),
+            .res.pitch2D.height = AV_CEIL_RSHIFT(frame->height, sub_y),
+        };
+
+        int ret = CHECK_CU(cu->cuTexObjectCreate(&tex->tex[i], &res_desc, &tex_desc, NULL));
+        if (ret < 0) {
+            cuda_tex_uninit(cu, tex);
+            return ret;
+        }
+    }
+
+    return 0;
+}
+
 static int call_resize_kernel(AVFilterContext *ctx, CUfunction func,
-                              CUtexObject src_tex[4], int src_left, int src_top, int src_width, int src_height,
-                              AVFrame *out_frame, int dst_width, int dst_height, int dst_pitch, int mpeg_range,
+                              const CUtexObject src_tex[4],
+                              int src_left, int src_top, int src_width, int src_height,
+                              const CUdeviceptr out_data[4],
+                              int dst_width, int dst_height, int dst_pitch, int mpeg_range,
                               const CUDAScaleFilter *filter)
 {
     CUDAScaleContext *s = ctx->priv;
@@ -722,10 +803,10 @@ static int call_resize_kernel(AVFilterContext *ctx, CUfunction func,
     CUDAScaleKernelParams params = {
         .src_tex = {src_tex[0], src_tex[1], src_tex[2], src_tex[3]},
         .dst = {
-            (CUdeviceptr)out_frame->data[0],
-            (CUdeviceptr)out_frame->data[1],
-            (CUdeviceptr)out_frame->data[2],
-            (CUdeviceptr)out_frame->data[3]
+            out_data[0],
+            out_data[1],
+            out_data[2],
+            out_data[3]
         },
         .dst_width = dst_width,
         .dst_height = dst_height,
@@ -752,12 +833,11 @@ static int call_resize_kernel(AVFilterContext *ctx, CUfunction func,
 }
 
 static int scalecuda_resize(AVFilterContext *ctx, int pass,
-                            AVFrame *out, AVFrame *in)
+                            const CUDATex *out, const CUDATex *in)
 {
     CUDAScaleContext *s = ctx->priv;
-    CudaFunctions *cu = s->hwctx->internal->cuda_dl;
-    int i, ret;
     int mpeg_range = in->color_range != AVCOL_RANGE_JPEG;
+    int ret;
 
     const AVPixFmtDescriptor *out_desc = s->out_desc;
     int out_planes = s->out_planes;
@@ -766,91 +846,66 @@ static int scalecuda_resize(AVFilterContext *ctx, int pass,
         out_planes = s->in_planes;
     }
 
-    CUtexObject tex[4] = { 0, 0, 0, 0 };
-
-    int crop_width = (in->width - in->crop_right) - in->crop_left;
-    int crop_height = (in->height - in->crop_bottom) - in->crop_top;
-
-    for (i = 0; i < s->in_planes; i++) {
-        CUDA_TEXTURE_DESC tex_desc = {
-            .filterMode = s->interp_use_linear ?
-                          CU_TR_FILTER_MODE_LINEAR :
-                          CU_TR_FILTER_MODE_POINT,
-            .flags = s->interp_as_integer ? CU_TRSF_READ_AS_INTEGER : 0,
-        };
-
-        CUDA_RESOURCE_DESC res_desc = {
-            .resType = CU_RESOURCE_TYPE_PITCH2D,
-            .res.pitch2D.format = s->in_plane_depths[i] <= 8 ?
-                                  CU_AD_FORMAT_UNSIGNED_INT8 :
-                                  CU_AD_FORMAT_UNSIGNED_INT16,
-            .res.pitch2D.numChannels = s->in_plane_channels[i],
-            .res.pitch2D.pitchInBytes = in->linesize[i],
-            .res.pitch2D.devPtr = (CUdeviceptr)in->data[i],
-        };
-
-        if (i == 1 || i == 2) {
-            res_desc.res.pitch2D.width = AV_CEIL_RSHIFT(in->width, s->in_desc->log2_chroma_w);
-            res_desc.res.pitch2D.height = AV_CEIL_RSHIFT(in->height, s->in_desc->log2_chroma_h);
-        } else {
-            res_desc.res.pitch2D.width = in->width;
-            res_desc.res.pitch2D.height = in->height;
-        }
-
-        ret = CHECK_CU(cu->cuTexObjectCreate(&tex[i], &res_desc, &tex_desc, NULL));
-        if (ret < 0)
-            goto exit;
-    }
-
     // scale primary plane(s). Usually Y (and A), or single plane of RGB frames.
     ret = call_resize_kernel(ctx, s->cu_func[pass],
-                             tex, in->crop_left, in->crop_top, crop_width, crop_height,
-                             out, out->width, out->height, out->linesize[0], mpeg_range,
+                             in->tex, in->crop_left, in->crop_top,
+                             in->crop_width, in->crop_height,
+                             out->data, out->width, out->height,
+                             out->linesize[0], mpeg_range,
                              &s->filters[pass]);
     if (ret < 0)
-        goto exit;
+        return ret;
 
     if (out_planes > 1) {
         // scale UV plane. Scale function sets both U and V plane, or singular interleaved plane.
-        ret = call_resize_kernel(ctx, s->cu_func_uv[pass], tex,
+        ret = call_resize_kernel(ctx, s->cu_func_uv[pass], in->tex,
                                  AV_CEIL_RSHIFT(in->crop_left, s->in_desc->log2_chroma_w),
                                  AV_CEIL_RSHIFT(in->crop_top, s->in_desc->log2_chroma_h),
-                                 AV_CEIL_RSHIFT(crop_width, s->in_desc->log2_chroma_w),
-                                 AV_CEIL_RSHIFT(crop_height, s->in_desc->log2_chroma_h),
-                                 out,
+                                 AV_CEIL_RSHIFT(in->crop_width, s->in_desc->log2_chroma_w),
+                                 AV_CEIL_RSHIFT(in->crop_height, s->in_desc->log2_chroma_h),
+                                 out->data,
                                  AV_CEIL_RSHIFT(out->width, out_desc->log2_chroma_w),
                                  AV_CEIL_RSHIFT(out->height, out_desc->log2_chroma_h),
                                  out->linesize[1], mpeg_range,
                                  &s->filters_uv[pass]);
         if (ret < 0)
-            goto exit;
+            return ret;
     }
 
-exit:
-    for (i = 0; i < s->in_planes; i++)
-        if (tex[i])
-            CHECK_CU(cu->cuTexObjectDestroy(tex[i]));
-
-    return ret;
+    return 0;
 }
 
 static int cudascale_scale(AVFilterContext *ctx, AVFrame *out, AVFrame *in)
 {
     CUDAScaleContext *s = ctx->priv;
+    CudaFunctions *cu = s->hwctx->internal->cuda_dl;
     AVFilterLink *outlink = ctx->outputs[0];
     int ret = 0;
 
-    AVFrame *src = in;
+    CUDATex in_tex = {0}, out_tex = {0}, inter_tex = {0};
+    ret = cuda_tex_map_frame(ctx, in, s->in_plane_depths, s->in_plane_channels, &in_tex);
+    if (ret < 0)
+        goto fail;
+
+    ret = cuda_tex_map_frame(ctx, s->frame, NULL, NULL, &out_tex);
+    if (ret < 0)
+        goto fail;
+
+    const CUDATex *src = &in_tex;
     if (s->inter_buf) {
         /* Handle first pass separately */
-        s->inter_buf->color_range = in->color_range;
-        ret = scalecuda_resize(ctx, FILTER_TMP, s->inter_buf, in);
+        ret = cuda_tex_map_frame(ctx, s->inter_buf, s->in_plane_depths,
+                                 s->in_plane_channels, &inter_tex);
         if (ret < 0)
             goto fail;
-        src = s->inter_buf;
+        inter_tex.color_range = in->color_range;
+        ret = scalecuda_resize(ctx, FILTER_TMP, &inter_tex, src);
+        if (ret < 0)
+            goto fail;
+        src = &inter_tex;
     }
 
-    ret = scalecuda_resize(ctx, FILTER_OUT, s->frame, src);
+    ret = scalecuda_resize(ctx, FILTER_OUT, &out_tex, src);
     if (ret < 0)
         goto fail;
 
@@ -874,6 +929,9 @@ static int cudascale_scale(AVFilterContext *ctx, AVFrame *out, AVFrame *in)
     }
 
 fail:
+    cuda_tex_uninit(cu, &in_tex);
+    cuda_tex_uninit(cu, &out_tex);
+    cuda_tex_uninit(cu, &inter_tex);
     return ret;
 }
 
-- 
2.52.0


From f3f292929e2da24aefa4037e96cbb96e551470b2 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Fri, 26 Jun 2026 16:42:19 +0200
Subject: [PATCH 009/180] avfilter/vf_scale_cuda: defer buffer allocation to
 setup_filters()

At this point, s->hwctx and CudaFunctions * are available.

Signed-off-by: Niklas Haas <[email protected]>
(cherry picked from commit 4289a29bb049d817724723c76fb1a8c929da40db)
Signed-off-by: Marvin Scholz <[email protected]>
---
 libavfilter/vf_scale_cuda.c | 21 ++++++++++-----------
 1 file changed, 10 insertions(+), 11 deletions(-)

diff --git a/libavfilter/vf_scale_cuda.c b/libavfilter/vf_scale_cuda.c
index 540b869f7c..a71f91d824 100644
--- a/libavfilter/vf_scale_cuda.c
+++ b/libavfilter/vf_scale_cuda.c
@@ -260,20 +260,22 @@ fail:
     return ret;
 }
 
-static av_cold int inter_buf_init(CUDAScaleContext *s, AVBufferRef *device_ctx,
-                                  enum AVPixelFormat format, int width, int height)
+static av_cold int inter_buf_init(CUDAScaleContext *s, AVFilterLink *inlink,
+                                  int width, int height)
 {
     AVBufferRef *ref = NULL;
     AVHWFramesContext *fctx;
     int ret;
 
-    ref = av_hwframe_ctx_alloc(device_ctx);
+    FilterLink *inl = ff_filter_link(inlink);
+    AVHWFramesContext *in_frames_ctx = (AVHWFramesContext*)inl->hw_frames_ctx->data;
+    ref = av_hwframe_ctx_alloc(in_frames_ctx->device_ref);
     if (!ref)
         return AVERROR(ENOMEM);
     fctx = (AVHWFramesContext*)ref->data;
 
     fctx->format    = AV_PIX_FMT_CUDA;
-    fctx->sw_format = format;
+    fctx->sw_format = in_frames_ctx->sw_format;
     fctx->width     = FFALIGN(width,  32);
     fctx->height    = FFALIGN(height, 32);
 
@@ -400,13 +402,6 @@ static av_cold int init_processing_chain(AVFilterContext *ctx, int in_width, int
             s->use_filters = 0;
         } else if (s->use_filters < 0 && (in_width < out_width || in_height < out_height))
             s->use_filters = 1; /* downscaling; needed for anti-aliasing */
-
-        if (s->use_filters) {
-            ret = inter_buf_init(s, in_frames_ctx->device_ref, in_format,
-                                 out_width, in_height);
-            if (ret < 0)
-                return ret;
-        }
     }
 
     outl->hw_frames_ctx = av_buffer_ref(s->frames_ctx);
@@ -647,6 +642,10 @@ static av_cold int cudascale_setup_filters(AVFilterContext *ctx)
         }
     }
 
+    ret = inter_buf_init(s, inlink, outlink->w, inlink->h);
+    if (ret < 0)
+        goto fail;
+
     ret = 0;
 
 fail:
-- 
2.52.0


From 3a7097cfa7be222f954e99b7f3b0980ce3072079 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Fri, 26 Jun 2026 16:44:37 +0200
Subject: [PATCH 010/180] avfilter/vf_scale_cuda: use persistent intermediate
 CUDATex

Instead of re-creating this object every frame.

Signed-off-by: Niklas Haas <[email protected]>
(cherry picked from commit e79e9f06ba41980558d220f185dafb335ad3702d)
Signed-off-by: Marvin Scholz <[email protected]>
---
 libavfilter/vf_scale_cuda.c | 31 ++++++++++++++++++++-----------
 1 file changed, 20 insertions(+), 11 deletions(-)

diff --git a/libavfilter/vf_scale_cuda.c b/libavfilter/vf_scale_cuda.c
index a71f91d824..1f0ef2555a 100644
--- a/libavfilter/vf_scale_cuda.c
+++ b/libavfilter/vf_scale_cuda.c
@@ -155,6 +155,7 @@ typedef struct CUDAScaleContext {
     CUDAScaleFilter filters[FILTER_NB];
     CUDAScaleFilter filters_uv[FILTER_NB];
     AVFrame *inter_buf; /* intermediate buffer for separated scaling */
+    CUDATex inter_tex;
     int use_filters; /* -1 for auto */
 
     float param;
@@ -204,6 +205,7 @@ static av_cold void cudascale_uninit(AVFilterContext *ctx)
 
         CHECK_CU(cu->cuCtxPushCurrent(s->hwctx->cuda_ctx));
 
+        cuda_tex_uninit(cu, &s->inter_tex);
         for (int i = 0; i < FF_ARRAY_ELEMS(s->filters); i++) {
             filter_uninit(cu, &s->filters[i]);
             filter_uninit(cu, &s->filters_uv[i]);
@@ -260,9 +262,15 @@ fail:
     return ret;
 }
 
-static av_cold int inter_buf_init(CUDAScaleContext *s, AVFilterLink *inlink,
+static int cuda_tex_map_frame(AVFilterContext *ctx, const AVFrame *frame,
+                              const int depths[4], const int channels[4],
+                              CUDATex *tex);
+
+static av_cold int inter_buf_init(AVFilterContext *ctx, AVFilterLink *inlink,
                                   int width, int height)
 {
+    CUDAScaleContext *s = ctx->priv;
+    CudaFunctions *cu = s->hwctx->internal->cuda_dl;
     AVBufferRef *ref = NULL;
     AVHWFramesContext *fctx;
     int ret;
@@ -297,10 +305,16 @@ static av_cold int inter_buf_init(CUDAScaleContext *s, AVFilterLink *inlink,
     s->inter_buf->width  = width;
     s->inter_buf->height = height;
 
+    ret = cuda_tex_map_frame(ctx, s->inter_buf, s->in_plane_depths,
+                             s->in_plane_channels, &s->inter_tex);
+    if (ret < 0)
+        goto fail;
+
     av_buffer_unref(&ref);
     return 0;
 
 fail:
+    cuda_tex_uninit(cu, &s->inter_tex);
     av_frame_free(&s->inter_buf);
     av_buffer_unref(&ref);
     return ret;
@@ -642,7 +656,7 @@ static av_cold int cudascale_setup_filters(AVFilterContext *ctx)
         }
     }
 
-    ret = inter_buf_init(s, inlink, outlink->w, inlink->h);
+    ret = inter_buf_init(ctx, inlink, outlink->w, inlink->h);
     if (ret < 0)
         goto fail;
 
@@ -881,7 +895,7 @@ static int cudascale_scale(AVFilterContext *ctx, AVFrame *out, AVFrame *in)
     AVFilterLink *outlink = ctx->outputs[0];
     int ret = 0;
 
-    CUDATex in_tex = {0}, out_tex = {0}, inter_tex = {0};
+    CUDATex in_tex = {0}, out_tex = {0};
     ret = cuda_tex_map_frame(ctx, in, s->in_plane_depths, s->in_plane_channels, &in_tex);
     if (ret < 0)
         goto fail;
@@ -893,15 +907,11 @@ static int cudascale_scale(AVFilterContext *ctx, AVFrame *out, AVFrame *in)
     const CUDATex *src = &in_tex;
     if (s->inter_buf) {
         /* Handle first pass separately */
-        ret = cuda_tex_map_frame(ctx, s->inter_buf, s->in_plane_depths,
-                                 s->in_plane_channels, &inter_tex);
+        s->inter_tex.color_range = in->color_range;
+        ret = scalecuda_resize(ctx, FILTER_TMP, &s->inter_tex, src);
         if (ret < 0)
             goto fail;
-        inter_tex.color_range = in->color_range;
-        ret = scalecuda_resize(ctx, FILTER_TMP, &inter_tex, src);
-        if (ret < 0)
-            goto fail;
-        src = &inter_tex;
+        src = &s->inter_tex;
     }
 
     ret = scalecuda_resize(ctx, FILTER_OUT, &out_tex, src);
@@ -930,7 +940,6 @@ static int cudascale_scale(AVFilterContext *ctx, AVFrame *out, AVFrame *in)
 fail:
     cuda_tex_uninit(cu, &in_tex);
     cuda_tex_uninit(cu, &out_tex);
-    cuda_tex_uninit(cu, &inter_tex);
     return ret;
 }
 
-- 
2.52.0


From 316651a9aeb0c40f2196197b48dec7bd746c3c45 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Fri, 26 Jun 2026 16:47:27 +0200
Subject: [PATCH 011/180] avfilter/vf_scale_cuda: allocate intermediate buffer
 directly

Instead of going via an AVFrame at all. This will allow us to fix the
intermediate chroma plane size for split downscaling.

Signed-off-by: Niklas Haas <[email protected]>
(cherry picked from commit 420a9e90b8d7721f00864602b7205fcd78e1cd14)
Signed-off-by: Marvin Scholz <[email protected]>
---
 libavfilter/vf_scale_cuda.c | 100 +++++++++++++++++++-----------------
 1 file changed, 52 insertions(+), 48 deletions(-)

diff --git a/libavfilter/vf_scale_cuda.c b/libavfilter/vf_scale_cuda.c
index 1f0ef2555a..6204da98dc 100644
--- a/libavfilter/vf_scale_cuda.c
+++ b/libavfilter/vf_scale_cuda.c
@@ -24,7 +24,6 @@
 #include <stdio.h>
 #include <string.h>
 
-#include "libavutil/avassert.h"
 #include "libavutil/common.h"
 #include "libavutil/hwcontext.h"
 #include "libavutil/hwcontext_cuda_internal.h"
@@ -111,6 +110,7 @@ typedef struct CUDATex {
     int         width, height;
     int         crop_left, crop_top, crop_width, crop_height;
     int         color_range;
+    int         external_data;
 } CUDATex;
 
 typedef struct CUDAScaleContext {
@@ -154,7 +154,6 @@ typedef struct CUDAScaleContext {
 
     CUDAScaleFilter filters[FILTER_NB];
     CUDAScaleFilter filters_uv[FILTER_NB];
-    AVFrame *inter_buf; /* intermediate buffer for separated scaling */
     CUDATex inter_tex;
     int use_filters; /* -1 for auto */
 
@@ -190,6 +189,8 @@ static void cuda_tex_uninit(CudaFunctions *cu, CUDATex *t)
     for (int i = 0; i < FF_ARRAY_ELEMS(t->tex); i++) {
         if (t->tex[i])
             cu->cuTexObjectDestroy(t->tex[i]);
+        if (t->data[i] && !t->external_data)
+            cu->cuMemFree(t->data[i]);
     }
 
     memset(t, 0, sizeof(*t));
@@ -222,7 +223,6 @@ static av_cold void cudascale_uninit(AVFilterContext *ctx)
     av_frame_free(&s->frame);
     av_buffer_unref(&s->frames_ctx);
     av_frame_free(&s->tmp_frame);
-    av_frame_free(&s->inter_buf);
 }
 
 static av_cold int init_hwframe_ctx(CUDAScaleContext *s, AVBufferRef *device_ctx, int width, int height)
@@ -262,61 +262,64 @@ fail:
     return ret;
 }
 
-static int cuda_tex_map_frame(AVFilterContext *ctx, const AVFrame *frame,
-                              const int depths[4], const int channels[4],
-                              CUDATex *tex);
-
-static av_cold int inter_buf_init(AVFilterContext *ctx, AVFilterLink *inlink,
-                                  int width, int height)
+static av_cold int inter_buf_init(AVFilterContext *ctx, int width, int height)
 {
     CUDAScaleContext *s = ctx->priv;
     CudaFunctions *cu = s->hwctx->internal->cuda_dl;
-    AVBufferRef *ref = NULL;
-    AVHWFramesContext *fctx;
-    int ret;
+    int ret = 0;
 
-    FilterLink *inl = ff_filter_link(inlink);
-    AVHWFramesContext *in_frames_ctx = (AVHWFramesContext*)inl->hw_frames_ctx->data;
-    ref = av_hwframe_ctx_alloc(in_frames_ctx->device_ref);
-    if (!ref)
-        return AVERROR(ENOMEM);
-    fctx = (AVHWFramesContext*)ref->data;
+    cuda_tex_uninit(cu, &s->inter_tex);
+    s->inter_tex = (CUDATex) {
+        .width       = width,
+        .height      = height,
+        .crop_width  = width,
+        .crop_height = height,
+    };
 
-    fctx->format    = AV_PIX_FMT_CUDA;
-    fctx->sw_format = in_frames_ctx->sw_format;
-    fctx->width     = FFALIGN(width,  32);
-    fctx->height    = FFALIGN(height, 32);
+    for (int i = 0; i < s->in_planes; i++) {
+        const int is_chroma = i == 1 || i == 2;
+        const int sub_x   = is_chroma ? s->in_desc->log2_chroma_w : 0;
+        const int sub_y   = is_chroma ? s->in_desc->log2_chroma_h : 0;
+        const int plane_w = AV_CEIL_RSHIFT(width,  sub_x);
+        const int plane_h = AV_CEIL_RSHIFT(height, sub_y);
+        const int sizeof_pixel = (s->in_plane_depths[i] <= 8 ? 1 : 2) *
+                                  s->in_plane_channels[i];
 
-    ret = av_hwframe_ctx_init(ref);
-    if (ret < 0)
-        goto fail;
+        size_t pitch;
+        ret = CHECK_CU(cu->cuMemAllocPitch(&s->inter_tex.data[i], &pitch,
+                                           (size_t) plane_w * sizeof_pixel,
+                                           plane_h, 16));
+        if (ret < 0)
+            goto fail;
+        s->inter_tex.linesize[i] = pitch;
 
-    av_assert0(!s->inter_buf);
-    s->inter_buf = av_frame_alloc();
-    if (!s->inter_buf) {
-        ret = AVERROR(ENOMEM);
-        goto fail;
+        CUDA_TEXTURE_DESC tex_desc = {
+            /* inter tex is always read as float */
+            .filterMode = CU_TR_FILTER_MODE_POINT,
+        };
+
+        CUDA_RESOURCE_DESC res_desc = {
+            .resType = CU_RESOURCE_TYPE_PITCH2D,
+            .res.pitch2D.format = s->in_plane_depths[i] <= 8 ?
+                                  CU_AD_FORMAT_UNSIGNED_INT8 :
+                                  CU_AD_FORMAT_UNSIGNED_INT16,
+            .res.pitch2D.numChannels  = s->in_plane_channels[i],
+            .res.pitch2D.devPtr       = s->inter_tex.data[i],
+            .res.pitch2D.pitchInBytes = pitch,
+            .res.pitch2D.width        = plane_w,
+            .res.pitch2D.height       = plane_h,
+        };
+
+        ret = CHECK_CU(cu->cuTexObjectCreate(&s->inter_tex.tex[i], &res_desc,
+                                             &tex_desc, NULL));
+        if (ret < 0)
+            goto fail;
     }
 
-    ret = av_hwframe_get_buffer(ref, s->inter_buf, 0);
-    if (ret < 0)
-        goto fail;
-
-    s->inter_buf->width  = width;
-    s->inter_buf->height = height;
-
-    ret = cuda_tex_map_frame(ctx, s->inter_buf, s->in_plane_depths,
-                             s->in_plane_channels, &s->inter_tex);
-    if (ret < 0)
-        goto fail;
-
-    av_buffer_unref(&ref);
     return 0;
 
 fail:
     cuda_tex_uninit(cu, &s->inter_tex);
-    av_frame_free(&s->inter_buf);
-    av_buffer_unref(&ref);
     return ret;
 }
 
@@ -501,7 +504,7 @@ static av_cold int cudascale_load_functions(AVFilterContext *ctx)
         goto fail;
     av_log(ctx, AV_LOG_DEBUG, "Chroma filter: %s (%s -> %s)\n", buf, av_get_pix_fmt_name(s->in_fmt), av_get_pix_fmt_name(s->out_fmt));
 
-    if (s->inter_buf) {
+    if (s->use_filters) {
         /* Intermediate pass is always horizontal */
         snprintf(buf, sizeof(buf), "Subsample_Generic_h_%s_%s", in_fmt_name, in_fmt_name);
         ret = CHECK_CU(cu->cuModuleGetFunction(&s->cu_func[FILTER_TMP], s->cu_module, buf));
@@ -656,7 +659,7 @@ static av_cold int cudascale_setup_filters(AVFilterContext *ctx)
         }
     }
 
-    ret = inter_buf_init(ctx, inlink, outlink->w, inlink->h);
+    ret = inter_buf_init(ctx, outlink->w, inlink->h);
     if (ret < 0)
         goto fail;
 
@@ -763,6 +766,7 @@ static int cuda_tex_map_frame(AVFilterContext *ctx, const AVFrame *frame,
         .crop_width  = (frame->width  - frame->crop_right)  - frame->crop_left,
         .crop_height = (frame->height - frame->crop_bottom) - frame->crop_top,
         .color_range = frame->color_range,
+        .external_data = 1,
     };
 
     for (int i = 0; i < planes; i++) {
@@ -905,7 +909,7 @@ static int cudascale_scale(AVFilterContext *ctx, AVFrame *out, AVFrame *in)
         goto fail;
 
     const CUDATex *src = &in_tex;
-    if (s->inter_buf) {
+    if (s->use_filters) {
         /* Handle first pass separately */
         s->inter_tex.color_range = in->color_range;
         ret = scalecuda_resize(ctx, FILTER_TMP, &s->inter_tex, src);
-- 
2.52.0


From 74c735ab10abc97c4a2a9f497e274ed709fa94e6 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Fri, 26 Jun 2026 17:07:05 +0200
Subject: [PATCH 012/180] avfilter/vf_scale_cuda: allocate inter buffer with
 correct subsampling

Since the input and output format can differ (e.g. 444 -> 420), we need to
reference the correct subsampling for the partially applied filter.

Keep track of this in the CUDATex itself.

Signed-off-by: Niklas Haas <[email protected]>
(cherry picked from commit 01972b4f852b2886b3b821aac96d9c2c1d01a6ae)
Signed-off-by: Marvin Scholz <[email protected]>
---
 libavfilter/vf_scale_cuda.c | 62 ++++++++++++++++++++-----------------
 1 file changed, 33 insertions(+), 29 deletions(-)

diff --git a/libavfilter/vf_scale_cuda.c b/libavfilter/vf_scale_cuda.c
index 6204da98dc..9ee962ac9a 100644
--- a/libavfilter/vf_scale_cuda.c
+++ b/libavfilter/vf_scale_cuda.c
@@ -108,6 +108,7 @@ typedef struct CUDATex {
     CUdeviceptr data[4];
     int         linesize[4];
     int         width, height;
+    int         log2_chroma_w, log2_chroma_h;
     int         crop_left, crop_top, crop_width, crop_height;
     int         color_range;
     int         external_data;
@@ -262,7 +263,7 @@ fail:
     return ret;
 }
 
-static av_cold int inter_buf_init(AVFilterContext *ctx, int width, int height)
+static av_cold int inter_buf_init(AVFilterContext *ctx, int out_width, int in_height)
 {
     CUDAScaleContext *s = ctx->priv;
     CudaFunctions *cu = s->hwctx->internal->cuda_dl;
@@ -270,18 +271,20 @@ static av_cold int inter_buf_init(AVFilterContext *ctx, int width, int height)
 
     cuda_tex_uninit(cu, &s->inter_tex);
     s->inter_tex = (CUDATex) {
-        .width       = width,
-        .height      = height,
-        .crop_width  = width,
-        .crop_height = height,
+        .width          = out_width,
+        .height         = in_height,
+        .crop_width     = out_width,
+        .crop_height    = in_height,
+        .log2_chroma_w  = s->out_desc->log2_chroma_w,
+        .log2_chroma_h  = s->in_desc->log2_chroma_h,
     };
 
     for (int i = 0; i < s->in_planes; i++) {
         const int is_chroma = i == 1 || i == 2;
-        const int sub_x   = is_chroma ? s->in_desc->log2_chroma_w : 0;
-        const int sub_y   = is_chroma ? s->in_desc->log2_chroma_h : 0;
-        const int plane_w = AV_CEIL_RSHIFT(width,  sub_x);
-        const int plane_h = AV_CEIL_RSHIFT(height, sub_y);
+        const int sub_x   = is_chroma ? s->inter_tex.log2_chroma_w : 0;
+        const int sub_y   = is_chroma ? s->inter_tex.log2_chroma_h : 0;
+        const int plane_w = AV_CEIL_RSHIFT(out_width, sub_x);
+        const int plane_h = AV_CEIL_RSHIFT(in_height, sub_y);
         const int sizeof_pixel = (s->in_plane_depths[i] <= 8 ? 1 : 2) *
                                   s->in_plane_channels[i];
 
@@ -609,8 +612,10 @@ static av_cold int cudascale_setup_filters(AVFilterContext *ctx)
     CUcontext dummy;
     int ret;
 
-    const int sub_x = s->in_desc->log2_chroma_w;
-    const int sub_y = s->in_desc->log2_chroma_h;
+    const int in_sub_x  = s->in_desc->log2_chroma_w;
+    const int in_sub_y  = s->in_desc->log2_chroma_h;
+    const int out_sub_x = s->out_desc->log2_chroma_w;
+    const int out_sub_y = s->out_desc->log2_chroma_h;
 
     ret = CHECK_CU(cu->cuCtxPushCurrent(s->hwctx->cuda_ctx));
     if (ret < 0)
@@ -633,11 +638,11 @@ static av_cold int cudascale_setup_filters(AVFilterContext *ctx)
         if (ret < 0)
             goto fail;
         if (s->in_planes > 1) {
-            const int src_size = AV_CEIL_RSHIFT(inlink->w,  sub_x);
-            const int dst_size = AV_CEIL_RSHIFT(outlink->w, sub_x);
-            const double ratio = (double) outlink->w / inlink->w;
+            const int src_size = AV_CEIL_RSHIFT(inlink->w,  in_sub_x);
+            const int dst_size = AV_CEIL_RSHIFT(outlink->w, out_sub_x);
+            const double virtual_size = (double) outlink->w / (1 << out_sub_x);
             ret = cudascale_filter_init(ctx, &s->filters_uv[pass_x],
-                                        src_size, dst_size, src_size * ratio);
+                                        src_size, dst_size, virtual_size);
             if (ret < 0)
                 goto fail;
         }
@@ -649,11 +654,11 @@ static av_cold int cudascale_setup_filters(AVFilterContext *ctx)
         if (ret < 0)
             goto fail;
         if (s->in_planes > 1) {
-            const int src_size = AV_CEIL_RSHIFT(inlink->h,  sub_y);
-            const int dst_size = AV_CEIL_RSHIFT(outlink->h, sub_y);
-            const double ratio = (double) outlink->h / inlink->h;
+            const int src_size = AV_CEIL_RSHIFT(inlink->h,  in_sub_y);
+            const int dst_size = AV_CEIL_RSHIFT(outlink->h, out_sub_y);
+            const double virtual_size = (double) outlink->h / (1 << out_sub_y);
             ret = cudascale_filter_init(ctx, &s->filters_uv[pass_y],
-                                        src_size, dst_size, src_size * ratio);
+                                        src_size, dst_size, virtual_size);
             if (ret < 0)
                 goto fail;
         }
@@ -766,6 +771,8 @@ static int cuda_tex_map_frame(AVFilterContext *ctx, const AVFrame *frame,
         .crop_width  = (frame->width  - frame->crop_right)  - frame->crop_left,
         .crop_height = (frame->height - frame->crop_bottom) - frame->crop_top,
         .color_range = frame->color_range,
+        .log2_chroma_w = desc->log2_chroma_w,
+        .log2_chroma_h = desc->log2_chroma_h,
         .external_data = 1,
     };
 
@@ -856,12 +863,9 @@ static int scalecuda_resize(AVFilterContext *ctx, int pass,
     int mpeg_range = in->color_range != AVCOL_RANGE_JPEG;
     int ret;
 
-    const AVPixFmtDescriptor *out_desc = s->out_desc;
     int out_planes = s->out_planes;
-    if (pass == FILTER_TMP) {
-        out_desc   = s->in_desc;
+    if (pass == FILTER_TMP)
         out_planes = s->in_planes;
-    }
 
     // scale primary plane(s). Usually Y (and A), or single plane of RGB frames.
     ret = call_resize_kernel(ctx, s->cu_func[pass],
@@ -876,13 +880,13 @@ static int scalecuda_resize(AVFilterContext *ctx, int pass,
     if (out_planes > 1) {
         // scale UV plane. Scale function sets both U and V plane, or singular interleaved plane.
         ret = call_resize_kernel(ctx, s->cu_func_uv[pass], in->tex,
-                                 AV_CEIL_RSHIFT(in->crop_left, s->in_desc->log2_chroma_w),
-                                 AV_CEIL_RSHIFT(in->crop_top, s->in_desc->log2_chroma_h),
-                                 AV_CEIL_RSHIFT(in->crop_width, s->in_desc->log2_chroma_w),
-                                 AV_CEIL_RSHIFT(in->crop_height, s->in_desc->log2_chroma_h),
+                                 AV_CEIL_RSHIFT(in->crop_left, in->log2_chroma_w),
+                                 AV_CEIL_RSHIFT(in->crop_top, in->log2_chroma_h),
+                                 AV_CEIL_RSHIFT(in->crop_width, in->log2_chroma_w),
+                                 AV_CEIL_RSHIFT(in->crop_height, in->log2_chroma_h),
                                  out->data,
-                                 AV_CEIL_RSHIFT(out->width, out_desc->log2_chroma_w),
-                                 AV_CEIL_RSHIFT(out->height, out_desc->log2_chroma_h),
+                                 AV_CEIL_RSHIFT(out->width, out->log2_chroma_w),
+                                 AV_CEIL_RSHIFT(out->height, out->log2_chroma_h),
                                  out->linesize[1], mpeg_range,
                                  &s->filters_uv[pass]);
         if (ret < 0)
-- 
2.52.0


From 901a25e0d13ee98340126e56dd97847979870fca Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Fri, 26 Jun 2026 17:24:36 +0200
Subject: [PATCH 013/180] avfilter/vf_scale_cuda: fix inverted downscaling
 check

Signed-off-by: Niklas Haas <[email protected]>
(cherry picked from commit 6baf5613036fd7d838c41e85f94d7c7ad7aaf1da)
Signed-off-by: Marvin Scholz <[email protected]>
---
 libavfilter/vf_scale_cuda.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libavfilter/vf_scale_cuda.c b/libavfilter/vf_scale_cuda.c
index 9ee962ac9a..409145c44f 100644
--- a/libavfilter/vf_scale_cuda.c
+++ b/libavfilter/vf_scale_cuda.c
@@ -420,7 +420,7 @@ static av_cold int init_processing_chain(AVFilterContext *ctx, int in_width, int
 
         if (s->interp_algo == INTERP_ALGO_NEAREST) {
             s->use_filters = 0;
-        } else if (s->use_filters < 0 && (in_width < out_width || in_height < out_height))
+        } else if (s->use_filters < 0 && (out_width < in_width || out_height < in_height))
             s->use_filters = 1; /* downscaling; needed for anti-aliasing */
     }
 
-- 
2.52.0


From a508495cd729b0207aa79172110819f49e6431fa Mon Sep 17 00:00:00 2001
From: nyanmisaka <[email protected]>
Date: Mon, 29 Jun 2026 15:32:00 +0800
Subject: [PATCH 014/180] avutil/hwcontext_cuda: fix yuv420p V/U plane overlap
 in cuda_get_buffer()

Odd-height yuv420p result in incorrect calculations of the U-plane
address offset. The last row of the V-plane overlapped with and was
overwritten by the first row of the U-plane, leading to chroma artifacts.

```
ffmpeg -init_hw_device cuda=cu -filter_hw_device cu -f lavfi -i \
testsrc=s=1920x1081,format=yuv420p -vf hwupload -c:v hevc_nvenc \
-vframes 1 -y <OUTPUT>
```

Signed-off-by: nyanmisaka <[email protected]>
(cherry picked from commit 3f6bf150cb018334809bec029325b28cff8a5a9a)
Signed-off-by: Marvin Scholz <[email protected]>
---
 libavutil/hwcontext_cuda.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libavutil/hwcontext_cuda.c b/libavutil/hwcontext_cuda.c
index c57ea4bb7a..227a0d0b19 100644
--- a/libavutil/hwcontext_cuda.c
+++ b/libavutil/hwcontext_cuda.c
@@ -209,7 +209,7 @@ static int cuda_get_buffer(AVHWFramesContext *ctx, AVFrame *frame)
     if (ctx->sw_format == AV_PIX_FMT_YUV420P) {
         frame->linesize[1] = frame->linesize[2] = frame->linesize[0] / 2;
         frame->data[2]     = frame->data[1];
-        frame->data[1]     = frame->data[2] + frame->linesize[2] * (ctx->height / 2);
+        frame->data[1]     = frame->data[2] + frame->linesize[2] * AV_CEIL_RSHIFT(ctx->height, 1);
     }
 
     frame->format = AV_PIX_FMT_CUDA;
-- 
2.52.0


From 228c714a9fe10915618f11c1cb1505abcebf81ab Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Storsj=C3=B6?= <[email protected]>
Date: Mon, 8 Jun 2026 09:34:00 +0000
Subject: [PATCH 015/180] configure: Test for response file support in a way
 that works with busybox-w32

Don't use /dev/null as test input file for the test for response
file support.

Busybox on Windows does provide POSIX utilities to the point of
being able to build and test ffmpeg, but it doesn't provide full
POSIX emulation like msys2 and cygwin do.

Busybox utils do handle paths like /dev/null themselves. But when
busybox invokes a native Windows executable (like the "$ar" tool
in this test), it passes such a path as-is. Msys2 on the other hand
rewrites unixy paths in command line parameters - even when prefixed;
e.g. "@/dev/null" gets rewritten into "@nul".

Instead of testing with a /dev/null input, test with a real temp
file instead.

(cherry picked from commit 32d7e9d71e2cf5d85728559de904044c520ba2c0)
---
 configure | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/configure b/configure
index 8941ced1f4..89777c0899 100755
--- a/configure
+++ b/configure
@@ -5144,6 +5144,7 @@ tmpfile TMPO   .o
 tmpfile TMPS   .S
 tmpfile TMPSH  .sh
 tmpfile TMPV   .ver
+tmpfile TMPRSP .rsp
 
 unset -f mktemp
 
@@ -8268,7 +8269,8 @@ esac
 
 if [ "$response_files" != "no" ]; then
     ar_out=${FFTMPDIR}/test$LIBSUF
-    respfile="@/dev/null"
+    echo > "$TMPRSP"
+    respfile="@$TMPRSP"
     out_arg="$(echo $ar_o | sed "s;\$@;$ar_out;g")"
     if test_cmd $ar $arflags $out_arg $respfile; then
         response_files="yes"
-- 
2.52.0


From 9d97419239ba9bc78168315f514ac513a329f26d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Storsj=C3=B6?= <[email protected]>
Date: Wed, 24 Jun 2026 11:39:18 +0000
Subject: [PATCH 016/180] configure: Pass the .objs response file to makedef

This fixes generating shared libraries with a toolchain that requires
makedef (MSVC, or mingw toolchains with LLD, that lacks support for
--version-script).

This uses the existing response file generated for linking, by
splitting out the choice of input parameter to makedef to a variable
expanded by make.

This avoids relying on "echo" within makedef supporting long command
lines; msys2/cygwin shells handle it fine, while busybox doesn't.

(cherry picked from commit 8512161c81242e11158133bed019d5a51b089ca8)
---
 compat/windows/makedef | 5 +++++
 configure              | 9 +++++++--
 ffbuild/library.mak    | 5 +++--
 3 files changed, 15 insertions(+), 4 deletions(-)

diff --git a/compat/windows/makedef b/compat/windows/makedef
index af42f08fd5..c5755d6cca 100755
--- a/compat/windows/makedef
+++ b/compat/windows/makedef
@@ -33,6 +33,11 @@ if [ ! -f "$vscript" ]; then
 fi
 
 for object in "$@"; do
+    case "$object" in
+    @*)
+        object="${object#@}"
+        ;;
+    esac
     if [ ! -f "$object" ]; then
         echo "Object does not exist: ${object}" >&2
         exit 1
diff --git a/configure b/configure
index 89777c0899..785e23fb16 100755
--- a/configure
+++ b/configure
@@ -6250,7 +6250,7 @@ case $target_os in
         SLIB_INSTALL_LINKS=
         SLIB_INSTALL_EXTRA_SHLIB='$(SLIBNAME:$(SLIBSUF)=.lib)'
         SLIB_INSTALL_EXTRA_LIB='lib$(SLIBNAME:$(SLIBSUF)=.dll.a) $(SLIBNAME_WITH_MAJOR:$(SLIBSUF)=.def)'
-        SLIB_CREATE_DEF_CMD='EXTERN_PREFIX="$(EXTERN_PREFIX)" AR="$(AR_CMD)" NM="$(NM_CMD)" $(SRC_PATH)/compat/windows/makedef $(SUBDIR)lib$(NAME).ver $(OBJS) > $$(@:$(SLIBSUF)=.def)'
+        SLIB_CREATE_DEF_CMD='EXTERN_PREFIX="$(EXTERN_PREFIX)" AR="$(AR_CMD)" NM="$(NM_CMD)" $(SRC_PATH)/compat/windows/makedef $(SUBDIR)lib$(NAME).ver $(SLIB_CREATE_DEF_INPUTS) > $$(@:$(SLIBSUF)=.def)'
         SHFLAGS='-shared -Wl,--out-implib,$(SUBDIR)lib$(SLIBNAME:$(SLIBSUF)=.dll.a) -Wl,--disable-auto-image-base $$(@:$(SLIBSUF)=.def)'
         enabled x86_64 && objformat="win64" || objformat="win32"
         dlltool="${cross_prefix}dlltool"
@@ -6289,7 +6289,7 @@ case $target_os in
         SLIBSUF=".dll"
         SLIBNAME_WITH_VERSION='$(SLIBPREF)$(FULLNAME)-$(LIBVERSION)$(SLIBSUF)'
         SLIBNAME_WITH_MAJOR='$(SLIBPREF)$(FULLNAME)-$(LIBMAJOR)$(SLIBSUF)'
-        SLIB_CREATE_DEF_CMD='LDFLAGS="$(LDFLAGS)" EXTERN_PREFIX="$(EXTERN_PREFIX)" $(SRC_PATH)/compat/windows/makedef $(SUBDIR)lib$(NAME).ver $(OBJS) > $$(@:$(SLIBSUF)=.def)'
+        SLIB_CREATE_DEF_CMD='LDFLAGS="$(LDFLAGS)" EXTERN_PREFIX="$(EXTERN_PREFIX)" $(SRC_PATH)/compat/windows/makedef $(SUBDIR)lib$(NAME).ver $(SLIB_CREATE_DEF_INPUTS) > $$(@:$(SLIBSUF)=.def)'
         SLIB_INSTALL_NAME='$(SLIBNAME_WITH_MAJOR)'
         SLIB_INSTALL_LINKS=
         SLIB_INSTALL_EXTRA_SHLIB='$(SLIBNAME:$(SLIBSUF)=.lib)'
@@ -8768,6 +8768,11 @@ SLIBNAME=${SLIBNAME}
 SLIBNAME_WITH_VERSION=${SLIBNAME_WITH_VERSION}
 SLIBNAME_WITH_MAJOR=${SLIBNAME_WITH_MAJOR}
 SLIB_CREATE_DEF_CMD=${SLIB_CREATE_DEF_CMD}
+ifeq (\$(RESPONSE_FILES),yes)
+SLIB_CREATE_DEF_INPUTS=@\$\[email protected]
+else
+SLIB_CREATE_DEF_INPUTS=\$(OBJS)
+endif
 SLIB_EXTRA_CMD=${SLIB_EXTRA_CMD}
 SLIB_INSTALL_NAME=${SLIB_INSTALL_NAME}
 SLIB_INSTALL_LINKS=${SLIB_INSTALL_LINKS}
diff --git a/ffbuild/library.mak b/ffbuild/library.mak
index 93b3b2e5a7..2759a73a5c 100644
--- a/ffbuild/library.mak
+++ b/ffbuild/library.mak
@@ -86,14 +86,15 @@ $(SUBDIR)$(SLIBNAME): $(SUBDIR)$(SLIBNAME_WITH_MAJOR)
 	$(Q)cd ./$(SUBDIR) && $(LN_S) $(SLIBNAME_WITH_MAJOR) $(SLIBNAME)
 
 $(SUBDIR)$(SLIBNAME_WITH_MAJOR): $(OBJS) $(SHLIBOBJS) $(SUBDIR)lib$(NAME).ver
-	$(SLIB_CREATE_DEF_CMD)
 ifeq ($(RESPONSE_FILES),yes)
 ifeq ($(HAVE_BUILTIN_FILE),yes)
 	$$(file >[email protected],$$(filter %.o,$$^))
 else
 	$(Q)echo $$(filter %.o,$$^) > [email protected]
 endif
-
+endif
+	$(SLIB_CREATE_DEF_CMD)
+ifeq ($(RESPONSE_FILES),yes)
 	$$(call LINK,$$(call $(NAME)LINK_SO_ARGS) $$(LD_O) @[email protected] $$(call $(NAME)LINK_EXTRA))
 else
 	$$(call LINK,$$(call $(NAME)LINK_SO_ARGS) $$(LD_O) $$(filter %.o,$$^) $$(call $(NAME)LINK_EXTRA))
-- 
2.52.0


From b06c5316b768aad9c147c7582bba8a73a1f7e754 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Mon, 6 Jul 2026 02:53:38 +0200
Subject: [PATCH 017/180] avfilter/vsrc_gfxcapture_winrt: add missing
 system_error include
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Fixes: error: no type named 'system_error' in namespace 'std'
Signed-off-by: Kacper Michajłow <[email protected]>
(cherry picked from commit c6498178bbfcdb6cc67ce01242f3faeb9d039119)
---
 libavfilter/vsrc_gfxcapture_winrt.cpp | 1 +
 1 file changed, 1 insertion(+)

diff --git a/libavfilter/vsrc_gfxcapture_winrt.cpp b/libavfilter/vsrc_gfxcapture_winrt.cpp
index 9cc38342b7..ddc6a7df2d 100644
--- a/libavfilter/vsrc_gfxcapture_winrt.cpp
+++ b/libavfilter/vsrc_gfxcapture_winrt.cpp
@@ -54,6 +54,7 @@ extern "C" {
 #include <mutex>
 #include <regex>
 #include <string>
+#include <system_error>
 #include <thread>
 #include <type_traits>
 
-- 
2.52.0


From ca9d8281024d24f0bf0b018a0ab94d2605ee011b Mon Sep 17 00:00:00 2001
From: Jack Lau <[email protected]>
Date: Fri, 3 Jul 2026 20:56:36 +0800
Subject: [PATCH 018/180] avformat/whip: disable DTLS certificate verification

Fix DTLS failure since 9549c9ad79ee399ff469a420223d4b6118498f7e

WebRTC does not verify CA because it allows self-signed cerificate.

Signed-off-by: Jack Lau <[email protected]>
(cherry picked from commit 6f2f3755a06bdab40028dd8bfcfe91e057aac845)
---
 libavformat/whip.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/libavformat/whip.c b/libavformat/whip.c
index edd9136d3e..ae2c116a4d 100644
--- a/libavformat/whip.c
+++ b/libavformat/whip.c
@@ -396,6 +396,8 @@ static av_cold int dtls_initialize(AVFormatContext *s)
     av_dict_set_int(&opts, "external_sock", 1, 0);
     av_dict_set_int(&opts, "use_srtp", 1, 0);
     av_dict_set_int(&opts, "listen", is_dtls_active ? 0 : 1, 0);
+    // Do not verify CA
+    av_dict_set_int(&opts, "verify", 0, 0);
     ret = ffurl_open_whitelist(&whip->dtls_uc, buf, AVIO_FLAG_READ_WRITE, &s->interrupt_callback,
         &opts, s->protocol_whitelist, s->protocol_blacklist, NULL);
     av_dict_free(&opts);
-- 
2.52.0


From 8d9bd942ab4d4919deb00f5caa9158824baaeab2 Mon Sep 17 00:00:00 2001
From: Lynne <[email protected]>
Date: Sat, 4 Jul 2026 13:31:15 +0900
Subject: [PATCH 019/180] apv_decode: call ff_thread_finish_setup()

"[...] decoders with update_thread_context callback have to call
ff_thread_finish_setup() themselves; it is not done implicitly in
ff_thread_get_buffer() any more.
Not doing so makes the decoder effectively single-threaded."

(cherry picked from commit 300cac3078185718de65bfd66b75b5dcfc57a5c0)
---
 libavcodec/apv_decode.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/libavcodec/apv_decode.c b/libavcodec/apv_decode.c
index c44d5d21ad..05ce42da44 100644
--- a/libavcodec/apv_decode.c
+++ b/libavcodec/apv_decode.c
@@ -384,11 +384,15 @@ static int apv_decode(AVCodecContext *avctx, AVFrame *output,
     apv_derive_tile_info(tile_info, &input->frame_header);
 
     if (avctx->hwaccel) {
-        const FFHWAccel *hwaccel = ffhwaccel(avctx->hwaccel);
-
         err = ff_hwaccel_frame_priv_alloc(avctx, &apv->hwaccel_picture_private);
         if (err < 0)
             return err;
+    }
+
+    ff_thread_finish_setup(avctx);
+
+    if (avctx->hwaccel) {
+        const FFHWAccel *hwaccel = ffhwaccel(avctx->hwaccel);
 
         err = hwaccel->start_frame(avctx, apv->pkt->buf,
                                    apv->pkt->data, apv->pkt->size);
-- 
2.52.0


From e1c08b9c0f4156f0e0f6294a49e492befe95a69f Mon Sep 17 00:00:00 2001
From: Zhao Zhili <[email protected]>
Date: Mon, 15 Jun 2026 22:31:03 +0800
Subject: [PATCH 020/180] avcodec/ffv1enc_vulkan: fix format specifier for
 VkDeviceSize

Fix build warning on macOS:
> format specifies type 'size_t' (aka 'unsigned long') but the
> argument has type 'VkDeviceSize' (aka 'unsigned long long')

VkDeviceSize is typedef as uint64_t.

(cherry picked from commit 97ba3c4a9a02083e26f8b0f639987292bc61bffc)
---
 libavcodec/ffv1enc_vulkan.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libavcodec/ffv1enc_vulkan.c b/libavcodec/ffv1enc_vulkan.c
index 83950cc504..2c83e6869e 100644
--- a/libavcodec/ffv1enc_vulkan.c
+++ b/libavcodec/ffv1enc_vulkan.c
@@ -1387,7 +1387,7 @@ static av_cold int vulkan_encode_ffv1_init(AVCodecContext *avctx)
     maxsize = ff_ffv1_encode_buffer_size(avctx);
     if (maxsize > fv->s.props_11.maxMemoryAllocationSize) {
         av_log(avctx, AV_LOG_WARNING, "Encoding buffer size (%zu) larger "
-                                      "than maximum device allocation (%zu), clipping\n",
+                                      "than maximum device allocation (%"PRIu64"), clipping\n",
                maxsize, fv->s.props_11.maxMemoryAllocationSize);
         maxsize = fv->s.props_11.maxMemoryAllocationSize;
     }
-- 
2.52.0


From 2a31f05fcae5f32165b3b8116f31eec562a09387 Mon Sep 17 00:00:00 2001
From: Lynne <[email protected]>
Date: Fri, 12 Jun 2026 14:27:33 +0900
Subject: [PATCH 021/180] vulkan/prores_raw: reconstruct DC values in 32-bit

DCs can go over int16_t, and when it does, dc_add wraps, so the wrong sign
is reported, which causes errors when decoding more DCs.

Do the prediction in int and narrow to int16 only at store time. The
read_ac_vals() sign change is cosmetic (the value is only ever 0/-1, and
the old code already widened it).

(cherry picked from commit 5784a775dfbabada9b589f289987a2beef7a9071)
---
 libavcodec/vulkan/prores_raw_decode.comp.glsl | 27 ++++++++++---------
 1 file changed, 14 insertions(+), 13 deletions(-)

diff --git a/libavcodec/vulkan/prores_raw_decode.comp.glsl b/libavcodec/vulkan/prores_raw_decode.comp.glsl
index 92859d59d0..3aed23ef7e 100644
--- a/libavcodec/vulkan/prores_raw_decode.comp.glsl
+++ b/libavcodec/vulkan/prores_raw_decode.comp.glsl
@@ -122,13 +122,14 @@ void store_val(ivec2 offs, int blk, int c, int16_t v)
 void read_dc_vals(ivec2 offs, int nb_blocks)
 {
     int dc;
-    int16_t dc_add;
-    int16_t prev_dc = I16(0), sign = I16(0);
+    int dc_add;
+    int prev_dc = 0;
+    int sign = 0;
 
     /* Special handling for first block */
     dc = get_value(I16(700));
-    prev_dc = I16((dc >> 1) ^ -(dc & 1));
-    store_val(offs, 0, 0, prev_dc);
+    prev_dc = (dc >> 1) ^ -(dc & 1);
+    store_val(offs, 0, 0, I16(prev_dc));
 
     for (int n = 1; n < nb_blocks; n++) {
         if (expectEXT(left_bits(gb) <= 0, false))
@@ -142,12 +143,12 @@ void read_dc_vals(ivec2 offs, int nb_blocks)
 
         dc = get_value(dc_codebook);
 
-        sign ^= I16(dc & 1);
-        dc_add = I16((-int(sign) ^ TODCCODEBOOK(dc)) + int(sign));
-        sign = I16(dc_add < 0);
+        sign ^= dc & 1;
+        dc_add = (-sign ^ TODCCODEBOOK(dc)) + sign;
+        sign = int(dc_add < 0);
         prev_dc += dc_add;
 
-        store_val(offs, n, 0, prev_dc);
+        store_val(offs, n, 0, I16(prev_dc));
     }
 }
 
@@ -161,7 +162,7 @@ void read_ac_vals(ivec2 offs, int nb_blocks)
     int16_t ac_codebook = I16(49);
     int16_t rn_codebook = I16( 0);
     int16_t ln_codebook = I16(66);
-    int16_t sign;
+    int sign;
     int16_t val;
 
     for (int n = nb_blocks; n <= nb_codes;) {
@@ -176,9 +177,9 @@ void read_ac_vals(ivec2 offs, int nb_blocks)
 
             ac = get_value(ac_codebook);
             ac_codebook = ac_cb[min(ac, 95 - 1)];
-            sign = -int16_t(get_bit(gb));
+            sign = -int(get_bit(gb));
 
-            val = I16(((ac + 1) ^ int(sign)) - int(sign));
+            val = I16(((ac + 1) ^ sign) - sign);
             store_val(offs, n & block_mask, n >> log2_nb_blocks, val);
 
             n++;
@@ -198,9 +199,9 @@ void read_ac_vals(ivec2 offs, int nb_blocks)
             break;
 
         ac = get_value(ac_codebook);
-        sign = -int16_t(get_bit(gb));
+        sign = -int(get_bit(gb));
 
-        val = I16(((ac + 1) ^ int(sign)) - int(sign));
+        val = I16(((ac + 1) ^ sign) - sign);
         store_val(offs, n & block_mask, n >> log2_nb_blocks, val);
 
         ac_codebook = ac_cb[min(ac, 95 - 1)];
-- 
2.52.0


From 3d129a4a8531e9f7baa8558c7c994b4dd36bcf04 Mon Sep 17 00:00:00 2001
From: Pavel Kohout <[email protected]>
Date: Tue, 30 Jun 2026 21:55:49 +0200
Subject: [PATCH 022/180] avcodec/vulkan_hevc: reject too many VPS HRD
 parameter sets

Fixes: stack buffer overflow
Fixes: tD7Mj0ST7ND3
Fixes: 82864c21112157951ce91b4430a9018edd02f5ab (vulkan_hevc: use VK_KHR_video_maintenance2 if available)
Found-by: Pavel Kohout (Aisle Research)
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit 92737390dc133daadce47dd7d2ec8ef3d9ebcbed)
---
 libavcodec/vulkan_hevc.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/libavcodec/vulkan_hevc.c b/libavcodec/vulkan_hevc.c
index 8f0d1f6636..9b9264f1cd 100644
--- a/libavcodec/vulkan_hevc.c
+++ b/libavcodec/vulkan_hevc.c
@@ -875,6 +875,9 @@ static int vk_hevc_end_frame(AVCodecContext *avctx)
                 vksps_p.vcl_hdr, &vksps_p.ptl, &vksps_p.dpbm,
                 &vksps_p.pal, vksps_p.str, &vksps_p.ltr);
 
+        if (sps->vps->vps_num_hrd_parameters > HEVC_MAX_SUB_LAYERS)
+            return AVERROR_INVALIDDATA;
+
         vkvps_p.sls = vkvps_ps;
         set_vps(sps->vps, &vkvps, &vkvps_p.ptl, &vkvps_p.dpbm,
                 vkvps_p.hdr, vkvps_p.sls);
-- 
2.52.0


From f1137f107412d213965c04a634d32c2b795137b2 Mon Sep 17 00:00:00 2001
From: Philip Langdale <[email protected]>
Date: Mon, 6 Jul 2026 09:24:21 -0700
Subject: [PATCH 023/180] avutil/hwcontext_vulkan: fix CUDA interop for packed
 formats

The current export_to_cuda logic only works for planar and semi-planar formats.
When presented with a single plane packed format, it will incorrectly
calculate the number of channels, resulting in failures later on when cuda
code tries to access the frame. Let's fix it.

(cherry picked from commit 927f205eb8de44fc106a36f00ea9d713c813a4f3)
---
 libavutil/hwcontext_vulkan.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/libavutil/hwcontext_vulkan.c b/libavutil/hwcontext_vulkan.c
index 20b6ed46f8..467ae43b19 100644
--- a/libavutil/hwcontext_vulkan.c
+++ b/libavutil/hwcontext_vulkan.c
@@ -3871,6 +3871,7 @@ static int vulkan_export_to_cuda(AVHWFramesContext *hwfc,
     CudaFunctions *cu = cu_internal->cuda_dl;
     CUarray_format cufmt = desc->comp[0].depth > 8 ? CU_AD_FORMAT_UNSIGNED_INT16 :
                                                      CU_AD_FORMAT_UNSIGNED_INT8;
+    const int elem_size = 1 + desc->comp[0].depth > 8;
 
     dst_f = (AVVkFrame *)frame->data[0];
     dst_int = dst_f->internal;
@@ -3915,7 +3916,7 @@ static int vulkan_export_to_cuda(AVHWFramesContext *hwfc,
                 .arrayDesc = {
                     .Depth = 0,
                     .Format = cufmt,
-                    .NumChannels = 1 + ((planes == 2) && i),
+                    .NumChannels = desc->comp[i].step / elem_size,
                     .Flags = 0,
                 },
                 .numLevels = 1,
-- 
2.52.0


From 6f2c280187a6a2bee9c1d1b28031ae8538b04057 Mon Sep 17 00:00:00 2001
From: Philip Langdale <[email protected]>
Date: Mon, 6 Jul 2026 10:04:10 -0700
Subject: [PATCH 024/180] avutil/hwcontext_vulkan: CUDA interop semaphores are
 per image

The current semaphore logic dates back to a time where we did not have any
multiplane images, and it has not kept up. As a result, we currently try and
manipulate too many semaphores when dealing with multiplane images, leading to
errors and crashes. Let's fix it.

(cherry picked from commit 30ba440386deb62eb9e489c8939812138a71afe6)
---
 libavutil/hwcontext_vulkan.c | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/libavutil/hwcontext_vulkan.c b/libavutil/hwcontext_vulkan.c
index 467ae43b19..bb0d6f203f 100644
--- a/libavutil/hwcontext_vulkan.c
+++ b/libavutil/hwcontext_vulkan.c
@@ -3963,6 +3963,7 @@ static int vulkan_transfer_data_from_cuda(AVHWFramesContext *hwfc,
     VulkanFramesPriv *fp = hwfc->hwctx;
     const int planes = av_pix_fmt_count_planes(hwfc->sw_format);
     const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(hwfc->sw_format);
+    int nb_images;
 
     AVHWFramesContext *cuda_fc = (AVHWFramesContext*)src->hw_frames_ctx->data;
     AVHWDeviceContext *cuda_cu = cuda_fc->device_ctx;
@@ -3973,6 +3974,7 @@ static int vulkan_transfer_data_from_cuda(AVHWFramesContext *hwfc,
     CUDA_EXTERNAL_SEMAPHORE_SIGNAL_PARAMS s_s_par[AV_NUM_DATA_POINTERS] = { 0 };
 
     dst_f = (AVVkFrame *)dst->data[0];
+    nb_images = ff_vk_count_images(dst_f);
 
     err = prepare_frame(hwfc, &fp->upload_exec, dst_f, PREP_MODE_EXTERNAL_EXPORT);
     if (err < 0)
@@ -3990,13 +3992,13 @@ static int vulkan_transfer_data_from_cuda(AVHWFramesContext *hwfc,
 
     dst_int = dst_f->internal;
 
-    for (int i = 0; i < planes; i++) {
+    for (int i = 0; i < nb_images; i++) {
         s_w_par[i].params.fence.value = dst_f->sem_value[i] + 0;
         s_s_par[i].params.fence.value = dst_f->sem_value[i] + 1;
     }
 
     err = CHECK_CU(cu->cuWaitExternalSemaphoresAsync(dst_int->cu_sem, s_w_par,
-                                                     planes, cuda_dev->stream));
+                                                     nb_images, cuda_dev->stream));
     if (err < 0)
         goto fail;
 
@@ -4023,11 +4025,11 @@ static int vulkan_transfer_data_from_cuda(AVHWFramesContext *hwfc,
     }
 
     err = CHECK_CU(cu->cuSignalExternalSemaphoresAsync(dst_int->cu_sem, s_s_par,
-                                                       planes, cuda_dev->stream));
+                                                       nb_images, cuda_dev->stream));
     if (err < 0)
         goto fail;
 
-    for (int i = 0; i < planes; i++)
+    for (int i = 0; i < nb_images; i++)
         dst_f->sem_value[i]++;
 
     CHECK_CU(cu->cuCtxPopCurrent(&dummy));
@@ -4878,7 +4880,7 @@ static int vulkan_transfer_data_to_cuda(AVHWFramesContext *hwfc, AVFrame *dst,
 
     dst_int = dst_f->internal;
 
-    for (int i = 0; i < planes; i++) {
+    for (int i = 0; i < nb_images; i++) {
         s_w_par[i].params.fence.value = dst_f->sem_value[i] + 0;
         s_s_par[i].params.fence.value = dst_f->sem_value[i] + 1;
     }
@@ -4915,7 +4917,7 @@ static int vulkan_transfer_data_to_cuda(AVHWFramesContext *hwfc, AVFrame *dst,
     if (err < 0)
         goto fail;
 
-    for (int i = 0; i < planes; i++)
+    for (int i = 0; i < nb_images; i++)
         dst_f->sem_value[i]++;
 
     CHECK_CU(cu->cuCtxPopCurrent(&dummy));
-- 
2.52.0


From 63211cbec12066e180e3f63d2303903c88d5892e Mon Sep 17 00:00:00 2001
From: Philip Langdale <[email protected]>
Date: Mon, 6 Jul 2026 12:36:02 -0700
Subject: [PATCH 025/180] avutil/hwcontext_vulkan: reject unsupported
 semi-planar CUDA imports

Cuda currently only supports packed and single-component planar formats, but
fails to import semi-planer (eg: NV12, P010). Even though there are now
semi-planar cuda array formats, which the latest nvdec can use, these formats
are not used when mapping Vulkan imports. Maybe they'll fix that some day.

But until then, let's explicitly detect the case and return a clear error
message for the user.

Exercising this error path revealed that vulkan_free_internal() frees
f->internal on a transfer error and then runs again when the frame is
destroyed, dereferencing the freed pointer; let's make it idempotent.

(cherry picked from commit c29d710cd5d0f80bbb56f7ec1f35d5fb7ed44d05)
---
 libavutil/hwcontext_vulkan.c | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/libavutil/hwcontext_vulkan.c b/libavutil/hwcontext_vulkan.c
index bb0d6f203f..fad35f9ad6 100644
--- a/libavutil/hwcontext_vulkan.c
+++ b/libavutil/hwcontext_vulkan.c
@@ -2309,6 +2309,10 @@ static void vulkan_free_internal(VulkanDevicePriv *p, AVVkFrame *f)
 {
     av_unused AVVkFrameInternal *internal = f->internal;
 
+    // Make this function safe to call repeatedly
+    if (!internal)
+        return;
+
 #if CONFIG_CUDA
     if (internal->cuda_fc_ref) {
         AVHWFramesContext *cuda_fc = (AVHWFramesContext *)internal->cuda_fc_ref->data;
@@ -3898,6 +3902,18 @@ static int vulkan_export_to_cuda(AVHWFramesContext *hwfc,
 
         if (nb_images != planes) {
             for (int i = 0; i < planes; i++) {
+                /* Cuda now defines array formats for semi-planar, but these are
+                 * not currently supported for imported Vulkan images. */
+                if (desc->comp[i].step / elem_size > 1) {
+                    av_log(ctx, AV_LOG_ERROR,
+                           "Cannot map a multiplane Vulkan image (%d image(s) "
+                           "for %d plane(s)) to CUDA; create the Vulkan device "
+                           "with the disable_multiplane=1 option (one image per "
+                           "plane) for CUDA interop.\n", nb_images, planes);
+                    err = AVERROR(ENOSYS);
+                    goto fail;
+                }
+
                 VkImageSubresource subres = {
                     .aspectMask = i == 2 ? VK_IMAGE_ASPECT_MEMORY_PLANE_2_BIT_EXT :
                                   i == 1 ? VK_IMAGE_ASPECT_MEMORY_PLANE_1_BIT_EXT :
-- 
2.52.0


From 68fa1f7f486c444e74fbb20917278730a9c630ed Mon Sep 17 00:00:00 2001
From: Zhao Zhili <[email protected]>
Date: Thu, 9 Jul 2026 20:51:07 +0800
Subject: [PATCH 026/180] libavutil/vulkan: fix elem_size computation in
 vulkan_export_to_cuda

(cherry picked from commit b4b30ff8fd1580c4fc1330afdf6c8fde242e1754)
---
 libavutil/hwcontext_vulkan.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libavutil/hwcontext_vulkan.c b/libavutil/hwcontext_vulkan.c
index fad35f9ad6..2de2de7e93 100644
--- a/libavutil/hwcontext_vulkan.c
+++ b/libavutil/hwcontext_vulkan.c
@@ -3875,7 +3875,7 @@ static int vulkan_export_to_cuda(AVHWFramesContext *hwfc,
     CudaFunctions *cu = cu_internal->cuda_dl;
     CUarray_format cufmt = desc->comp[0].depth > 8 ? CU_AD_FORMAT_UNSIGNED_INT16 :
                                                      CU_AD_FORMAT_UNSIGNED_INT8;
-    const int elem_size = 1 + desc->comp[0].depth > 8;
+    const int elem_size = 1 + (desc->comp[0].depth > 8);
 
     dst_f = (AVVkFrame *)frame->data[0];
     dst_int = dst_f->internal;
-- 
2.52.0


From 30ae65e506cdb6078714e6d808a5dea28fd66fb2 Mon Sep 17 00:00:00 2001
From: Zhao Zhili <[email protected]>
Date: Thu, 9 Jul 2026 22:19:44 +0800
Subject: [PATCH 027/180] avutil/hwcontext_vulkan: query the actual image
 format in try_export_flags

GetPhysicalDeviceImageFormatProperties2() must be queried with the format
that will actually be used to create the image, otherwise the external
memory export capability check answers for a different format than the one
being allocated.

When AV_VK_FRAME_FLAG_DISABLE_MULTIPLANE is set (e.g. by ffplay for CUDA
interop), vulkan_frames_init selects the per-plane fallback format via
vkfmt_from_pixfmt2(disable_multiplane=1) and stores it in hwctx->format[].
The image is then created with that fallback format. Querying the
multiplane vkf instead returns capability for a format that is never
instantiated, and on NVIDIA GPUs with OPTIMAL tiling the answer differs
between the two formats, producing broken CUDA hwaccel output.

Read hwctx->format[0], which is populated by vulkan_frames_init before
vulkan_pool_alloc runs, so the query matches the image regardless of
whether the multiplane or fallback path was taken.

Fix artifacts with `ffplay -hwaccel cuda foo.mp4`.

(cherry picked from commit 0baa71b53cd9dc6ecf0cf9a5c271bcbde04135ba)
---
 libavutil/hwcontext_vulkan.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libavutil/hwcontext_vulkan.c b/libavutil/hwcontext_vulkan.c
index 2de2de7e93..3da0d3c04d 100644
--- a/libavutil/hwcontext_vulkan.c
+++ b/libavutil/hwcontext_vulkan.c
@@ -2799,7 +2799,7 @@ static void try_export_flags(AVHWFramesContext *hwfc,
     VkPhysicalDeviceImageFormatInfo2 pinfo = {
         .sType  = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2,
         .pNext  = !exp ? NULL : &enext,
-        .format = vk_find_format_entry(hwfc->sw_format)->vkf,
+        .format = hwctx->format[0],
         .type   = VK_IMAGE_TYPE_2D,
         .tiling = hwctx->tiling,
         .usage  = hwctx->usage,
-- 
2.52.0


From 6758e66fb5272fd46d3454d2b7b3a4195037a4a3 Mon Sep 17 00:00:00 2001
From: Lynne <[email protected]>
Date: Sun, 5 Jul 2026 16:30:10 +0900
Subject: [PATCH 028/180] ffv1enc_vulkan: fix VRAM size detection

The reduction accumulated into the zero-initialized context field
instead of the local variable, so the detected VRAM was whatever
device-local heap the implementation listed last, usually the
256MiB BAR heap rather than the multi-GiB VRAM heap.

This made the encoder take the host-memory fallback (or fail the
allocation outright at 12K and above) on hardware with plenty of VRAM.

(cherry picked from commit 43d0d365f3bb4ca9f6e5e6615a4f7aecc6e9df9d)
---
 libavcodec/ffv1enc_vulkan.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libavcodec/ffv1enc_vulkan.c b/libavcodec/ffv1enc_vulkan.c
index 2c83e6869e..fdbe63efa9 100644
--- a/libavcodec/ffv1enc_vulkan.c
+++ b/libavcodec/ffv1enc_vulkan.c
@@ -1376,7 +1376,7 @@ static av_cold int vulkan_encode_ffv1_init(AVCodecContext *avctx)
     max_host_size = 0;
     for (int i = 0; i < fv->s.mprops.memoryHeapCount; i++) {
         if (fv->s.mprops.memoryHeaps[i].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT)
-            max_heap_size = FFMAX(fv->max_heap_size,
+            max_heap_size = FFMAX(max_heap_size,
                                   fv->s.mprops.memoryHeaps[i].size);
         if (!(fv->s.mprops.memoryHeaps[i].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT))
             max_host_size = FFMAX(max_host_size,
-- 
2.52.0


From b9aa305686df5f3bd0fe48fb70086d260ab7791e Mon Sep 17 00:00:00 2001
From: Philip Langdale <[email protected]>
Date: Wed, 8 Jul 2026 16:56:34 -0700
Subject: [PATCH 029/180] avutil/hwcontext_vulkan: declare cuda as a
 transfer-compatible format

The cuda hwcontext has listed AV_PIX_FMT_VULKAN among its supported
formats since Vulkan interop was first added, so a device's frame
constraints advertise Vulkan as a transfer-compatible ("sw") format.
This is what allows an hwupload targeting a cuda device to accept a
Vulkan frame as input during format negotiation.

The Vulkan hwcontext never made the reciprocal declaration, so an
hwupload targeting a Vulkan device would not accept a cuda frame, and a
cuda -> Vulkan upload could not be configured even though the transfer
itself is supported. Advertise AV_PIX_FMT_CUDA in the Vulkan frame
constraints to ensure we have consistent behaviour in both directions.

(cherry picked from commit d09d5afc3aebede25d2d245ee23b75a47ea17c3a)
---
 libavutil/hwcontext_vulkan.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/libavutil/hwcontext_vulkan.c b/libavutil/hwcontext_vulkan.c
index 3da0d3c04d..90d3d14dbb 100644
--- a/libavutil/hwcontext_vulkan.c
+++ b/libavutil/hwcontext_vulkan.c
@@ -2215,7 +2215,7 @@ static int vulkan_frames_get_constraints(AVHWDeviceContext *ctx,
                                     NULL, NULL, NULL, NULL, p->disable_multiplane, 1) >= 0;
     }
 
-    constraints->valid_sw_formats = av_malloc_array(count + 1,
+    constraints->valid_sw_formats = av_malloc_array(count + 1 + CONFIG_CUDA,
                                                     sizeof(enum AVPixelFormat));
     if (!constraints->valid_sw_formats)
         return AVERROR(ENOMEM);
@@ -2230,6 +2230,10 @@ static int vulkan_frames_get_constraints(AVHWDeviceContext *ctx,
         }
     }
 
+#if CONFIG_CUDA
+    constraints->valid_sw_formats[count++] = AV_PIX_FMT_CUDA;
+#endif
+
     constraints->valid_sw_formats[count++] = AV_PIX_FMT_NONE;
 
     constraints->min_width  = 1;
-- 
2.52.0


From c79f25b7572201e29f1852098ec27a7b148b99a4 Mon Sep 17 00:00:00 2001
From: Lynne <[email protected]>
Date: Mon, 13 Jul 2026 00:13:59 +0900
Subject: [PATCH 030/180] swscale/vulkan: fix invalid SPIR-V generation for
 plane-remapped passes

The CLEAR codegen iterated components by value[i].den, while the
constants pass iterates by clear.mask; unmasked components may hold
leftover values with a nonzero denominator, consuming more constant
IDs than were registered and emitting ID 0 into the instruction
stream.

The image handle arrays were also sized by the number of planes an
op touches, but plane_src/plane_dst contain actual frame plane
indices, so a pass writing only e.g. the alpha plane references
handle 3 while only handle 0 was loaded.

Either results in invalid SPIR-V, which crashes RADV inside
spirv_to_nir when creating the shader object.

(cherry picked from commit b2b0429d153cdbc1af0711af98e18d4ae861d4cf)
---
 libswscale/vulkan/ops.c | 18 ++++++++++++++----
 1 file changed, 14 insertions(+), 4 deletions(-)

diff --git a/libswscale/vulkan/ops.c b/libswscale/vulkan/ops.c
index 045f220745..54f1c689eb 100644
--- a/libswscale/vulkan/ops.c
+++ b/libswscale/vulkan/ops.c
@@ -908,6 +908,16 @@ static int read_filtered(SPICtx *spi, SPIRVIDs *id, const SwsOpList *ops,
                                     acc_s[0], acc_s[1], acc_s[2], acc_s[3]);
 }
 
+/* Plane indices refer to actual frame planes, so the image handle arrays
+ * have to cover the highest plane referenced, not just the plane count. */
+static int rw_op_img_count(const SwsOp *op, const uint8_t *planes)
+{
+    int count = 0;
+    for (int i = 0; i < ff_sws_rw_op_planes(op); i++)
+        count = FFMAX(count, planes[i] + 1);
+    return count;
+}
+
 static int add_ops_spirv(SwsContext *sws, VulkanPriv *p, FFVulkanOpsCtx *s,
                          const SwsOpList *ops, FFVulkanShader *shd)
 {
@@ -929,11 +939,11 @@ static int add_ops_spirv(SwsContext *sws, VulkanPriv *p, FFVulkanOpsCtx *s,
 
     /* Image ops, to determine types */
     const SwsOp *op_w = ff_sws_op_list_output(ops);
-    int out_img_count = ff_sws_rw_op_planes(op_w);
+    int out_img_count = rw_op_img_count(op_w, ops->plane_dst);
     p->dst_rep = op_w->type == SWS_PIXEL_F32 ? FF_VK_REP_FLOAT : FF_VK_REP_UINT;
 
     const SwsOp *op_r = ff_sws_op_list_input(ops);
-    int in_img_count = op_r ? ff_sws_rw_op_planes(op_r) : 0;
+    int in_img_count = op_r ? rw_op_img_count(op_r, ops->plane_src) : 0;
     if (op_r)
         p->src_rep = op_r->type == SWS_PIXEL_F32 ? FF_VK_REP_FLOAT : FF_VK_REP_UINT;
 
@@ -1038,7 +1048,7 @@ static int add_ops_spirv(SwsContext *sws, VulkanPriv *p, FFVulkanOpsCtx *s,
     }
 
     /* Load output image handles */
-    int out_img[4];
+    int out_img[4] = { 0 };
     for (int i = 0; i < out_img_count; i++) {
         int img = spi_OpAccessChain(spi, id->out_img_sptr,
                                     id->in_vars[2], id->u32_cid[i]);
@@ -1171,7 +1181,7 @@ static int add_ops_spirv(SwsContext *sws, VulkanPriv *p, FFVulkanOpsCtx *s,
             break;
         case SWS_OP_CLEAR:
             for (int i = 0; i < 4; i++) {
-                if (!op->clear.value[i].den)
+                if (!SWS_COMP_TEST(op->clear.mask, i))
                     continue;
                 data = spi_OpCompositeInsert(spi, type_v,
                                              id->const_ids[nb_const_ids++],
-- 
2.52.0


From 716d2a47c565ed41aa18aa59ed38d7e90ce34ccc Mon Sep 17 00:00:00 2001
From: Lynne <[email protected]>
Date: Mon, 13 Jul 2026 00:13:59 +0900
Subject: [PATCH 031/180] swscale/ops_dispatch: fix use-after-free when adding
 opaque ops passes

comp points into p, which is freed before comp->backend is read.
Use the copy taken before the free.

(cherry picked from commit 4b9c4b9cfb56126dccd9dd4e0c78736d1f5d6ff1)
---
 libswscale/ops_dispatch.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libswscale/ops_dispatch.c b/libswscale/ops_dispatch.c
index dd166a5f92..2da38fc333 100644
--- a/libswscale/ops_dispatch.c
+++ b/libswscale/ops_dispatch.c
@@ -550,7 +550,7 @@ static int compile_single(const CompileArgs *args, const SwsOpList *ops,
                                     input, 0, c.slice_align, c.func_opaque,
                                     NULL, c.priv, c.free, output);
         if (ret >= 0) {
-            (*output)->backend = comp->backend->flags;
+            (*output)->backend = c.backend->flags;
             ff_sws_pass_link_output(*output, link);
         }
         return ret;
-- 
2.52.0


From 7c028af129157d8b61f1f9025c6fc48d6a5236dc Mon Sep 17 00:00:00 2001
From: Lynne <[email protected]>
Date: Sun, 12 Jul 2026 14:10:15 +0900
Subject: [PATCH 032/180] avcodec: remove unneeded vulkan_spirv.h includes

Leftover crud from before the rewrite to compile-time SPIR-V.

(cherry picked from commit 1a4250e9249682bae2c1e888f9d751a5f34ee9c0)
---
 libavcodec/proresenc_kostya_vulkan.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/libavcodec/proresenc_kostya_vulkan.c b/libavcodec/proresenc_kostya_vulkan.c
index 11b11675b7..ab71394337 100644
--- a/libavcodec/proresenc_kostya_vulkan.c
+++ b/libavcodec/proresenc_kostya_vulkan.c
@@ -27,7 +27,6 @@
 #include "libavutil/mem_internal.h"
 #include "libavutil/opt.h"
 #include "libavutil/pixdesc.h"
-#include "libavutil/vulkan_spirv.h"
 #include "libavutil/hwcontext_vulkan.h"
 #include "libavutil/vulkan_loader.h"
 #include "libavutil/vulkan.h"
-- 
2.52.0


From 9b53c361abf2fc28574e43f0608fc9ffe4b0105c Mon Sep 17 00:00:00 2001
From: Lynne <[email protected]>
Date: Sun, 12 Jul 2026 14:10:31 +0900
Subject: [PATCH 033/180] vf_scale_vulkan: port to compile-time SPIR-V
 generation

Same functionality as before, but cleaner.

(cherry picked from commit 6219d4fdd1fb61b7fff0c713f92dbdd156feaa35)
---
 libavfilter/vf_scale_vulkan.c        | 229 +++++++--------------------
 libavfilter/vulkan/Makefile          |   3 +-
 libavfilter/vulkan/debayer.comp.glsl |   1 +
 libavfilter/vulkan/scale.comp.glsl   | 120 ++++++++++++++
 4 files changed, 182 insertions(+), 171 deletions(-)
 create mode 100644 libavfilter/vulkan/scale.comp.glsl

diff --git a/libavfilter/vf_scale_vulkan.c b/libavfilter/vf_scale_vulkan.c
index 19b4e5e5ac..4e54d8806c 100644
--- a/libavfilter/vf_scale_vulkan.c
+++ b/libavfilter/vf_scale_vulkan.c
@@ -18,9 +18,7 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  */
 
-#include "libavutil/random_seed.h"
 #include "libavutil/opt.h"
-#include "libavutil/vulkan_spirv.h"
 #include "vulkan_filter.h"
 #include "scale_eval.h"
 #include "filters.h"
@@ -28,6 +26,9 @@
 #include "video.h"
 #include "libswscale/swscale.h"
 
+extern const unsigned char ff_scale_comp_spv_data[];
+extern const unsigned int ff_scale_comp_spv_len;
+
 extern const unsigned char ff_debayer_comp_spv_data[];
 extern const unsigned int ff_debayer_comp_spv_len;
 
@@ -45,6 +46,14 @@ enum DebayerFunc {
     DB_NB,
 };
 
+/* Output modes, must match scale.comp.glsl */
+enum ScaleMode {
+    MODE_COPY = 0,
+    MODE_NV12,
+    MODE_YUV420,
+    MODE_YUV444,
+};
+
 typedef struct ScaleVulkanContext {
     FFVulkanContext vkctx;
     SwsContext *sws;
@@ -62,6 +71,7 @@ typedef struct ScaleVulkanContext {
         int crop_y;
         int crop_w;
         int crop_h;
+        float in_dims[2];
     } opts;
 
     char *out_format_string;
@@ -73,145 +83,17 @@ typedef struct ScaleVulkanContext {
     enum DebayerFunc debayer;
 } ScaleVulkanContext;
 
-static const char scale_bilinear[] = {
-    C(0, vec4 scale_bilinear(int idx, ivec2 pos, vec2 crop_range, vec2 crop_off))
-    C(0, {                                                                      )
-    C(1,     vec2 npos = (vec2(pos) + 0.5f) / imageSize(output_img[idx]);       )
-    C(1,     npos *= crop_range;    /* Reduce the range */                      )
-    C(1,     npos += crop_off;      /* Offset the start */                      )
-    C(1,     return texture(input_img[idx], npos);                              )
-    C(0, }                                                                      )
-};
-
-static const char rgb2yuv[] = {
-    C(0, vec4 rgb2yuv(vec4 src, int fullrange)                                  )
-    C(0, {                                                                      )
-    C(1,     src *= yuv_matrix;                                                 )
-    C(1,     if (fullrange == 1) {                                              )
-    C(2,         src += vec4(0.0, 0.5, 0.5, 0.0);                               )
-    C(1,     } else {                                                           )
-    C(2,         src *= vec4(219.0 / 255.0, 224.0 / 255.0, 224.0 / 255.0, 1.0); )
-    C(2,         src += vec4(16.0 / 255.0, 128.0 / 255.0, 128.0 / 255.0, 0.0);  )
-    C(1,     }                                                                  )
-    C(1,     return src;                                                        )
-    C(0, }                                                                      )
-};
-
-static const char write_nv12[] = {
-    C(0, void write_nv12(vec4 src, ivec2 pos)                                   )
-    C(0, {                                                                      )
-    C(1,     imageStore(output_img[0], pos, vec4(src.r, 0.0, 0.0, 0.0));        )
-    C(1,     pos /= ivec2(2);                                                   )
-    C(1,     imageStore(output_img[1], pos, vec4(src.g, src.b, 0.0, 0.0));      )
-    C(0, }                                                                      )
-};
-
-static const char write_420[] = {
-    C(0, void write_420(vec4 src, ivec2 pos)                                    )
-    C(0, {                                                                      )
-    C(1,     imageStore(output_img[0], pos, vec4(src.r, 0.0, 0.0, 0.0));        )
-    C(1,     pos /= ivec2(2);                                                   )
-    C(1,     imageStore(output_img[1], pos, vec4(src.g, 0.0, 0.0, 0.0));        )
-    C(1,     imageStore(output_img[2], pos, vec4(src.b, 0.0, 0.0, 0.0));        )
-    C(0, }                                                                      )
-};
-
-static const char write_444[] = {
-    C(0, void write_444(vec4 src, ivec2 pos)                                    )
-    C(0, {                                                                      )
-    C(1,     imageStore(output_img[0], pos, vec4(src.r, 0.0, 0.0, 0.0));        )
-    C(1,     imageStore(output_img[1], pos, vec4(src.g, 0.0, 0.0, 0.0));        )
-    C(1,     imageStore(output_img[2], pos, vec4(src.b, 0.0, 0.0, 0.0));        )
-    C(0, }                                                                      )
-};
-
-static int init_scale_shader(AVFilterContext *ctx, FFVulkanShader *shd,
-                             FFVulkanDescriptorSetBinding *desc, AVFrame *in)
-{
-    ScaleVulkanContext *s = ctx->priv;
-    GLSLD(   scale_bilinear                                                  );
-
-    if (s->vkctx.output_format != s->vkctx.input_format) {
-        GLSLD(   rgb2yuv                                                     );
-    }
-
-    switch (s->vkctx.output_format) {
-    case AV_PIX_FMT_NV12:    GLSLD(write_nv12); break;
-    case AV_PIX_FMT_YUV420P: GLSLD( write_420); break;
-    case AV_PIX_FMT_YUV444P: GLSLD( write_444); break;
-    default: break;
-    }
-
-    GLSLC(0, void main()                                                     );
-    GLSLC(0, {                                                               );
-    GLSLC(1,     ivec2 size;                                                 );
-    GLSLC(1,     ivec2 pos = ivec2(gl_GlobalInvocationID.xy);                );
-    GLSLF(1,     vec2 in_d = vec2(%i, %i);             ,in->width, in->height);
-    GLSLC(1,     vec2 c_r = vec2(crop_w, crop_h) / in_d;                     );
-    GLSLC(1,     vec2 c_o = vec2(crop_x, crop_y) / in_d;                     );
-    GLSLC(0,                                                                 );
-
-    if (s->vkctx.output_format == s->vkctx.input_format) {
-        for (int i = 0; i < desc[1].elems; i++) {
-            GLSLF(1,  size = imageSize(output_img[%i]);                    ,i);
-            GLSLC(1,  if (IS_WITHIN(pos, size)) {                            );
-            switch (s->scaler) {
-            case F_NEAREST:
-            case F_BILINEAR:
-                GLSLF(2, vec4 res = scale_bilinear(%i, pos, c_r, c_o);     ,i);
-                GLSLF(2, imageStore(output_img[%i], pos, res);             ,i);
-                break;
-            };
-            GLSLC(1, }                                                       );
-        }
-    } else {
-        GLSLC(1, vec4 res = scale_bilinear(0, pos, c_r, c_o);                );
-        GLSLF(1, res = rgb2yuv(res, %i);    ,s->out_range == AVCOL_RANGE_JPEG);
-        switch (s->vkctx.output_format) {
-        case AV_PIX_FMT_NV12:    GLSLC(1, write_nv12(res, pos); ); break;
-        case AV_PIX_FMT_YUV420P: GLSLC(1,  write_420(res, pos); ); break;
-        case AV_PIX_FMT_YUV444P: GLSLC(1,  write_444(res, pos); ); break;
-        default: return AVERROR(EINVAL);
-        }
-    }
-
-    GLSLC(0, }                                                               );
-
-    if (s->vkctx.output_format != s->vkctx.input_format) {
-        const AVLumaCoefficients *lcoeffs;
-        double tmp_mat[3][3];
-
-        lcoeffs = av_csp_luma_coeffs_from_avcsp(in->colorspace);
-        if (!lcoeffs) {
-            av_log(ctx, AV_LOG_ERROR, "Unsupported colorspace\n");
-            return AVERROR(EINVAL);
-        }
-
-        ff_fill_rgb2yuv_table(lcoeffs, tmp_mat);
-
-        for (int y = 0; y < 3; y++)
-            for (int x = 0; x < 3; x++)
-                s->opts.yuv_matrix[x][y] = tmp_mat[x][y];
-        s->opts.yuv_matrix[3][3] = 1.0;
-    }
-
-    return 0;
-}
-
 static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
 {
     int err;
-    uint8_t *spv_data;
-    size_t spv_len;
-    void *spv_opaque = NULL;
     VkFilter sampler_mode;
+    enum ScaleMode mode;
     ScaleVulkanContext *s = ctx->priv;
     FFVulkanContext *vkctx = &s->vkctx;
     FFVulkanShader *shd = &s->shd;
-    FFVkSPIRVCompiler *spv;
-    FFVulkanDescriptorSetBinding *desc;
 
     int in_planes = av_pix_fmt_count_planes(s->vkctx.input_format);
+    int out_planes = av_pix_fmt_count_planes(s->vkctx.output_format);
 
     switch (s->scaler) {
     case F_NEAREST:
@@ -222,29 +104,33 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
         break;
     };
 
-    spv = ff_vk_spirv_init();
-    if (!spv) {
-        av_log(ctx, AV_LOG_ERROR, "Unable to initialize SPIR-V compiler!\n");
-        return AVERROR_EXTERNAL;
+    if (s->vkctx.output_format == s->vkctx.input_format) {
+        mode = MODE_COPY;
+    } else {
+        switch (s->vkctx.output_format) {
+        case AV_PIX_FMT_NV12:    mode = MODE_NV12;   break;
+        case AV_PIX_FMT_YUV420P: mode = MODE_YUV420; break;
+        case AV_PIX_FMT_YUV444P: mode = MODE_YUV444; break;
+        default: return AVERROR(EINVAL);
+        }
     }
 
     RET(ff_vk_exec_pool_init(vkctx, s->qf, &s->e, s->qf->num*4, 0, 0, 0, NULL));
 
     RET(ff_vk_init_sampler(vkctx, &s->sampler, 0, sampler_mode));
 
-    RET(ff_vk_shader_init(vkctx, &s->shd, "scale",
-                          VK_SHADER_STAGE_COMPUTE_BIT,
-                          NULL, 0,
-                          32, 32, 1,
-                          0));
+    SPEC_LIST_CREATE(sl, 3, 3*sizeof(int32_t))
+    SPEC_LIST_ADD(sl, 0, 32, out_planes);
+    SPEC_LIST_ADD(sl, 1, 32, mode);
+    SPEC_LIST_ADD(sl, 2, 32, s->out_range == AVCOL_RANGE_JPEG);
 
-    desc = (FFVulkanDescriptorSetBinding []) {
+    ff_vk_shader_load(&s->shd, VK_SHADER_STAGE_COMPUTE_BIT, sl,
+                      (uint32_t []) { 32, 32, 1 }, 0);
+
+    const FFVulkanDescriptorSetBinding desc[] = {
         {
             .name       = "input_img",
             .type       = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
-            .mem_layout = NULL,
-            .mem_quali  = "readonly",
-            .dimensions = 2,
             .elems      = in_planes,
             .stages     = VK_SHADER_STAGE_COMPUTE_BIT,
             .samplers   = DUP_SAMPLER(s->sampler),
@@ -252,46 +138,47 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
         {
             .name       = "output_img",
             .type       = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
-            .mem_layout = ff_vk_shader_rep_fmt(s->vkctx.output_format, FF_VK_REP_FLOAT),
-            .mem_quali  = "writeonly",
-            .dimensions = 2,
-            .elems      = av_pix_fmt_count_planes(s->vkctx.output_format),
+            .elems      = out_planes,
             .stages     = VK_SHADER_STAGE_COMPUTE_BIT,
         },
     };
 
-    RET(ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 2, 0, 0));
-
-    GLSLC(0, layout(push_constant, std430) uniform pushConstants {        );
-    GLSLC(1,    mat4 yuv_matrix;                                          );
-    GLSLC(1,    int crop_x;                                               );
-    GLSLC(1,    int crop_y;                                               );
-    GLSLC(1,    int crop_w;                                               );
-    GLSLC(1,    int crop_h;                                               );
-    GLSLC(0, };                                                           );
-    GLSLC(0,                                                              );
+    ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 2, 0, 0);
 
     ff_vk_shader_add_push_const(&s->shd, 0, sizeof(s->opts),
                                 VK_SHADER_STAGE_COMPUTE_BIT);
 
-    err = init_scale_shader(ctx, shd, desc, in);
-    if (err < 0)
-        goto fail;
+    if (mode != MODE_COPY) {
+        const AVLumaCoefficients *lcoeffs;
+        double tmp_mat[3][3];
 
-    RET(spv->compile_shader(vkctx, spv, shd, &spv_data, &spv_len, "main",
-                            &spv_opaque));
-    RET(ff_vk_shader_link(vkctx, shd, spv_data, spv_len, "main"));
+        lcoeffs = av_csp_luma_coeffs_from_avcsp(in->colorspace);
+        if (!lcoeffs) {
+            av_log(ctx, AV_LOG_ERROR, "Unsupported colorspace\n");
+            err = AVERROR(EINVAL);
+            goto fail;
+        }
+
+        ff_fill_rgb2yuv_table(lcoeffs, tmp_mat);
+
+        for (int y = 0; y < 3; y++)
+            for (int x = 0; x < 3; x++)
+                s->opts.yuv_matrix[x][y] = tmp_mat[x][y];
+        s->opts.yuv_matrix[3][3] = 1.0;
+    }
+
+    s->opts.in_dims[0] = in->width;
+    s->opts.in_dims[1] = in->height;
+
+    RET(ff_vk_shader_link(vkctx, shd,
+                          ff_scale_comp_spv_data,
+                          ff_scale_comp_spv_len, "main"));
 
     RET(ff_vk_shader_register_exec(vkctx, &s->e, &s->shd));
 
     s->initialized = 1;
 
 fail:
-    if (spv_opaque)
-        spv->free_shader(spv, &spv_opaque);
-    if (spv)
-        spv->uninit(&spv);
-
     return err;
 }
 
@@ -455,6 +342,8 @@ static int scale_vulkan_config_output(AVFilterLink *outlink)
         if (!s->sws)
             return AVERROR(ENOMEM);
         av_opt_set(s->sws, "sws_flags", "unstable", 0);
+        av_opt_set(s->sws, "scaler",
+                   s->scaler == F_NEAREST ? "point" : "bilinear", 0);
     } else if (s->vkctx.output_format != s->vkctx.input_format) {
         if (!ff_vk_mt_is_np_rgb(s->vkctx.input_format)) {
             av_log(avctx, AV_LOG_ERROR, "Unsupported input format for conversion\n");
diff --git a/libavfilter/vulkan/Makefile b/libavfilter/vulkan/Makefile
index 2cfe9cfa93..8a0912b4df 100644
--- a/libavfilter/vulkan/Makefile
+++ b/libavfilter/vulkan/Makefile
@@ -8,7 +8,8 @@ OBJS-$(CONFIG_BWDIF_VULKAN_FILTER) += vulkan/bwdif.comp.spv.o
 OBJS-$(CONFIG_CHROMABER_VULKAN_FILTER) += vulkan/chromaber.comp.spv.o
 OBJS-$(CONFIG_COLOR_VULKAN_FILTER) += vulkan/color.comp.spv.o
 OBJS-$(CONFIG_GBLUR_VULKAN_FILTER) += vulkan/gblur.comp.spv.o
-OBJS-$(CONFIG_SCALE_VULKAN_FILTER) += vulkan/debayer.comp.spv.o
+OBJS-$(CONFIG_SCALE_VULKAN_FILTER) += vulkan/scale.comp.spv.o \
+                                      vulkan/debayer.comp.spv.o
 OBJS-$(CONFIG_SCDET_VULKAN_FILTER) += vulkan/scdet.comp.spv.o
 OBJS-$(CONFIG_OVERLAY_VULKAN_FILTER) += vulkan/overlay.comp.spv.o
 OBJS-$(CONFIG_FLIP_VULKAN_FILTER) += vulkan/flip.comp.spv.o
diff --git a/libavfilter/vulkan/debayer.comp.glsl b/libavfilter/vulkan/debayer.comp.glsl
index 0a4e22de99..90a1516717 100644
--- a/libavfilter/vulkan/debayer.comp.glsl
+++ b/libavfilter/vulkan/debayer.comp.glsl
@@ -36,6 +36,7 @@ layout(push_constant, std430) uniform pushConstants {
    int crop_y;
    int crop_w;
    int crop_h;
+   vec2 in_dims;
 };
 
 #define LD(xo, yo) \
diff --git a/libavfilter/vulkan/scale.comp.glsl b/libavfilter/vulkan/scale.comp.glsl
new file mode 100644
index 0000000000..b439344106
--- /dev/null
+++ b/libavfilter/vulkan/scale.comp.glsl
@@ -0,0 +1,120 @@
+/*
+ *
+ * Copyright (c) Lynne <[email protected]>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#pragma shader_stage(compute)
+
+#extension GL_EXT_shader_image_load_formatted : require
+#extension GL_EXT_scalar_block_layout : require
+#extension GL_EXT_nonuniform_qualifier : require
+
+layout (local_size_x_id = 253, local_size_y_id = 254, local_size_z_id = 255) in;
+
+#define MODE_COPY 0
+#define MODE_NV12 1
+#define MODE_YUV420 2
+#define MODE_YUV444 3
+
+layout (constant_id = 0) const int nb_planes = 0;
+layout (constant_id = 1) const int mode = MODE_COPY;
+layout (constant_id = 2) const int fullrange = 0;
+
+layout (set = 0, binding = 0) uniform sampler2D input_img[];
+layout (set = 0, binding = 1) uniform writeonly image2D output_img[];
+
+layout (push_constant, std430) uniform pushConstants {
+    mat4 yuv_matrix;
+    int crop_x;
+    int crop_y;
+    int crop_w;
+    int crop_h;
+    vec2 in_dims;
+};
+
+vec4 scale_bilinear(int idx, ivec2 pos, vec2 crop_range, vec2 crop_off)
+{
+    vec2 npos = (vec2(pos) + 0.5f) / imageSize(output_img[idx]);
+    npos *= crop_range;    /* Reduce the range */
+    npos += crop_off;      /* Offset the start */
+    return texture(input_img[idx], npos);
+}
+
+vec4 rgb2yuv(vec4 src)
+{
+    src *= yuv_matrix;
+    if (fullrange == 1) {
+        src += vec4(0.0, 0.5, 0.5, 0.0);
+    } else {
+        src *= vec4(219.0 / 255.0, 224.0 / 255.0, 224.0 / 255.0, 1.0);
+        src += vec4(16.0 / 255.0, 128.0 / 255.0, 128.0 / 255.0, 0.0);
+    }
+    return src;
+}
+
+void write_nv12(vec4 src, ivec2 pos)
+{
+    imageStore(output_img[0], pos, vec4(src.r, 0.0, 0.0, 0.0));
+    pos /= ivec2(2);
+    imageStore(output_img[1], pos, vec4(src.g, src.b, 0.0, 0.0));
+}
+
+void write_420(vec4 src, ivec2 pos)
+{
+    imageStore(output_img[0], pos, vec4(src.r, 0.0, 0.0, 0.0));
+    pos /= ivec2(2);
+    imageStore(output_img[1], pos, vec4(src.g, 0.0, 0.0, 0.0));
+    imageStore(output_img[2], pos, vec4(src.b, 0.0, 0.0, 0.0));
+}
+
+void write_444(vec4 src, ivec2 pos)
+{
+    imageStore(output_img[0], pos, vec4(src.r, 0.0, 0.0, 0.0));
+    imageStore(output_img[1], pos, vec4(src.g, 0.0, 0.0, 0.0));
+    imageStore(output_img[2], pos, vec4(src.b, 0.0, 0.0, 0.0));
+}
+
+void main()
+{
+    ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
+    vec2 c_r = vec2(crop_w, crop_h) / in_dims;
+    vec2 c_o = vec2(crop_x, crop_y) / in_dims;
+
+    if (mode == MODE_COPY) {
+        for (int i = 0; i < nb_planes; i++) {
+            ivec2 size = imageSize(output_img[i]);
+            if (any(greaterThanEqual(pos, size)))
+                continue;
+
+            vec4 res = scale_bilinear(i, pos, c_r, c_o);
+            imageStore(output_img[i], pos, res);
+        }
+    } else {
+        ivec2 size = imageSize(output_img[0]);
+        if (any(greaterThanEqual(pos, size)))
+            return;
+
+        vec4 res = rgb2yuv(scale_bilinear(0, pos, c_r, c_o));
+        switch (mode) {
+        case MODE_NV12:   write_nv12(res, pos); break;
+        case MODE_YUV420: write_420(res, pos);  break;
+        case MODE_YUV444: write_444(res, pos);  break;
+        }
+    }
+}
-- 
2.52.0


From 4c6017946bf889d80692f0290a8e6a693bbb64d4 Mon Sep 17 00:00:00 2001
From: Lynne <[email protected]>
Date: Sun, 12 Jul 2026 14:11:32 +0900
Subject: [PATCH 034/180] swscale: remove GLSL backend

No longer needed. The SPIR-V version is pretty clean and neat.

(cherry picked from commit aa6aa4ec3ef89fcaf1c7f73da4caa2116b3cbea0)
---
 doc/scaler.texi                    |   3 -
 libswscale/ops.c                   |   6 -
 libswscale/options.c               |   1 -
 libswscale/swscale.h               |   4 +-
 libswscale/vulkan/Makefile         |   2 -
 libswscale/vulkan/ops.c            | 375 +----------------------------
 libswscale/vulkan/ops.h            |   7 -
 libswscale/vulkan/vulkan_glslang.c |  19 --
 libswscale/vulkan/vulkan_shaderc.c |  19 --
 9 files changed, 6 insertions(+), 430 deletions(-)
 delete mode 100644 libswscale/vulkan/vulkan_glslang.c
 delete mode 100644 libswscale/vulkan/vulkan_shaderc.c

diff --git a/doc/scaler.texi b/doc/scaler.texi
index a66c8217f8..274e500837 100644
--- a/doc/scaler.texi
+++ b/doc/scaler.texi
@@ -227,9 +227,6 @@ AArch64 NEON kernels.
 @item spirv
 Vulkan SPIR-V backend.
 
-@item glsl
-Vulkan GLSL backend.
-
 @end table
 
 @end table
diff --git a/libswscale/ops.c b/libswscale/ops.c
index 7d7c22a006..fe1e3bfb9d 100644
--- a/libswscale/ops.c
+++ b/libswscale/ops.c
@@ -38,9 +38,6 @@ extern const SwsOpBackend backend_x86;
 #if HAVE_SPIRV_HEADERS_SPIRV_H || HAVE_SPIRV_UNIFIED1_SPIRV_H
 extern const SwsOpBackend backend_spirv;
 #endif
-#if CONFIG_LIBSHADERC || CONFIG_LIBGLSLANG
-extern const SwsOpBackend backend_glsl;
-#endif
 
 const SwsOpBackend * const ff_sws_op_backends[] = {
     &backend_murder,
@@ -52,9 +49,6 @@ const SwsOpBackend * const ff_sws_op_backends[] = {
     &backend_c,
 #if HAVE_SPIRV_HEADERS_SPIRV_H || HAVE_SPIRV_UNIFIED1_SPIRV_H
     &backend_spirv,
-#endif
-#if CONFIG_LIBSHADERC || CONFIG_LIBGLSLANG
-    &backend_glsl,
 #endif
     NULL
 };
diff --git a/libswscale/options.c b/libswscale/options.c
index 960c6b91dc..fe6c5a6eac 100644
--- a/libswscale/options.c
+++ b/libswscale/options.c
@@ -117,7 +117,6 @@ static const AVOption swscale_options[] = {
         { "x86",         "x86 SIMD kernels",              0,                 AV_OPT_TYPE_CONST,  { .i64  = SWS_BACKEND_X86      }, .flags = VE, .unit = "sws_backend" },
         { "aarch64",     "AArch64 NEON kernels",          0,                 AV_OPT_TYPE_CONST,  { .i64  = SWS_BACKEND_AARCH64  }, .flags = VE, .unit = "sws_backend" },
         { "spirv",       "Vulkan SPIR-V backend",         0,                 AV_OPT_TYPE_CONST,  { .i64  = SWS_BACKEND_SPIRV    }, .flags = VE, .unit = "sws_backend" },
-        { "glsl",        "Vulkan GLSL backend",           0,                 AV_OPT_TYPE_CONST,  { .i64  = SWS_BACKEND_GLSL     }, .flags = VE, .unit = "sws_backend" },
 
     { NULL }
 };
diff --git a/libswscale/swscale.h b/libswscale/swscale.h
index 9b53ebbdff..c913e76985 100644
--- a/libswscale/swscale.h
+++ b/libswscale/swscale.h
@@ -118,13 +118,11 @@ typedef enum SwsBackend {
     SWS_BACKEND_X86         = (1 << 3), ///< Chained x86 SIMD kernels
     SWS_BACKEND_AARCH64     = (1 << 4), ///< Chained AArch64 NEON kernels
     SWS_BACKEND_SPIRV       = (1 << 5), ///< Vulkan SPIR-V backend
-    SWS_BACKEND_GLSL        = (1 << 6), ///< Vulkan GLSL backend
     SWS_BACKEND_UNSTABLE    = SWS_BACKEND_C |
                               SWS_BACKEND_MEMCPY |
                               SWS_BACKEND_X86 |
                               SWS_BACKEND_AARCH64 |
-                              SWS_BACKEND_SPIRV |
-                              SWS_BACKEND_GLSL,
+                              SWS_BACKEND_SPIRV,
 
     SWS_BACKEND_ALL = SWS_BACKEND_STABLE | SWS_BACKEND_UNSTABLE,
     SWS_BACKEND_MAX_ENUM = 0x7FFFFFFF, ///< force size to 32 bits, not a valid backend
diff --git a/libswscale/vulkan/Makefile b/libswscale/vulkan/Makefile
index e899f35955..396cdeb936 100644
--- a/libswscale/vulkan/Makefile
+++ b/libswscale/vulkan/Makefile
@@ -2,5 +2,3 @@ clean::
 	$(RM) $(CLEANSUFFIXES:%=libswscale/vulkan/%)
 
 OBJS-$(CONFIG_VULKAN) += vulkan/ops.o vulkan/vulkan.o
-OBJS-$(CONFIG_LIBSHADERC) += vulkan/vulkan_shaderc.o
-OBJS-$(CONFIG_LIBGLSLANG) += vulkan/vulkan_glslang.o
diff --git a/libswscale/vulkan/ops.c b/libswscale/vulkan/ops.c
index 54f1c689eb..4206340273 100644
--- a/libswscale/vulkan/ops.c
+++ b/libswscale/vulkan/ops.c
@@ -35,10 +35,6 @@ static void ff_sws_vk_uninit(AVRefStructOpaque opaque, void *obj)
 {
     FFVulkanOpsCtx *s = obj;
 
-#if CONFIG_LIBSHADERC || CONFIG_LIBGLSLANG
-    if (s->spvc)
-        s->spvc->uninit(&s->spvc);
-#endif
     ff_vk_uninit(&s->vkctx);
 }
 
@@ -72,14 +68,6 @@ int ff_sws_vk_init(SwsContext *sws, AVBufferRef *dev_ref)
         return AVERROR(ENOTSUP);
     }
 
-#if CONFIG_LIBSHADERC || CONFIG_LIBGLSLANG
-    if (!s->spvc) {
-        s->spvc = ff_vk_spirv_init();
-        if (!s->spvc)
-            return AVERROR(ENOMEM);
-    }
-#endif
-
     return 0;
 }
 
@@ -1140,7 +1128,7 @@ static int add_ops_spirv(SwsContext *sws, VulkanPriv *p, FFVulkanOpsCtx *s,
 
         switch (op->op) {
         case SWS_OP_READ:
-            if (op->rw.frac || op->rw.filter.op) {
+            if (op->rw.frac) {
                 return AVERROR(ENOTSUP);
             } else if (op->rw.filter.op) {
                 av_assert0(op->rw.mode != SWS_RW_PALETTE);
@@ -1299,338 +1287,7 @@ static int add_ops_spirv(SwsContext *sws, VulkanPriv *p, FFVulkanOpsCtx *s,
 }
 #endif
 
-#if CONFIG_LIBSHADERC || CONFIG_LIBGLSLANG
-static void add_desc_read_write(FFVulkanDescriptorSetBinding *out_desc,
-                                enum FFVkShaderRepFormat *out_rep,
-                                const SwsOp *op)
-{
-    const char *img_type = op->type == SWS_PIXEL_F32 ? "rgba32f"  :
-                           op->type == SWS_PIXEL_U32 ? "rgba32ui" :
-                           op->type == SWS_PIXEL_U16 ? "rgba16ui" :
-                                                       "rgba8ui";
-
-    *out_desc = (FFVulkanDescriptorSetBinding) {
-        .name = op->op == SWS_OP_WRITE ? "dst_img" : "src_img",
-        .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
-        .mem_layout = img_type,
-        .mem_quali = op->op == SWS_OP_WRITE ? "writeonly" : "readonly",
-        .dimensions = 2,
-        .elems = 4,
-        .stages = VK_SHADER_STAGE_COMPUTE_BIT,
-    };
-
-    *out_rep = op->type == SWS_PIXEL_F32 ? FF_VK_REP_FLOAT : FF_VK_REP_UINT;
-}
-
-#define QSTR "(%"PRId64"/%"PRId64"%s)"
-#define QTYPE(Q) (Q).num, (Q).den, cur_type == SWS_PIXEL_F32 ? ".0f" : ""
-
-static void read_glsl(const SwsOpList *ops, const SwsOp *op, FFVulkanShader *shd,
-                      int idx, const char *type_name,
-                      const char *type_v, const char *type_s)
-{
-    const SwsFilterWeights *wd = op->rw.filter.kernel;
-    const int interlaced = ops->src.interlaced;
-    if (op->rw.filter.op) {
-        const char *axis    = op->rw.filter.op == SWS_OP_FILTER_H ? "pos.x" : "pos.y";
-        const char *coord_x = op->rw.filter.op == SWS_OP_FILTER_H ? "o + i" : "pos.x";
-        const char *coord_y;
-        if (op->rw.filter.op == SWS_OP_FILTER_H)
-            coord_y = interlaced ? "spos.y" : "pos.y";
-        else
-            coord_y = interlaced ? "((o + i) * 2 + int(params.field))" : "o + i";
-        GLSLC(1, tmp = vec4(0);                                               );
-        av_bprintf(&shd->src, "    int o = filter_o%i[%s];\n", idx, axis);
-        av_bprintf(&shd->src, "    for (int i = 0; i < %i; i++) {\n",
-                   wd->filter_size);
-        av_bprintf(&shd->src, "        float w = filter_w%i[%s][i];\n",
-                   idx, axis);
-        if (op->rw.mode == SWS_RW_PACKED) {
-            GLSLF(2, tmp += w * %s(imageLoad(src_img[%i], ivec2(%s, %s)));     ,
-                  type_v, ops->plane_src[0], coord_x, coord_y);
-        } else {
-            for (int i = 0; i < op->rw.elems; i++)
-                GLSLF(2,
-                      tmp.%c += w * %s(imageLoad(src_img[%i], ivec2(%s, %s))[0]); ,
-                      "xyzw"[i], type_s, ops->plane_src[i], coord_x, coord_y);
-        }
-        GLSLC(1, }                                                            );
-        GLSLC(1, f32 = tmp;                                                   );
-    } else {
-        const char *src_pos = interlaced ? "spos" : "pos";
-        if (op->rw.mode == SWS_RW_PACKED) {
-            GLSLF(1, %s = %s(imageLoad(src_img[%i], %s));                      ,
-                  type_name, type_v, ops->plane_src[0], src_pos);
-        } else {
-            for (int i = 0; i < op->rw.elems; i++)
-                GLSLF(1, %s.%c = %s(imageLoad(src_img[%i], %s)[0]);            ,
-                      type_name, "xyzw"[i], type_s, ops->plane_src[i], src_pos);
-        }
-    }
-}
-
-static int add_ops_glsl(SwsContext *sws, VulkanPriv *p, FFVulkanOpsCtx *s,
-                        const SwsOpList *ops, FFVulkanShader *shd)
-{
-    int err;
-    uint8_t *spv_data;
-    size_t spv_len;
-    void *spv_opaque = NULL;
-    const int interlaced = ops->src.interlaced || ops->dst.interlaced;
-
-    err = ff_vk_shader_init(&s->vkctx, shd, "sws_pass",
-                            VK_SHADER_STAGE_COMPUTE_BIT,
-                            NULL, 0, 32, 32, 1, 0);
-    if (err < 0)
-        return err;
-
-    p->interlaced = interlaced;
-    if (interlaced)
-        ff_vk_shader_add_push_const(shd, 0, sizeof(uint32_t),
-                                    VK_SHADER_STAGE_COMPUTE_BIT);
-
-    int nb_desc = 0;
-    FFVulkanDescriptorSetBinding buf_desc[8];
-
-    const SwsOp *read  = ff_sws_op_list_input(ops);
-    const SwsOp *write = ff_sws_op_list_output(ops);
-    if (read)
-        add_desc_read_write(&buf_desc[nb_desc++], &p->src_rep, read);
-    add_desc_read_write(&buf_desc[nb_desc++], &p->dst_rep, write);
-    ff_vk_shader_add_descriptor_set(&s->vkctx, shd, buf_desc, nb_desc, 0, 0);
-
-    err = create_bufs(s, p, ops);
-    if (err < 0)
-        return err;
-
-    nb_desc = 0;
-    char data_buf_name[MAX_DATA_BUFS][256];
-    char data_str_name[MAX_DATA_BUFS][256];
-    for (int n = 0; n < ops->num_ops; n++) {
-        const SwsOp *op = &ops->ops[n];
-        if (op->op == SWS_OP_DITHER) {
-            int size = (1 << op->dither.size_log2);
-            av_assert0(size < 8192);
-            snprintf(data_buf_name[nb_desc], 256, "dither_buf%i", n);
-            snprintf(data_str_name[nb_desc], 256, "float dither_mat%i[%i][%i];",
-                     n, size, size);
-            buf_desc[nb_desc] = (FFVulkanDescriptorSetBinding) {
-                .name        = data_buf_name[nb_desc],
-                .type        = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
-                .stages      = VK_SHADER_STAGE_COMPUTE_BIT,
-                .mem_layout  = "scalar",
-                .buf_content = data_str_name[nb_desc],
-            };
-            nb_desc++;
-        } else if (op->op == SWS_OP_FILTER_H || op->op == SWS_OP_FILTER_V ||
-                   ((op->op == SWS_OP_READ || op->op == SWS_OP_WRITE) &&
-                    op->rw.filter.op)) {
-            const SwsFilterWeights *wd = (op->op == SWS_OP_READ ||
-                                          op->op == SWS_OP_WRITE) ?
-                                         op->rw.filter.kernel : op->filter.kernel;
-            snprintf(data_buf_name[nb_desc], 256, "filter_buf%i", n);
-            snprintf(data_str_name[nb_desc], 256,
-                     "float filter_w%i[%i][%i];\n"
-                 "    int filter_o%i[%i];",
-                     n, wd->dst_size, wd->filter_size,
-                     n, wd->dst_size);
-            buf_desc[nb_desc] = (FFVulkanDescriptorSetBinding) {
-                .name        = data_buf_name[nb_desc],
-                .type        = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
-                .stages      = VK_SHADER_STAGE_COMPUTE_BIT,
-                .mem_layout  = "scalar",
-                .buf_content = data_str_name[nb_desc],
-            };
-            nb_desc++;
-        }
-    }
-    if (nb_desc)
-        ff_vk_shader_add_descriptor_set(&s->vkctx, shd, buf_desc,
-                                        nb_desc, 1, 0);
-
-    if (interlaced) {
-        GLSLC(0, layout(push_constant, std430) uniform pushConstants {        );
-        GLSLC(1,     uint field;                                              );
-        GLSLC(0, } params;                                                    );
-        GLSLC(0,                                                              );
-    }
-
-    GLSLC(0, void main()                                                      );
-    GLSLC(0, {                                                                );
-    GLSLC(1,     ivec2 pos = ivec2(gl_GlobalInvocationID.xy);                 );
-    GLSLC(1,     ivec2 size = imageSize(dst_img[0]);                          );
-    if (ops->src.interlaced)
-        GLSLC(1, ivec2 spos = ivec2(pos.x, pos.y * 2 + int(params.field));    );
-    if (ops->dst.interlaced)
-        GLSLC(1, ivec2 dpos = ivec2(pos.x, pos.y * 2 + int(params.field));    );
-    if (ops->dst.interlaced) {
-        GLSLC(1, if (any(greaterThanEqual(dpos, size)))                       );
-    } else {
-        GLSLC(1, if (any(greaterThanEqual(pos, size)))                        );
-    }
-    GLSLC(2,         return;                                                  );
-    GLSLC(0,                                                                  );
-    GLSLC(1,     u8vec4 u8;                                                   );
-    GLSLC(1,     u16vec4 u16;                                                 );
-    GLSLC(1,     u32vec4 u32;                                                 );
-    GLSLC(1,     precise f32vec4 f32;                                         );
-    GLSLC(1,     precise f32vec4 tmp;                                         );
-    GLSLC(0,                                                                  );
-
-    for (int n = 0; n < ops->num_ops; n++) {
-        const SwsOp *op = &ops->ops[n];
-        SwsPixelType cur_type = op->op == SWS_OP_CONVERT ? op->convert.to :
-                                op->type;
-        const char *type_name = ff_sws_pixel_type_name(cur_type);
-        const char *type_v = cur_type == SWS_PIXEL_F32 ? "f32vec4" :
-                             cur_type == SWS_PIXEL_U32 ? "u32vec4" :
-                             cur_type == SWS_PIXEL_U16 ? "u16vec4" : "u8vec4";
-        const char *type_s = cur_type == SWS_PIXEL_F32 ? "float" :
-                             cur_type == SWS_PIXEL_U32 ? "uint32_t" :
-                             cur_type == SWS_PIXEL_U16 ? "uint16_t" : "uint8_t";
-        av_bprintf(&shd->src, "    // %s\n", ff_sws_op_type_name(op->op));
-
-        switch (op->op) {
-        case SWS_OP_READ: {
-            if (op->rw.frac)
-                return AVERROR(ENOTSUP);
-            switch (op->rw.mode) {
-            case SWS_RW_PLANAR:
-            case SWS_RW_PACKED:
-                read_glsl(ops, op, shd, n, type_name, type_v, type_s);
-                break;
-            default:
-                return AVERROR(ENOTSUP);
-            }
-            break;
-        }
-        case SWS_OP_WRITE: {
-            const char *dst_pos = ops->dst.interlaced ? "dpos" : "pos";
-            if (op->rw.frac || op->rw.filter.op) {
-                return AVERROR(ENOTSUP);
-            } else if (op->rw.mode == SWS_RW_PACKED) {
-                GLSLF(1, imageStore(dst_img[%i], %s, %s(%s));                   ,
-                      ops->plane_dst[0], dst_pos, type_v, type_name);
-            } else {
-                for (int i = 0; i < op->rw.elems; i++)
-                    GLSLF(1, imageStore(dst_img[%i], %s, %s(%s[%i]));           ,
-                          ops->plane_dst[i], dst_pos, type_v, type_name, i);
-            }
-            break;
-        }
-        case SWS_OP_SWIZZLE: {
-            av_bprintf(&shd->src, "    %s = %s.", type_name, type_name);
-            for (int i = 0; i < 4; i++)
-                av_bprintf(&shd->src, "%c", "xyzw"[op->swizzle.in[i]]);
-            av_bprintf(&shd->src, ";\n");
-            break;
-        }
-        case SWS_OP_CLEAR: {
-            for (int i = 0; i < 4; i++) {
-                if (!SWS_COMP_TEST(op->clear.mask, i))
-                    continue;
-                av_bprintf(&shd->src, "    %s.%c = %s"QSTR";\n", type_name,
-                           "xyzw"[i], type_s, QTYPE(op->clear.value[i]));
-            }
-            break;
-        }
-        case SWS_OP_SCALE:
-            av_bprintf(&shd->src, "    %s = %s * "QSTR";\n",
-                       type_name, type_name, QTYPE(op->scale.factor));
-            break;
-        case SWS_OP_MIN:
-        case SWS_OP_MAX:
-            for (int i = 0; i < 4; i++) {
-                if (!op->clamp.limit[i].den)
-                    continue;
-                av_bprintf(&shd->src, "    %s.%c = %s(%s.%c, "QSTR");\n",
-                           type_name, "xyzw"[i],
-                           op->op == SWS_OP_MIN ? "min" : "max",
-                           type_name, "xyzw"[i], QTYPE(op->clamp.limit[i]));
-            }
-            break;
-        case SWS_OP_LSHIFT:
-        case SWS_OP_RSHIFT:
-            av_bprintf(&shd->src, "    %s %s= %i;\n", type_name,
-                       op->op == SWS_OP_LSHIFT ? "<<" : ">>", op->shift.amount);
-            break;
-        case SWS_OP_CONVERT:
-            if (ff_sws_pixel_type_is_int(cur_type) && op->convert.expand) {
-                const AVRational64 sc = ff_sws_pixel_expand(op->type, op->convert.to);
-                av_bprintf(&shd->src, "    %s = %s((%s*%"PRId64")/%"PRId64");\n",
-                           type_name, type_v, ff_sws_pixel_type_name(op->type),
-                           sc.num, sc.den);
-            } else {
-                av_bprintf(&shd->src, "    %s = %s(%s);\n",
-                           type_name, type_v, ff_sws_pixel_type_name(op->type));
-            }
-            break;
-        case SWS_OP_DITHER: {
-            int size = (1 << op->dither.size_log2);
-            for (int i = 0; i < 4; i++) {
-                if (op->dither.y_offset[i] < 0)
-                    continue;
-                av_bprintf(&shd->src, "    %s.%c += dither_mat%i[(pos.y + %i) & %i]"
-                                                                "[pos.x & %i];\n",
-                           type_name, "xyzw"[i], n,
-                           op->dither.y_offset[i], size - 1,
-                           size - 1);
-            }
-            break;
-        }
-        case SWS_OP_LINEAR:
-            for (int i = 0; i < 4; i++) {
-                if (op->lin.m[i][4].num)
-                    av_bprintf(&shd->src, "    tmp.%c = "QSTR";\n", "xyzw"[i],
-                               QTYPE(op->lin.m[i][4]));
-                else
-                    av_bprintf(&shd->src, "    tmp.%c = 0;\n", "xyzw"[i]);
-                for (int j = 0; j < 4; j++) {
-                    if (!op->lin.m[i][j].num)
-                        continue;
-                    av_bprintf(&shd->src, "    tmp.%c += f32.%c*"QSTR";\n",
-                               "xyzw"[i], "xyzw"[j], QTYPE(op->lin.m[i][j]));
-                }
-            }
-            av_bprintf(&shd->src, "    f32 = tmp;\n");
-            break;
-        case SWS_OP_UNPACK:
-            /* MSB->LSB indexing */
-            av_bprintf(&shd->src, "    %s = %s.%s;\n", type_name, type_name,
-                       ops->src.format == AV_PIX_FMT_X2BGR10 ? "wzyx" : "wxyz");
-            break;
-        case SWS_OP_PACK:
-            /* LSB->MSB indexing */
-            av_bprintf(&shd->src, "    %s = %s.%s;\n", type_name, type_name,
-                       ops->dst.format == AV_PIX_FMT_X2BGR10 ? "wzyx" : "yzwx");
-            break;
-        default:
-            return AVERROR(ENOTSUP);
-        }
-    }
-
-    GLSLC(0, }                                                                );
-
-    err = s->spvc->compile_shader(&s->vkctx, s->spvc, shd,
-                                  &spv_data, &spv_len, "main",
-                                  &spv_opaque);
-    if (err < 0)
-        return err;
-
-    err = ff_vk_shader_link(&s->vkctx, shd, spv_data, spv_len, "main");
-
-    if (spv_opaque)
-        s->spvc->free_shader(s->spvc, &spv_opaque);
-
-    if (err < 0)
-        return err;
-
-    return 0;
-}
-#endif
-
-static int compile(SwsContext *sws, const SwsOpList *ops, SwsCompiledOp *out,
-                   int glsl)
+static int compile(SwsContext *sws, const SwsOpList *ops, SwsCompiledOp *out)
 {
     int err;
     SwsInternal *c = sws_internal(sws);
@@ -1648,17 +1305,10 @@ static int compile(SwsContext *sws, const SwsOpList *ops, SwsCompiledOp *out,
     if (err < 0)
         goto fail;
 
-    if (glsl) {
-        err = AVERROR(ENOTSUP);
-#if CONFIG_LIBSHADERC || CONFIG_LIBGLSLANG
-        err = add_ops_glsl(sws, p, s, ops, &p->shd);
-#endif
-    } else {
-        err = AVERROR(ENOTSUP);
+    err = AVERROR(ENOTSUP);
 #if HAVE_SPIRV_HEADERS_SPIRV_H || HAVE_SPIRV_UNIFIED1_SPIRV_H
-        err = add_ops_spirv(sws, p, s, ops, &p->shd);
+    err = add_ops_spirv(sws, p, s, ops, &p->shd);
 #endif
-    }
     if (err < 0)
         goto fail;
 
@@ -1689,7 +1339,7 @@ fail:
 static int compile_spirv(SwsContext *sws, const SwsOpList *ops,
                          SwsCompiledOp *out)
 {
-    return compile(sws, ops, out, 0);
+    return compile(sws, ops, out);
 }
 
 const SwsOpBackend backend_spirv = {
@@ -1699,18 +1349,3 @@ const SwsOpBackend backend_spirv = {
     .hw_format = AV_PIX_FMT_VULKAN,
 };
 #endif
-
-#if CONFIG_LIBSHADERC || CONFIG_LIBGLSLANG
-static int compile_glsl(SwsContext *sws, const SwsOpList *ops,
-                        SwsCompiledOp *out)
-{
-    return compile(sws, ops, out, 1);
-}
-
-const SwsOpBackend backend_glsl = {
-    .name      = "glsl",
-    .flags     = SWS_BACKEND_GLSL,
-    .compile   = compile_glsl,
-    .hw_format = AV_PIX_FMT_VULKAN,
-};
-#endif
diff --git a/libswscale/vulkan/ops.h b/libswscale/vulkan/ops.h
index 6c9ae6c7fd..26596bee5f 100644
--- a/libswscale/vulkan/ops.h
+++ b/libswscale/vulkan/ops.h
@@ -24,16 +24,9 @@
 #include "libavutil/vulkan.h"
 #include "../swscale.h"
 
-#if CONFIG_LIBSHADERC || CONFIG_LIBGLSLANG
-#include "libavutil/vulkan_spirv.h"
-#endif
-
 typedef struct FFVulkanOpsCtx {
     FFVulkanContext vkctx;
     AVVulkanDeviceQueueFamily *qf;
-#if CONFIG_LIBSHADERC || CONFIG_LIBGLSLANG
-    FFVkSPIRVCompiler *spvc;
-#endif
 } FFVulkanOpsCtx;
 
 int ff_sws_vk_init(SwsContext *sws, AVBufferRef *dev_ref);
diff --git a/libswscale/vulkan/vulkan_glslang.c b/libswscale/vulkan/vulkan_glslang.c
deleted file mode 100644
index 9aa41567a3..0000000000
--- a/libswscale/vulkan/vulkan_glslang.c
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * This file is part of FFmpeg.
- *
- * FFmpeg is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * FFmpeg is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with FFmpeg; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
-#include "libavutil/vulkan_glslang.c"
diff --git a/libswscale/vulkan/vulkan_shaderc.c b/libswscale/vulkan/vulkan_shaderc.c
deleted file mode 100644
index 9f60bf4dfd..0000000000
--- a/libswscale/vulkan/vulkan_shaderc.c
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * This file is part of FFmpeg.
- *
- * FFmpeg is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * FFmpeg is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with FFmpeg; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
-#include "libavutil/vulkan_shaderc.c"
-- 
2.52.0


From ca089c35c4a368337fd132dd732f4a59c2fad137 Mon Sep 17 00:00:00 2001
From: Lynne <[email protected]>
Date: Sun, 12 Jul 2026 14:11:45 +0900
Subject: [PATCH 035/180] vulkan: remove runtime shader compilation support

No longer needed.

(cherry picked from commit 9e9ac0dcd93d0dfe31827237539cb82b4c7e4dc7)
---
 libavcodec/ffv1enc_vulkan.c          |  24 ++--
 libavcodec/proresenc_kostya_vulkan.c |  10 +-
 libavcodec/vulkan_apv.c              |   4 +-
 libavcodec/vulkan_dpx.c              |   2 +-
 libavcodec/vulkan_ffv1.c             |  12 +-
 libavcodec/vulkan_prores.c           |   4 +-
 libavcodec/vulkan_prores_raw.c       |   4 +-
 libavfilter/vf_avgblur_vulkan.c      |   2 +-
 libavfilter/vf_blackdetect_vulkan.c  |   2 +-
 libavfilter/vf_blend_vulkan.c        |   2 +-
 libavfilter/vf_bwdif_vulkan.c        |   2 +-
 libavfilter/vf_chromaber_vulkan.c    |   2 +-
 libavfilter/vf_flip_vulkan.c         |   2 +-
 libavfilter/vf_gblur_vulkan.c        |   6 +-
 libavfilter/vf_interlace_vulkan.c    |   2 +-
 libavfilter/vf_nlmeans_vulkan.c      |  12 +-
 libavfilter/vf_overlay_vulkan.c      |   2 +-
 libavfilter/vf_scale_vulkan.c        |   4 +-
 libavfilter/vf_scdet_vulkan.c        |   2 +-
 libavfilter/vf_transpose_vulkan.c    |   2 +-
 libavfilter/vf_v360_vulkan.c         |   2 +-
 libavfilter/vf_xfade_vulkan.c        |   2 +-
 libavfilter/vsrc_testsrc_vulkan.c    |   2 +-
 libavutil/vulkan.c                   | 181 +--------------------------
 libavutil/vulkan.h                   |  59 +--------
 libswscale/vulkan/ops.c              |   4 +-
 26 files changed, 63 insertions(+), 289 deletions(-)

diff --git a/libavcodec/ffv1enc_vulkan.c b/libavcodec/ffv1enc_vulkan.c
index fdbe63efa9..2259e841d4 100644
--- a/libavcodec/ffv1enc_vulkan.c
+++ b/libavcodec/ffv1enc_vulkan.c
@@ -945,7 +945,7 @@ static int init_rct_search_shader(AVCodecContext *avctx, VkSpecializationInfo *s
             .stages = VK_SHADER_STAGE_COMPUTE_BIT,
         },
     };
-    ff_vk_shader_add_descriptor_set(&fv->s, shd, desc_set_const, 1, 1, 0);
+    ff_vk_shader_add_descriptor_set(&fv->s, shd, desc_set_const, 1, 1);
 
     const FFVulkanDescriptorSetBinding desc_set[] = {
         { /* slice_data_buf */
@@ -958,7 +958,7 @@ static int init_rct_search_shader(AVCodecContext *avctx, VkSpecializationInfo *s
             .elems  = av_pix_fmt_count_planes(fv->s.frames->sw_format),
         },
     };
-    ff_vk_shader_add_descriptor_set(&fv->s, shd, desc_set, 2, 0, 0);
+    ff_vk_shader_add_descriptor_set(&fv->s, shd, desc_set, 2, 0);
 
     RET(ff_vk_shader_link(&fv->s, shd,
                           ff_ffv1_enc_rct_search_comp_spv_data,
@@ -989,7 +989,7 @@ static int init_sort32_shader(AVCodecContext *avctx, VkSpecializationInfo *sl)
             .stages = VK_SHADER_STAGE_COMPUTE_BIT,
         },
     };
-    ff_vk_shader_add_descriptor_set(&fv->s, shd, desc_set_const, 1, 1, 0);
+    ff_vk_shader_add_descriptor_set(&fv->s, shd, desc_set_const, 1, 1);
 
     const FFVulkanDescriptorSetBinding desc_set[] = {
         { /* slice_data_buf */
@@ -1006,7 +1006,7 @@ static int init_sort32_shader(AVCodecContext *avctx, VkSpecializationInfo *sl)
             .stages = VK_SHADER_STAGE_COMPUTE_BIT,
         },
     };
-    ff_vk_shader_add_descriptor_set(&fv->s, shd, desc_set, 3, 0, 0);
+    ff_vk_shader_add_descriptor_set(&fv->s, shd, desc_set, 3, 0);
 
     RET(ff_vk_shader_link(&fv->s, shd,
                           ff_ffv1_enc_sort32_comp_spv_data,
@@ -1036,7 +1036,7 @@ static int init_remap_shader(AVCodecContext *avctx, VkSpecializationInfo *sl)
             .stages = VK_SHADER_STAGE_COMPUTE_BIT,
         },
     };
-    ff_vk_shader_add_descriptor_set(&fv->s, shd, desc_set_const, 1, 1, 0);
+    ff_vk_shader_add_descriptor_set(&fv->s, shd, desc_set_const, 1, 1);
 
     const FFVulkanDescriptorSetBinding desc_set[] = {
         { /* slice_data_buf */
@@ -1053,7 +1053,7 @@ static int init_remap_shader(AVCodecContext *avctx, VkSpecializationInfo *sl)
             .stages = VK_SHADER_STAGE_COMPUTE_BIT,
         },
     };
-    ff_vk_shader_add_descriptor_set(&fv->s, shd, desc_set, 3, 0, 0);
+    ff_vk_shader_add_descriptor_set(&fv->s, shd, desc_set, 3, 0);
 
     RET(ff_vk_shader_link(&fv->s, shd,
                           ff_ffv1_enc_remap_comp_spv_data,
@@ -1083,7 +1083,7 @@ static int init_setup_shader(AVCodecContext *avctx, VkSpecializationInfo *sl)
             .stages = VK_SHADER_STAGE_COMPUTE_BIT,
         },
     };
-    ff_vk_shader_add_descriptor_set(&fv->s, shd, desc_set_const, 1, 1, 0);
+    ff_vk_shader_add_descriptor_set(&fv->s, shd, desc_set_const, 1, 1);
 
     const FFVulkanDescriptorSetBinding desc_set[] = {
         { /* slice_data_buf */
@@ -1095,7 +1095,7 @@ static int init_setup_shader(AVCodecContext *avctx, VkSpecializationInfo *sl)
             .stages = VK_SHADER_STAGE_COMPUTE_BIT,
         },
     };
-    ff_vk_shader_add_descriptor_set(&fv->s, shd, desc_set, 2, 0, 0);
+    ff_vk_shader_add_descriptor_set(&fv->s, shd, desc_set, 2, 0);
 
     RET(ff_vk_shader_link(&fv->s, shd,
                           ff_ffv1_enc_setup_comp_spv_data,
@@ -1127,7 +1127,7 @@ static int init_reset_shader(AVCodecContext *avctx, VkSpecializationInfo *sl)
             .stages = VK_SHADER_STAGE_COMPUTE_BIT,
         },
     };
-    ff_vk_shader_add_descriptor_set(&fv->s, shd, desc_set_const, 1, 1, 0);
+    ff_vk_shader_add_descriptor_set(&fv->s, shd, desc_set_const, 1, 1);
 
     const FFVulkanDescriptorSetBinding desc_set[] = {
         { /* slice_data_buf */
@@ -1139,7 +1139,7 @@ static int init_reset_shader(AVCodecContext *avctx, VkSpecializationInfo *sl)
             .stages = VK_SHADER_STAGE_COMPUTE_BIT,
         },
     };
-    ff_vk_shader_add_descriptor_set(&fv->s, shd, desc_set, 2, 0, 0);
+    ff_vk_shader_add_descriptor_set(&fv->s, shd, desc_set, 2, 0);
 
     if (fv->ctx.ac == AC_GOLOMB_RICE)
         RET(ff_vk_shader_link(&fv->s, shd,
@@ -1184,7 +1184,7 @@ static int init_encode_shader(AVCodecContext *avctx, VkSpecializationInfo *sl)
             .stages = VK_SHADER_STAGE_COMPUTE_BIT,
         },
     };
-    ff_vk_shader_add_descriptor_set(&fv->s, shd, desc_set_const, 3, 1, 0);
+    ff_vk_shader_add_descriptor_set(&fv->s, shd, desc_set_const, 3, 1);
 
     const FFVulkanDescriptorSetBinding desc_set[] = {
         { /* slice_data_buf */
@@ -1214,7 +1214,7 @@ static int init_encode_shader(AVCodecContext *avctx, VkSpecializationInfo *sl)
         },
     };
     ff_vk_shader_add_descriptor_set(&fv->s, shd, desc_set,
-                                    4 + fv->is_rgb + !!f->remap_mode, 0, 0);
+                                    4 + fv->is_rgb + !!f->remap_mode, 0);
 
     if (f->bayer) {
         if (fv->ctx.ac == AC_GOLOMB_RICE)
diff --git a/libavcodec/proresenc_kostya_vulkan.c b/libavcodec/proresenc_kostya_vulkan.c
index ab71394337..e95302f928 100644
--- a/libavcodec/proresenc_kostya_vulkan.c
+++ b/libavcodec/proresenc_kostya_vulkan.c
@@ -169,7 +169,7 @@ static int init_slice_data_pipeline(ProresVulkanContext *pv, FFVulkanShader *shd
             .elems      = av_pix_fmt_count_planes(vkctx->frames->sw_format),
         },
     };
-    RET(ff_vk_shader_add_descriptor_set(vkctx, shd, desc, 2, 0, 0));
+    ff_vk_shader_add_descriptor_set(vkctx, shd, desc, 2, 0);
 
     ff_vk_shader_add_push_const(shd, 0, sizeof(SliceDataInfo), VK_SHADER_STAGE_COMPUTE_BIT);
 
@@ -210,7 +210,7 @@ static int init_alpha_data_pipeline(ProresVulkanContext *pv, FFVulkanShader* shd
             .stages     = VK_SHADER_STAGE_COMPUTE_BIT,
         },
     };
-    RET(ff_vk_shader_add_descriptor_set(vkctx, shd, desc, 2, 0, 0));
+    ff_vk_shader_add_descriptor_set(vkctx, shd, desc, 2, 0);
 
     RET(ff_vk_shader_link(vkctx, shd,
                           ff_prores_ks_alpha_data_comp_spv_data,
@@ -260,7 +260,7 @@ static int init_estimate_slice_pipeline(ProresVulkanContext *pv, FFVulkanShader*
             .stages      = VK_SHADER_STAGE_COMPUTE_BIT,
         },
     };
-    RET(ff_vk_shader_add_descriptor_set(vkctx, shd, desc, 3, 0, 0));
+    ff_vk_shader_add_descriptor_set(vkctx, shd, desc, 3, 0);
 
     RET(ff_vk_shader_link(vkctx, shd,
                           ff_prores_ks_estimate_slice_comp_spv_data,
@@ -304,7 +304,7 @@ static int init_trellis_node_pipeline(ProresVulkanContext *pv, FFVulkanShader* s
             .stages      = VK_SHADER_STAGE_COMPUTE_BIT,
         },
     };
-    RET(ff_vk_shader_add_descriptor_set(vkctx, shd, desc, 2, 0, 0));
+    ff_vk_shader_add_descriptor_set(vkctx, shd, desc, 2, 0);
 
     RET(ff_vk_shader_link(vkctx, shd,
                           ff_prores_ks_trellis_node_comp_spv_data,
@@ -350,7 +350,7 @@ static int init_encode_slice_pipeline(ProresVulkanContext *pv, FFVulkanShader* s
             .stages      = VK_SHADER_STAGE_COMPUTE_BIT,
         },
     };
-    RET(ff_vk_shader_add_descriptor_set(vkctx, shd, desc, 3, 0, 0));
+    ff_vk_shader_add_descriptor_set(vkctx, shd, desc, 3, 0);
 
     ff_vk_shader_add_push_const(shd, 0, sizeof(EncodeSliceInfo), VK_SHADER_STAGE_COMPUTE_BIT);
 
diff --git a/libavcodec/vulkan_apv.c b/libavcodec/vulkan_apv.c
index 891b7b74c5..79164c13fc 100644
--- a/libavcodec/vulkan_apv.c
+++ b/libavcodec/vulkan_apv.c
@@ -357,7 +357,7 @@ static int init_decode_shader(AVCodecContext *avctx, FFVulkanContext *s,
             .stages      = VK_SHADER_STAGE_COMPUTE_BIT,
         }
     };
-    ff_vk_shader_add_descriptor_set(s, shd, desc_set, 2, 0, 0);
+    ff_vk_shader_add_descriptor_set(s, shd, desc_set, 2, 0);
 
     RET(ff_vk_shader_link(s, shd,
                           ff_apv_decode_comp_spv_data,
@@ -407,7 +407,7 @@ static int init_idct_shader(AVCodecContext *avctx, FFVulkanContext *s,
             .stages      = VK_SHADER_STAGE_COMPUTE_BIT,
         },
     };
-    ff_vk_shader_add_descriptor_set(s, shd, desc_set, 2, 0, 0);
+    ff_vk_shader_add_descriptor_set(s, shd, desc_set, 2, 0);
 
     RET(ff_vk_shader_link(s, shd,
                           ff_apv_idct_comp_spv_data,
diff --git a/libavcodec/vulkan_dpx.c b/libavcodec/vulkan_dpx.c
index 517c4e243d..06bb2f9d04 100644
--- a/libavcodec/vulkan_dpx.c
+++ b/libavcodec/vulkan_dpx.c
@@ -258,7 +258,7 @@ static int init_shader(AVCodecContext *avctx, FFVulkanContext *s,
             .stages = VK_SHADER_STAGE_COMPUTE_BIT,
         },
     };
-    ff_vk_shader_add_descriptor_set(s, shd, desc_set, 2 + (2*!unpack), 0, 0);
+    ff_vk_shader_add_descriptor_set(s, shd, desc_set, 2 + (2*!unpack), 0);
 
     const unsigned char *src = ff_dpx_copy_comp_spv_data;
     size_t src_len = ff_dpx_copy_comp_spv_len;
diff --git a/libavcodec/vulkan_ffv1.c b/libavcodec/vulkan_ffv1.c
index ce2e392233..a485ac3055 100644
--- a/libavcodec/vulkan_ffv1.c
+++ b/libavcodec/vulkan_ffv1.c
@@ -577,7 +577,7 @@ static int init_setup_shader(FFV1Context *f, FFVulkanContext *s,
             .stages = VK_SHADER_STAGE_COMPUTE_BIT,
         },
     };
-    ff_vk_shader_add_descriptor_set(s, shd, desc_set_const, 2, 1, 0);
+    ff_vk_shader_add_descriptor_set(s, shd, desc_set_const, 2, 1);
 
     const FFVulkanDescriptorSetBinding desc_set[] = {
         { /* slice_data_buf */
@@ -597,7 +597,7 @@ static int init_setup_shader(FFV1Context *f, FFVulkanContext *s,
             .stages = VK_SHADER_STAGE_COMPUTE_BIT,
         },
     };
-    ff_vk_shader_add_descriptor_set(s, shd, desc_set, 4, 0, 0);
+    ff_vk_shader_add_descriptor_set(s, shd, desc_set, 4, 0);
 
     RET(ff_vk_shader_link(s, shd,
                           ff_ffv1_dec_setup_comp_spv_data,
@@ -628,7 +628,7 @@ static int init_reset_shader(FFV1Context *f, FFVulkanContext *s,
             .stages = VK_SHADER_STAGE_COMPUTE_BIT,
         },
     };
-    ff_vk_shader_add_descriptor_set(s, shd, desc_set_const, 1, 1, 0);
+    ff_vk_shader_add_descriptor_set(s, shd, desc_set_const, 1, 1);
 
     const FFVulkanDescriptorSetBinding desc_set[] = {
         { /* slice_data_buf */
@@ -640,7 +640,7 @@ static int init_reset_shader(FFV1Context *f, FFVulkanContext *s,
             .stages = VK_SHADER_STAGE_COMPUTE_BIT,
         },
     };
-    ff_vk_shader_add_descriptor_set(s, shd, desc_set, 2, 0, 0);
+    ff_vk_shader_add_descriptor_set(s, shd, desc_set, 2, 0);
 
     if (ac == AC_GOLOMB_RICE)
         RET(ff_vk_shader_link(s, shd,
@@ -683,7 +683,7 @@ static int init_decode_shader(FFV1Context *f, FFVulkanContext *s,
             .stages = VK_SHADER_STAGE_COMPUTE_BIT,
         },
     };
-    ff_vk_shader_add_descriptor_set(s, shd, desc_set_const, 2, 1, 0);
+    ff_vk_shader_add_descriptor_set(s, shd, desc_set_const, 2, 1);
 
     const FFVulkanDescriptorSetBinding desc_set[] = {
         { /* slice_data_buf */
@@ -727,7 +727,7 @@ static int init_decode_shader(FFV1Context *f, FFVulkanContext *s,
     /* Bindings 5 (dst) and 6 (fltmap_buf) are conditional */
     ff_vk_shader_add_descriptor_set(s, shd, desc_set,
                                     5 + rgb + (is_float && !bayer),
-                                    0, 0);
+                                    0);
 
     if (bayer) {
         if (ac == AC_GOLOMB_RICE)
diff --git a/libavcodec/vulkan_prores.c b/libavcodec/vulkan_prores.c
index 69a9a080b9..9c5443aa63 100644
--- a/libavcodec/vulkan_prores.c
+++ b/libavcodec/vulkan_prores.c
@@ -388,7 +388,7 @@ static int init_decode_shader(AVCodecContext *avctx, FFVulkanContext *s,
             .elems  = av_pix_fmt_count_planes(dec_frames_ctx->sw_format),
         },
     };
-    ff_vk_shader_add_descriptor_set(s, shd, desc_set, 3, 0, 0);
+    ff_vk_shader_add_descriptor_set(s, shd, desc_set, 3, 0);
 
     RET(ff_vk_shader_link(s, shd,
                           ff_prores_vld_comp_spv_data,
@@ -444,7 +444,7 @@ static int init_idct_shader(AVCodecContext *avctx, FFVulkanContext *s,
             .elems  = av_pix_fmt_count_planes(dec_frames_ctx->sw_format),
         },
     };
-    RET(ff_vk_shader_add_descriptor_set(s, shd, desc_set, 3, 0, 0));
+    ff_vk_shader_add_descriptor_set(s, shd, desc_set, 3, 0);
 
     RET(ff_vk_shader_link(s, shd,
                           ff_prores_idct_comp_spv_data,
diff --git a/libavcodec/vulkan_prores_raw.c b/libavcodec/vulkan_prores_raw.c
index b6314ab693..8edd522a94 100644
--- a/libavcodec/vulkan_prores_raw.c
+++ b/libavcodec/vulkan_prores_raw.c
@@ -295,7 +295,9 @@ static int add_desc(AVCodecContext *avctx, FFVulkanContext *s,
         },
     };
 
-    return ff_vk_shader_add_descriptor_set(s, shd, desc_set, 2, 0, 0);
+    ff_vk_shader_add_descriptor_set(s, shd, desc_set, 2, 0);
+
+    return 0;
 }
 
 static int init_decode_shader(AVCodecContext *avctx, FFVulkanContext *s,
diff --git a/libavfilter/vf_avgblur_vulkan.c b/libavfilter/vf_avgblur_vulkan.c
index 3716f62ccd..da00e3579d 100644
--- a/libavfilter/vf_avgblur_vulkan.c
+++ b/libavfilter/vf_avgblur_vulkan.c
@@ -81,7 +81,7 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
             .elems  = planes,
         },
     };
-    ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc_set, 2, 0, 0);
+    ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc_set, 2, 0);
 
     RET(ff_vk_shader_link(vkctx, &s->shd,
                           ff_avgblur_comp_spv_data,
diff --git a/libavfilter/vf_blackdetect_vulkan.c b/libavfilter/vf_blackdetect_vulkan.c
index 3a8da6a7e9..298b298a18 100644
--- a/libavfilter/vf_blackdetect_vulkan.c
+++ b/libavfilter/vf_blackdetect_vulkan.c
@@ -101,7 +101,7 @@ static av_cold int init_filter(AVFilterContext *ctx)
             .stages      = VK_SHADER_STAGE_COMPUTE_BIT,
         }
     };
-    ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 2, 0, 0);
+    ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 2, 0);
 
     RET(ff_vk_shader_link(vkctx, &s->shd,
                           ff_blackdetect_comp_spv_data,
diff --git a/libavfilter/vf_blend_vulkan.c b/libavfilter/vf_blend_vulkan.c
index b0eeed0b95..525381c04f 100644
--- a/libavfilter/vf_blend_vulkan.c
+++ b/libavfilter/vf_blend_vulkan.c
@@ -131,7 +131,7 @@ static av_cold int init_filter(AVFilterContext *avctx)
             .elems  = planes,
         },
     };
-    ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 3, 0, 0);
+    ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 3, 0);
 
     RET(ff_vk_shader_link(vkctx, &s->shd,
                           ff_blend_comp_spv_data,
diff --git a/libavfilter/vf_bwdif_vulkan.c b/libavfilter/vf_bwdif_vulkan.c
index ea4154daf5..c8f6ad1fed 100644
--- a/libavfilter/vf_bwdif_vulkan.c
+++ b/libavfilter/vf_bwdif_vulkan.c
@@ -86,7 +86,7 @@ static av_cold int init_filter(AVFilterContext *ctx)
             .elems  = planes,
         },
     };
-    ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc_set, 4, 0, 0);
+    ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc_set, 4, 0);
 
     ff_vk_shader_add_push_const(&s->shd, 0, sizeof(BWDIFParameters),
                                 VK_SHADER_STAGE_COMPUTE_BIT);
diff --git a/libavfilter/vf_chromaber_vulkan.c b/libavfilter/vf_chromaber_vulkan.c
index fa0bc8e300..7a9bf77e09 100644
--- a/libavfilter/vf_chromaber_vulkan.c
+++ b/libavfilter/vf_chromaber_vulkan.c
@@ -84,7 +84,7 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
             .elems      = planes,
         },
     };
-    ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 2, 0, 0);
+    ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 2, 0);
 
     RET(ff_vk_shader_link(vkctx, shd,
                           ff_chromaber_comp_spv_data,
diff --git a/libavfilter/vf_flip_vulkan.c b/libavfilter/vf_flip_vulkan.c
index b86f236d2a..b44c12c49d 100644
--- a/libavfilter/vf_flip_vulkan.c
+++ b/libavfilter/vf_flip_vulkan.c
@@ -78,7 +78,7 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
             .elems  = planes,
         },
     };
-    ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 2, 0, 0);
+    ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 2, 0);
 
     RET(ff_vk_shader_link(vkctx, shd,
                           ff_flip_comp_spv_data,
diff --git a/libavfilter/vf_gblur_vulkan.c b/libavfilter/vf_gblur_vulkan.c
index 18d65df8c9..021c94f5a5 100644
--- a/libavfilter/vf_gblur_vulkan.c
+++ b/libavfilter/vf_gblur_vulkan.c
@@ -122,7 +122,7 @@ static int init_gblur_pipeline(GBlurVulkanContext *s,
         .type        = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
         .stages      = VK_SHADER_STAGE_COMPUTE_BIT,
     };
-    ff_vk_shader_add_descriptor_set(&s->vkctx, shd, &buf_desc, 1, 1, 0);
+    ff_vk_shader_add_descriptor_set(&s->vkctx, shd, &buf_desc, 1, 1);
 
     RET(ff_vk_shader_link(&s->vkctx, shd,
                           ff_gblur_comp_spv_data,
@@ -180,13 +180,13 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
     /* Horizontal */
     ff_vk_shader_load(&s->shd_hor, VK_SHADER_STAGE_COMPUTE_BIT, NULL,
                       (uint32_t []) { 32, 1, 1 }, 0);
-    ff_vk_shader_add_descriptor_set(vkctx, &s->shd_hor, desc, 2, 0, 0);
+    ff_vk_shader_add_descriptor_set(vkctx, &s->shd_hor, desc, 2, 0);
     RET(init_gblur_pipeline(s, &s->shd_hor, &s->params_hor, s->size, s->sigma));
 
     /* Vertical */
     ff_vk_shader_load(&s->shd_ver, VK_SHADER_STAGE_COMPUTE_BIT, NULL,
                       (uint32_t []) { 1, 32, 1 }, 0);
-    ff_vk_shader_add_descriptor_set(vkctx, &s->shd_ver, desc, 2, 0, 0);
+    ff_vk_shader_add_descriptor_set(vkctx, &s->shd_ver, desc, 2, 0);
     RET(init_gblur_pipeline(s, &s->shd_ver, &s->params_ver, s->sizeV, s->sigmaV));
 
     s->initialized = 1;
diff --git a/libavfilter/vf_interlace_vulkan.c b/libavfilter/vf_interlace_vulkan.c
index 6f393a7a08..3de16c51ed 100644
--- a/libavfilter/vf_interlace_vulkan.c
+++ b/libavfilter/vf_interlace_vulkan.c
@@ -88,7 +88,7 @@ static av_cold int init_filter(AVFilterContext *ctx)
             .elems  = planes,
         },
     };
-    ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 3, 0, 0);
+    ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 3, 0);
 
     RET(ff_vk_shader_link(vkctx, &s->shd,
                           ff_interlace_comp_spv_data,
diff --git a/libavfilter/vf_nlmeans_vulkan.c b/libavfilter/vf_nlmeans_vulkan.c
index 902c072669..2a41f6fd8d 100644
--- a/libavfilter/vf_nlmeans_vulkan.c
+++ b/libavfilter/vf_nlmeans_vulkan.c
@@ -122,7 +122,7 @@ static av_cold int init_integral_pipeline(FFVulkanContext *vkctx, FFVkExecPool *
             .elems  = planes,
         },
     };
-    ff_vk_shader_add_descriptor_set(vkctx, shd, desc_set_img, 1, 0, 0);
+    ff_vk_shader_add_descriptor_set(vkctx, shd, desc_set_img, 1, 0);
 
     const FFVulkanDescriptorSetBinding desc_set_xyoffsets[] = {
         { /* xyoffsets_buffer */
@@ -130,7 +130,7 @@ static av_cold int init_integral_pipeline(FFVulkanContext *vkctx, FFVkExecPool *
             .stages = VK_SHADER_STAGE_COMPUTE_BIT,
         },
     };
-    ff_vk_shader_add_descriptor_set(vkctx, shd, desc_set_xyoffsets, 1, 1, 0);
+    ff_vk_shader_add_descriptor_set(vkctx, shd, desc_set_xyoffsets, 1, 1);
 
     RET(ff_vk_shader_link(vkctx, shd,
                           ff_nlmeans_vertical_comp_spv_data,
@@ -185,7 +185,7 @@ static av_cold int init_weights_pipeline(FFVulkanContext *vkctx, FFVkExecPool *e
             .stages = VK_SHADER_STAGE_COMPUTE_BIT,
         },
     };
-    ff_vk_shader_add_descriptor_set(vkctx, shd, desc_set, 3, 0, 0);
+    ff_vk_shader_add_descriptor_set(vkctx, shd, desc_set, 3, 0);
 
     const FFVulkanDescriptorSetBinding desc_set_xyoffsets[] = {
         { /* xyoffsets_buffer */
@@ -193,7 +193,7 @@ static av_cold int init_weights_pipeline(FFVulkanContext *vkctx, FFVkExecPool *e
             .stages = VK_SHADER_STAGE_COMPUTE_BIT,
         },
     };
-    ff_vk_shader_add_descriptor_set(vkctx, shd, desc_set_xyoffsets, 1, 1, 0);
+    ff_vk_shader_add_descriptor_set(vkctx, shd, desc_set_xyoffsets, 1, 1);
 
     RET(ff_vk_shader_link(vkctx, shd,
                           ff_nlmeans_weights_comp_spv_data,
@@ -238,7 +238,7 @@ static av_cold int init_denoise_pipeline(FFVulkanContext *vkctx, FFVkExecPool *e
             .elems  = planes,
         },
     };
-    ff_vk_shader_add_descriptor_set(vkctx, shd, desc_set_img, 2, 0, 0);
+    ff_vk_shader_add_descriptor_set(vkctx, shd, desc_set_img, 2, 0);
 
     const FFVulkanDescriptorSetBinding desc_set_ws[] = {
         { /* weights_buffer */
@@ -250,7 +250,7 @@ static av_cold int init_denoise_pipeline(FFVulkanContext *vkctx, FFVkExecPool *e
             .stages = VK_SHADER_STAGE_COMPUTE_BIT,
         },
     };
-    ff_vk_shader_add_descriptor_set(vkctx, shd, desc_set_ws, 2, 0, 0);
+    ff_vk_shader_add_descriptor_set(vkctx, shd, desc_set_ws, 2, 0);
 
     RET(ff_vk_shader_link(vkctx, shd,
                           ff_nlmeans_denoise_comp_spv_data,
diff --git a/libavfilter/vf_overlay_vulkan.c b/libavfilter/vf_overlay_vulkan.c
index 67b372a6f1..561fd812bd 100644
--- a/libavfilter/vf_overlay_vulkan.c
+++ b/libavfilter/vf_overlay_vulkan.c
@@ -95,7 +95,7 @@ static av_cold int init_filter(AVFilterContext *ctx)
             .elems  = planes,
         },
     };
-    ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 3, 0, 0);
+    ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 3, 0);
 
     RET(ff_vk_shader_link(vkctx, shd,
                           ff_overlay_comp_spv_data,
diff --git a/libavfilter/vf_scale_vulkan.c b/libavfilter/vf_scale_vulkan.c
index 4e54d8806c..2cb5e15587 100644
--- a/libavfilter/vf_scale_vulkan.c
+++ b/libavfilter/vf_scale_vulkan.c
@@ -143,7 +143,7 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
         },
     };
 
-    ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 2, 0, 0);
+    ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 2, 0);
 
     ff_vk_shader_add_push_const(&s->shd, 0, sizeof(s->opts),
                                 VK_SHADER_STAGE_COMPUTE_BIT);
@@ -211,7 +211,7 @@ static av_cold int init_debayer(AVFilterContext *ctx, AVFrame *in)
             .stages     = VK_SHADER_STAGE_COMPUTE_BIT,
         },
     };
-    ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 2, 0, 0);
+    ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 2, 0);
 
     RET(ff_vk_shader_link(vkctx, shd,
                           ff_debayer_comp_spv_data,
diff --git a/libavfilter/vf_scdet_vulkan.c b/libavfilter/vf_scdet_vulkan.c
index 552904a2bb..97273cc750 100644
--- a/libavfilter/vf_scdet_vulkan.c
+++ b/libavfilter/vf_scdet_vulkan.c
@@ -94,7 +94,7 @@ static av_cold int init_filter(AVFilterContext *ctx)
             .stages      = VK_SHADER_STAGE_COMPUTE_BIT,
         }
     };
-    ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 3, 0, 0);
+    ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 3, 0);
 
     RET(ff_vk_shader_link(vkctx, &s->shd,
                           ff_scdet_comp_spv_data,
diff --git a/libavfilter/vf_transpose_vulkan.c b/libavfilter/vf_transpose_vulkan.c
index e193bb6829..2eef40d061 100644
--- a/libavfilter/vf_transpose_vulkan.c
+++ b/libavfilter/vf_transpose_vulkan.c
@@ -75,7 +75,7 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
             .elems  = planes,
         },
     };
-    ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 2, 0, 0);
+    ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 2, 0);
 
     RET(ff_vk_shader_link(vkctx, &s->shd,
                           ff_transpose_comp_spv_data,
diff --git a/libavfilter/vf_v360_vulkan.c b/libavfilter/vf_v360_vulkan.c
index 25b55276f4..a88fc6f0a6 100644
--- a/libavfilter/vf_v360_vulkan.c
+++ b/libavfilter/vf_v360_vulkan.c
@@ -145,7 +145,7 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
             .elems  = planes,
         },
     };
-    ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc_set, 2, 0, 0);
+    ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc_set, 2, 0);
 
     RET(ff_vk_shader_link(vkctx, &s->shd,
                           ff_v360_comp_spv_data,
diff --git a/libavfilter/vf_xfade_vulkan.c b/libavfilter/vf_xfade_vulkan.c
index 8dcf0bef5c..82868d6933 100644
--- a/libavfilter/vf_xfade_vulkan.c
+++ b/libavfilter/vf_xfade_vulkan.c
@@ -128,7 +128,7 @@ static av_cold int init_vulkan(AVFilterContext *avctx)
             .stages     = VK_SHADER_STAGE_COMPUTE_BIT,
         },
     };
-    ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 3, 0, 0);
+    ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 3, 0);
 
     ff_vk_shader_add_push_const(&s->shd, 0, sizeof(XFadeParameters),
                                 VK_SHADER_STAGE_COMPUTE_BIT);
diff --git a/libavfilter/vsrc_testsrc_vulkan.c b/libavfilter/vsrc_testsrc_vulkan.c
index 6475423f39..bad6c0372a 100644
--- a/libavfilter/vsrc_testsrc_vulkan.c
+++ b/libavfilter/vsrc_testsrc_vulkan.c
@@ -94,7 +94,7 @@ static av_cold int init_filter(AVFilterContext *ctx, enum TestSrcVulkanMode mode
             .elems      = planes,
         },
     };
-    ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc_set, 1, 0, 0);
+    ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc_set, 1, 0);
 
     if (mode == TESTSRC_COLOR) {
         double rgb2yuv[3][3];
diff --git a/libavutil/vulkan.c b/libavutil/vulkan.c
index 07580dda3b..3b2cc35bf9 100644
--- a/libavutil/vulkan.c
+++ b/libavutil/vulkan.c
@@ -2162,92 +2162,6 @@ int ff_vk_shader_load(FFVulkanShader *shd,
     return 0;
 }
 
-int ff_vk_shader_init(FFVulkanContext *s, FFVulkanShader *shd, const char *name,
-                      VkPipelineStageFlags stage,
-                      const char *extensions[], int nb_extensions,
-                      int lg_x, int lg_y, int lg_z,
-                      uint32_t required_subgroup_size)
-{
-    ff_vk_shader_load(shd, stage, NULL,
-                      (uint32_t []) { lg_x, lg_y, lg_z }, required_subgroup_size);
-
-    shd->name = name;
-    shd->precompiled = 0;
-    av_bprint_init(&shd->src, 0, AV_BPRINT_SIZE_UNLIMITED);
-
-    if (required_subgroup_size) {
-        shd->subgroup_info.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO;
-        shd->subgroup_info.requiredSubgroupSize = required_subgroup_size;
-    }
-
-    av_bprintf(&shd->src, "/* %s shader: %s */\n",
-               (stage == VK_SHADER_STAGE_TASK_BIT_EXT ||
-                stage == VK_SHADER_STAGE_MESH_BIT_EXT) ?
-               "Mesh" :
-               (shd->bind_point == VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR) ?
-               "Raytrace" :
-               (shd->bind_point == VK_PIPELINE_BIND_POINT_COMPUTE) ?
-               "Compute" : "Graphics",
-               name);
-    GLSLF(0, #version %i                                                  ,460);
-    GLSLC(0,                                                                  );
-
-    /* Common utilities */
-    GLSLC(0, #define IS_WITHIN(v1, v2) ((v1.x < v2.x) && (v1.y < v2.y))       );
-    GLSLC(0,                                                                  );
-    GLSLC(0, #extension GL_EXT_scalar_block_layout : require                  );
-    GLSLC(0, #extension GL_EXT_shader_explicit_arithmetic_types : require     );
-    GLSLC(0, #extension GL_EXT_control_flow_attributes : require              );
-    GLSLC(0, #extension GL_EXT_shader_image_load_formatted : require          );
-    if (s->extensions & FF_VK_EXT_EXPECT_ASSUME) {
-        GLSLC(0, #extension GL_EXT_expect_assume : require                    );
-    } else {
-        GLSLC(0, #define assumeEXT(x) (x)                                     );
-        GLSLC(0, #define expectEXT(x, c) (x)                                  );
-    }
-    if ((s->extensions & FF_VK_EXT_DEBUG_UTILS) &&
-        (s->extensions & FF_VK_EXT_RELAXED_EXTENDED_INSTR)) {
-        GLSLC(0, #extension GL_EXT_debug_printf : require                     );
-        GLSLC(0, #define DEBUG                                                );
-    }
-
-    if (stage == VK_SHADER_STAGE_TASK_BIT_EXT ||
-        stage == VK_SHADER_STAGE_MESH_BIT_EXT)
-        GLSLC(0, #extension GL_EXT_mesh_shader : require                      );
-
-    for (int i = 0; i < nb_extensions; i++)
-        GLSLF(0, #extension %s : %s                  ,extensions[i], "require");
-    GLSLC(0,                                                                  );
-
-    GLSLF(0, layout (local_size_x = %i, local_size_y = %i, local_size_z = %i) in;
-          , shd->lg_size[0], shd->lg_size[1], shd->lg_size[2]);
-    GLSLC(0,                                                                  );
-
-    return 0;
-}
-
-void ff_vk_shader_print(void *ctx, FFVulkanShader *shd, int prio)
-{
-    int line = 0;
-    const char *p = shd->src.str;
-    const char *start = p;
-    const size_t len = strlen(p);
-
-    AVBPrint buf;
-    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
-
-    for (int i = 0; i < len; i++) {
-        if (p[i] == '\n') {
-            av_bprintf(&buf, "%i\t", ++line);
-            av_bprint_append_data(&buf, start, &p[i] - start + 1);
-            start = &p[i + 1];
-        }
-    }
-
-    av_log(ctx, prio, "Shader %s: \n%s", shd->name, buf.str);
-    av_bprint_finalize(&buf, NULL);
-}
-
 static int init_pipeline_layout(FFVulkanContext *s, FFVulkanShader *shd)
 {
     VkResult ret;
@@ -2522,34 +2436,10 @@ end:
     return err;
 }
 
-static const struct descriptor_props {
-    size_t struct_size; /* Size of the opaque which updates the descriptor */
-    const char *type;
-    int is_uniform;
-    int mem_quali;      /* Can use a memory qualifier */
-    int dim_needed;     /* Must indicate dimension */
-    int buf_content;    /* Must indicate buffer contents */
-} descriptor_props[] = {
-    [VK_DESCRIPTOR_TYPE_SAMPLER]                = { sizeof(VkDescriptorImageInfo),  "sampler",       1, 0, 0, 0, },
-    [VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE]          = { sizeof(VkDescriptorImageInfo),  "texture",       1, 0, 1, 0, },
-    [VK_DESCRIPTOR_TYPE_STORAGE_IMAGE]          = { sizeof(VkDescriptorImageInfo),  "image",         1, 1, 1, 0, },
-    [VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT]       = { sizeof(VkDescriptorImageInfo),  "subpassInput",  1, 0, 0, 0, },
-    [VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER] = { sizeof(VkDescriptorImageInfo),  "sampler",       1, 0, 1, 0, },
-    [VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER]         = { sizeof(VkDescriptorBufferInfo),  NULL,           1, 0, 0, 1, },
-    [VK_DESCRIPTOR_TYPE_STORAGE_BUFFER]         = { sizeof(VkDescriptorBufferInfo), "buffer",        0, 1, 0, 1, },
-    [VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC] = { sizeof(VkDescriptorBufferInfo),  NULL,           1, 0, 0, 1, },
-    [VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC] = { sizeof(VkDescriptorBufferInfo), "buffer",        0, 1, 0, 1, },
-    [VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER]   = { sizeof(VkBufferView),           "samplerBuffer", 1, 0, 0, 0, },
-    [VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER]   = { sizeof(VkBufferView),           "imageBuffer",   1, 0, 0, 0, },
-};
-
-int ff_vk_shader_add_descriptor_set(FFVulkanContext *s, FFVulkanShader *shd,
-                                    const FFVulkanDescriptorSetBinding *desc, int nb,
-                                    int singular, int print_to_shader_only)
+void ff_vk_shader_add_descriptor_set(FFVulkanContext *s, FFVulkanShader *shd,
+                                     const FFVulkanDescriptorSetBinding *desc, int nb,
+                                     int singular)
 {
-    if (print_to_shader_only)
-        goto print;
-
     FFVulkanDescriptorSet *set = &shd->desc_set[shd->nb_descriptor_sets++];
     av_assert1(shd->nb_descriptor_sets < FF_VK_MAX_DESCRIPTOR_SETS);
     av_assert1(nb < FF_VK_MAX_DESCRIPTOR_BINDINGS);
@@ -2578,69 +2468,6 @@ int ff_vk_shader_add_descriptor_set(FFVulkanContext *s, FFVulkanShader *shd,
 
     set->singular = singular;
     set->nb_bindings = nb;
-
-    if (shd->precompiled)
-        return 0;
-
-print:
-    /* Write shader info */
-    for (int i = 0; i < nb; i++) {
-        const struct descriptor_props *prop = &descriptor_props[desc[i].type];
-        GLSLA("layout (set = %i, binding = %i", FFMAX(shd->nb_descriptor_sets - 1, 0), i);
-
-        if (desc[i].mem_layout &&
-            (desc[i].type != VK_DESCRIPTOR_TYPE_STORAGE_IMAGE))
-            GLSLA(", %s", desc[i].mem_layout);
-
-        GLSLA(")");
-
-        if (prop->is_uniform)
-            GLSLA(" uniform");
-
-        if (prop->mem_quali && desc[i].mem_quali)
-            GLSLA(" %s", desc[i].mem_quali);
-
-        if (prop->type) {
-            GLSLA(" ");
-            if (desc[i].type == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE) {
-                if (desc[i].mem_layout) {
-                    int len = strlen(desc[i].mem_layout);
-                    if (desc[i].mem_layout[len - 1] == 'i' &&
-                        desc[i].mem_layout[len - 2] == 'u') {
-                        GLSLA("u");
-                    } else if (desc[i].mem_layout[len - 1] == 'i') {
-                        GLSLA("i");
-                    }
-                }
-            }
-            GLSLA("%s", prop->type);
-        }
-
-        if (prop->dim_needed)
-            GLSLA("%iD", desc[i].dimensions);
-
-        GLSLA(" %s", desc[i].name);
-
-        if (prop->buf_content) {
-            GLSLA(" {\n    ");
-            if (desc[i].buf_elems) {
-                GLSLA("%s", desc[i].buf_content);
-                GLSLA("[%i];", desc[i].buf_elems);
-            } else {
-                GLSLA("%s", desc[i].buf_content);
-            }
-            GLSLA("\n}");
-        }
-
-        if (desc[i].elems > 0)
-            GLSLA("[%i]", desc[i].elems);
-
-        GLSLA(";");
-        GLSLA("\n");
-    }
-    GLSLA("\n");
-
-    return 0;
 }
 
 int ff_vk_shader_register_exec(FFVulkanContext *s, FFVkExecPool *pool,
@@ -2854,8 +2681,6 @@ void ff_vk_shader_free(FFVulkanContext *s, FFVulkanShader *shd)
 {
     FFVulkanFunctions *vk = &s->vkfn;
 
-    av_bprint_finalize(&shd->src, NULL);
-
 #if 0
     if (shd->shader.module)
         vk->DestroyShaderModule(s->hwctx->act_dev, shd->shader.module,
diff --git a/libavutil/vulkan.h b/libavutil/vulkan.h
index 1a2fcc74f1..b37d998d37 100644
--- a/libavutil/vulkan.h
+++ b/libavutil/vulkan.h
@@ -24,46 +24,12 @@
 #include <stdatomic.h>
 
 #include "pixdesc.h"
-#include "bprint.h"
 #include "hwcontext.h"
 #include "vulkan_functions.h"
 #include "hwcontext_vulkan.h"
 #include "avassert.h"
 #include "intreadwrite.h"
 
-/* GLSL management macros */
-#define INDENT(N) INDENT_##N
-#define INDENT_0
-#define INDENT_1 INDENT_0 "    "
-#define INDENT_2 INDENT_1 INDENT_1
-#define INDENT_3 INDENT_2 INDENT_1
-#define INDENT_4 INDENT_3 INDENT_1
-#define INDENT_5 INDENT_4 INDENT_1
-#define INDENT_6 INDENT_5 INDENT_1
-#define C(N, S)          INDENT(N) #S "\n"
-
-#define GLSLC(N, S)                     \
-    do {                                \
-        av_bprintf(&shd->src, C(N, S)); \
-    } while (0)
-
-#define GLSLA(...)                          \
-    do {                                    \
-        av_bprintf(&shd->src, __VA_ARGS__); \
-    } while (0)
-
-#define GLSLF(N, S, ...)                             \
-    do {                                             \
-        av_bprintf(&shd->src, C(N, S), __VA_ARGS__); \
-    } while (0)
-
-#define GLSLD(D)                                        \
-    do {                                                \
-        av_bprintf(&shd->src, "\n");                    \
-        av_bprint_append_data(&shd->src, D, strlen(D)); \
-        av_bprintf(&shd->src, "\n");                    \
-    } while (0)
-
 /* Helper, pretty much every Vulkan return value needs to be checked */
 #define RET(x)                                                                 \
     do {                                                                       \
@@ -230,9 +196,6 @@ typedef struct FFVulkanShader {
     int precompiled;
     VkSpecializationInfo *specialization_info;
 
-    /* Shader text */
-    AVBPrint src;
-
     /* Compute shader local group sizes */
     uint32_t lg_size[3];
 
@@ -636,16 +599,6 @@ int ff_vk_host_map_buffer(FFVulkanContext *s, AVBufferRef **dst,
 int ff_vk_init_sampler(FFVulkanContext *s, VkSampler *sampler,
                        int unnorm_coords, VkFilter filt);
 
-/**
- * Initialize a shader object, with a specific set of extensions, type+bind,
- * local group size, and subgroup requirements.
- */
-int ff_vk_shader_init(FFVulkanContext *s, FFVulkanShader *shd, const char *name,
-                      VkPipelineStageFlags stage,
-                      const char *extensions[], int nb_extensions,
-                      int lg_x, int lg_y, int lg_z,
-                      uint32_t required_subgroup_size);
-
 /**
  * Initialize a shader object.
  * If spec is non-null, it must have been created with SPEC_LIST_CREATE().
@@ -655,12 +608,6 @@ int ff_vk_shader_load(FFVulkanShader *shd,
                       VkPipelineStageFlags stage, VkSpecializationInfo *spec,
                       uint32_t wg_size[3], uint32_t required_subgroup_size);
 
-/**
- * Output the shader code as logging data, with a specific
- * priority.
- */
-void ff_vk_shader_print(void *ctx, FFVulkanShader *shd, int prio);
-
 /**
  * Link a shader into an executable.
  */
@@ -677,9 +624,9 @@ int ff_vk_shader_add_push_const(FFVulkanShader *shd, int offset, int size,
 /**
  * Add descriptor to a shader. Must be called before shader init.
  */
-int ff_vk_shader_add_descriptor_set(FFVulkanContext *s, FFVulkanShader *shd,
-                                    const FFVulkanDescriptorSetBinding *desc, int nb,
-                                    int singular, int print_to_shader_only);
+void ff_vk_shader_add_descriptor_set(FFVulkanContext *s, FFVulkanShader *shd,
+                                     const FFVulkanDescriptorSetBinding *desc, int nb,
+                                     int singular);
 
 /**
  * Register a shader with an exec pool.
diff --git a/libswscale/vulkan/ops.c b/libswscale/vulkan/ops.c
index 4206340273..8fc16274a3 100644
--- a/libswscale/vulkan/ops.c
+++ b/libswscale/vulkan/ops.c
@@ -947,7 +947,7 @@ static int add_ops_spirv(SwsContext *sws, VulkanPriv *p, FFVulkanOpsCtx *s,
             .elems = 4,
         },
     };
-    ff_vk_shader_add_descriptor_set(&s->vkctx, shd, desc_set, 2, 0, 0);
+    ff_vk_shader_add_descriptor_set(&s->vkctx, shd, desc_set, 2, 0);
 
     /* Create dither buffers */
     int err = create_bufs(s, p, ops);
@@ -1007,7 +1007,7 @@ static int add_ops_spirv(SwsContext *sws, VulkanPriv *p, FFVulkanOpsCtx *s,
     }
     if (nb_data_bufs)
         ff_vk_shader_add_descriptor_set(&s->vkctx, shd, desc_set,
-                                        nb_data_bufs, 1, 0);
+                                        nb_data_bufs, 1);
 
     if (id->interlaced) {
         id->push_const_struct_id = spi_get_id(spi);
-- 
2.52.0


From 062de1909580de3d5b4cfa2b17c15d5cedac4f68 Mon Sep 17 00:00:00 2001
From: Lynne <[email protected]>
Date: Sun, 12 Jul 2026 14:11:59 +0900
Subject: [PATCH 036/180] configure: remove libshaderc and libglslang support

All of it is now gone.

(cherry picked from commit 92f2e6374dfbe9bf1c8e4d879f8810deafff3710)
---
 .forgejo/workflows/test.yml  |   2 +-
 configure                    |  36 +---
 doc/filters.texi             |   3 +-
 libavfilter/Makefile         |   4 -
 libavfilter/vulkan_glslang.c |  19 ---
 libavfilter/vulkan_shaderc.c |  19 ---
 libavutil/Makefile           |   2 -
 libavutil/vulkan_glslang.c   | 307 -----------------------------------
 libavutil/vulkan_shaderc.c   | 144 ----------------
 libavutil/vulkan_spirv.h     |  44 -----
 10 files changed, 7 insertions(+), 573 deletions(-)
 delete mode 100644 libavfilter/vulkan_glslang.c
 delete mode 100644 libavfilter/vulkan_shaderc.c
 delete mode 100644 libavutil/vulkan_glslang.c
 delete mode 100644 libavutil/vulkan_shaderc.c
 delete mode 100644 libavutil/vulkan_spirv.h

diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml
index 35e1064712..6606b8e94b 100644
--- a/.forgejo/workflows/test.yml
+++ b/.forgejo/workflows/test.yml
@@ -89,7 +89,7 @@ jobs:
       - name: Configure
         run: |
           mkdir build && cd build
-          ../ffmpeg/configure --pkg-config-flags="--static" $FFBUILD_TARGET_FLAGS $FF_CONFIGURE \
+          ../ffmpeg/configure --pkg-config-flags="--static" $FFBUILD_TARGET_FLAGS ${FF_CONFIGURE/--enable-libshaderc/} \
               --cc="$CC" --cxx="$CXX" --ar="$AR" --ranlib="$RANLIB" --nm="$NM" \
               --extra-cflags="$FF_CFLAGS" --extra-cxxflags="$FF_CXXFLAGS" \
               --extra-libs="$FF_LIBS" --extra-ldflags="$FF_LDFLAGS" --extra-ldexeflags="$FF_LDEXEFLAGS" \
diff --git a/configure b/configure
index 785e23fb16..374bd5c151 100755
--- a/configure
+++ b/configure
@@ -237,7 +237,6 @@ External library support:
   --enable-libfreetype     enable libfreetype, needed for drawtext filter [no]
   --enable-libfribidi      enable libfribidi, improves drawtext filter [no]
   --enable-libharfbuzz     enable libharfbuzz, needed for drawtext filter [no]
-  --enable-libglslang      enable runtime GLSL->SPIRV compilation via libglslang [no]
   --enable-libgme          enable Game Music Emu via libgme [no]
   --enable-libgsm          enable GSM de/encoding via libgsm [no]
   --enable-libiec61883     enable iec61883 via libiec61883 [no]
@@ -274,7 +273,6 @@ External library support:
   --enable-librsvg         enable SVG rasterization via librsvg [no]
   --enable-librubberband   enable rubberband needed for rubberband filter [no]
   --enable-librtmp         enable RTMP[E] support via librtmp [no]
-  --enable-libshaderc      enable runtime GLSL->SPIRV compilation via libshaderc [no]
   --enable-libshine        enable fixed-point MP3 encoding via libshine [no]
   --enable-libsmbclient    enable Samba protocol via libsmbclient [no]
   --enable-libsnappy       enable Snappy compression, needed for hap encoding [no]
@@ -2091,7 +2089,6 @@ EXTERNAL_LIBRARY_LIST="
     libfreetype
     libfribidi
     libharfbuzz
-    libglslang
     libgme
     libgsm
     libiec61883
@@ -2123,7 +2120,6 @@ EXTERNAL_LIBRARY_LIST="
     librist
     librsvg
     librtmp
-    libshaderc
     libshine
     libsmbclient
     libsnappy
@@ -4305,7 +4301,7 @@ zoompan_filter_deps="swscale"
 zscale_filter_deps="libzimg const_nan"
 scale_vaapi_filter_deps="vaapi"
 scale_vt_filter_deps="videotoolbox VTPixelTransferSessionCreate"
-scale_vulkan_filter_deps="vulkan spirv_compiler spirv_library"
+scale_vulkan_filter_deps="vulkan spirv_compiler swscale"
 vpp_qsv_filter_deps="libmfx"
 vpp_qsv_filter_select="qsvvpp"
 xfade_opencl_filter_deps="opencl"
@@ -4363,14 +4359,14 @@ avcodec_suggest="libm stdatomic zlib"
 avdevice_deps="avformat avcodec avutil"
 avdevice_suggest="libm stdatomic"
 avfilter_deps="avutil"
-avfilter_suggest="libm stdatomic zlib spirv_library"
+avfilter_suggest="libm stdatomic zlib"
 avformat_deps="avcodec avutil"
 avformat_suggest="libm network zlib stdatomic"
 avutil_suggest="amf clock_gettime ffnvcodec gcrypt libm zlib libdrm libmfx opencl openssl user32 vaapi vulkan videotoolbox corefoundation corevideo coremedia bcrypt stdatomic"
 swresample_deps="avutil"
 swresample_suggest="libm libsoxr stdatomic"
 swscale_deps="avutil"
-swscale_suggest="libm stdatomic spirv_library"
+swscale_suggest="libm stdatomic"
 shader_compression_suggest="zlib"
 
 avcodec_extralibs="pthreads_extralibs iconv_extralibs dxva2_extralibs liblcevc_dec_extralibs lcms2_extralibs"
@@ -6987,15 +6983,6 @@ if test -n "$custom_allocator"; then
     add_extralibs "$custom_allocator_extralibs"
 fi
 
-# Unlike other feature flags or libraries, spirv_library is not defined
-# within any of our predefined categories of components.
-# It gets defined if either libshaderc or libglslang check succeeds.
-# As such, its in a state of neither being explicitly enabled, nor
-# explicitly disabled, but even in this state, being mentioned in
-# _deps results in it always passing.
-# Disable it explicitly to fix this.
-disable spirv_library
-
 check_func_headers malloc.h _aligned_malloc     && enable aligned_malloc
 check_func  ${malloc_prefix}memalign            && enable memalign
 check_func  ${malloc_prefix}posix_memalign      && enable posix_memalign
@@ -7229,10 +7216,6 @@ if ! disabled ffnvcodec; then
       check_pkg_config ffnvcodec "ffnvcodec >= 11.1.5.3 ffnvcodec < 12.0" "$ffnv_hdr_list" ""
 fi
 
-if enabled_all libglslang libshaderc; then
-    die "ERROR: libshaderc and libglslang are mutually exclusive, if in doubt, disable libglslang"
-fi
-
 check_cpp_condition winrt windows.h "!WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)"
 
 if ! disabled w32threads && ! enabled pthreads; then
@@ -7364,16 +7347,6 @@ enabled libfontconfig     && require_pkg_config libfontconfig fontconfig "fontco
 enabled libfreetype       && require_pkg_config libfreetype freetype2 "ft2build.h FT_FREETYPE_H" FT_Init_FreeType
 enabled libfribidi        && require_pkg_config libfribidi fribidi fribidi.h fribidi_version_info
 enabled libharfbuzz       && require_pkg_config libharfbuzz harfbuzz hb.h hb_buffer_create
-if enabled libglslang; then
-    spvremap="-lSPVRemapper"
-    require_headers "glslang/build_info.h" && { test_cpp_condition glslang/build_info.h "GLSLANG_VERSION_MAJOR >= 16" && spvremap="" ; }
-    check_lib spirv_library glslang/Include/glslang_c_interface.h glslang_initialize_process \
-                            -lglslang -lMachineIndependent -lGenericCodeGen \
-                            ${spvremap} -lSPIRV -lSPIRV-Tools-opt -lSPIRV-Tools -lstdc++ $libm_extralibs $pthreads_extralibs ||
-                        require spirv_library glslang/Include/glslang_c_interface.h glslang_initialize_process \
-                            -lglslang -lMachineIndependent -lOSDependent -lHLSL -lOGLCompiler -lGenericCodeGen \
-                            ${spvremap} -lSPIRV -lSPIRV-Tools-opt -lSPIRV-Tools -lstdc++ $libm_extralibs $pthreads_extralibs ;
-fi
 enabled libgme            && { check_pkg_config libgme libgme gme/gme.h gme_new_emu ||
                                require libgme gme/gme.h gme_new_emu -lgme -lstdc++; }
 enabled libgsm            && { for gsm_hdr in "gsm.h" "gsm/gsm.h"; do
@@ -7459,7 +7432,6 @@ enabled librist           && require_pkg_config librist "librist >= 0.2.7" libri
 enabled librsvg           && require_pkg_config librsvg librsvg-2.0 librsvg-2.0/librsvg/rsvg.h rsvg_handle_new_from_data
 enabled librtmp           && require_pkg_config librtmp librtmp librtmp/rtmp.h RTMP_Socket
 enabled librubberband     && require_pkg_config librubberband "rubberband >= 1.8.1" rubberband/rubberband-c.h rubberband_new -lstdc++ && append librubberband_extralibs "-lstdc++"
-enabled libshaderc        && require_pkg_config spirv_library "shaderc >= 2019.1" shaderc/shaderc.h shaderc_compiler_initialize
 enabled libshine          && require_pkg_config libshine shine shine/layer3.h shine_encode_buffer
 enabled libsmbclient      && { check_pkg_config libsmbclient smbclient libsmbclient.h smbc_init ||
                                require libsmbclient libsmbclient.h smbc_init -lsmbclient; }
@@ -7880,7 +7852,7 @@ if enabled vulkan; then
         probe_glslc $program && break
     done
 else
-    disable libglslang libshaderc spirv_library spirv_compiler
+    disable spirv_compiler
 fi
 
 if enabled vulkan; then
diff --git a/doc/filters.texi b/doc/filters.texi
index aa0059f9cc..3534c87c9b 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -29179,7 +29179,8 @@ Preserve landscape geometry (when @var{width} >= @var{height}).
 Below is a description of the currently available Vulkan video filters.
 
 To enable compilation of these filters you need to configure FFmpeg with
-@code{--enable-vulkan} and either @code{--enable-libglslang} or @code{--enable-libshaderc}.
+@code{--enable-vulkan}, and have a SPIR-V compiler (@code{glslc} or
+@code{glslangValidator}) available at build time.
 
 Running Vulkan filters requires you to initialize a hardware device and to pass that device to all filters in any filter graph.
 @table @option
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index cbae5f4ffd..ce40cf3e65 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -665,10 +665,6 @@ OBJS-$(CONFIG_AVSYNCTEST_FILTER)             += src_avsynctest.o
 OBJS-$(CONFIG_AMOVIE_FILTER)                 += src_movie.o
 OBJS-$(CONFIG_MOVIE_FILTER)                  += src_movie.o
 
-# vulkan libs
-OBJS-$(CONFIG_LIBGLSLANG)                    += vulkan_glslang.o vulkan.o
-OBJS-$(CONFIG_LIBSHADERC)                    += vulkan_shaderc.o vulkan.o
-
 # Objects duplicated from other libraries for shared builds
 SHLIBOBJS                                    += log2_tab.o
 
diff --git a/libavfilter/vulkan_glslang.c b/libavfilter/vulkan_glslang.c
deleted file mode 100644
index 9aa41567a3..0000000000
--- a/libavfilter/vulkan_glslang.c
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * This file is part of FFmpeg.
- *
- * FFmpeg is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * FFmpeg is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with FFmpeg; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
-#include "libavutil/vulkan_glslang.c"
diff --git a/libavfilter/vulkan_shaderc.c b/libavfilter/vulkan_shaderc.c
deleted file mode 100644
index 9f60bf4dfd..0000000000
--- a/libavfilter/vulkan_shaderc.c
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * This file is part of FFmpeg.
- *
- * FFmpeg is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * FFmpeg is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with FFmpeg; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
-#include "libavutil/vulkan_shaderc.c"
diff --git a/libavutil/Makefile b/libavutil/Makefile
index 2e8a5de551..394812260e 100644
--- a/libavutil/Makefile
+++ b/libavutil/Makefile
@@ -253,8 +253,6 @@ SKIPHEADERS-$(CONFIG_VDPAU)            += hwcontext_vdpau.h
 SKIPHEADERS-$(CONFIG_VULKAN)           += hwcontext_vulkan.h vulkan.h   \
                                           vulkan_functions.h            \
                                           vulkan_loader.h
-SKIPHEADERS-$(CONFIG_LIBSHADERC)       += vulkan_spirv.h
-SKIPHEADERS-$(CONFIG_LIBGLSLANG)       += vulkan_spirv.h
 SKIPHEADERS-$(CONFIG_SHADER_COMPRESSION) += zlib_utils.h
 
 TESTPROGS = adler32                                                     \
diff --git a/libavutil/vulkan_glslang.c b/libavutil/vulkan_glslang.c
deleted file mode 100644
index aebb52dd44..0000000000
--- a/libavutil/vulkan_glslang.c
+++ /dev/null
@@ -1,307 +0,0 @@
-/*
- * This file is part of FFmpeg.
- *
- * FFmpeg is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * FFmpeg is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with FFmpeg; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
-#include <glslang/build_info.h>
-#include <glslang/Include/glslang_c_interface.h>
-
-#include "vulkan_spirv.h"
-#include "libavutil/mem.h"
-#include "libavutil/avassert.h"
-#include "libavutil/thread.h"
-
-static AVMutex glslc_mutex = AV_MUTEX_INITIALIZER;
-static int glslc_refcount = 0;
-
-static const glslang_resource_t glslc_resource_limits = {
-    .max_lights = 32,
-    .max_clip_planes = 6,
-    .max_texture_units = 32,
-    .max_texture_coords = 32,
-    .max_vertex_attribs = 64,
-    .max_vertex_uniform_components = 4096,
-    .max_varying_floats = 64,
-    .max_vertex_texture_image_units = 32,
-    .max_combined_texture_image_units = 80,
-    .max_texture_image_units = 32,
-    .max_fragment_uniform_components = 4096,
-    .max_draw_buffers = 32,
-    .max_vertex_uniform_vectors = 128,
-    .max_varying_vectors = 8,
-    .max_fragment_uniform_vectors = 16,
-    .max_vertex_output_vectors = 16,
-    .max_fragment_input_vectors = 15,
-    .min_program_texel_offset = -8,
-    .max_program_texel_offset = 7,
-    .max_clip_distances = 8,
-    .max_compute_work_group_count_x = 65535,
-    .max_compute_work_group_count_y = 65535,
-    .max_compute_work_group_count_z = 65535,
-    .max_compute_work_group_size_x = 1024,
-    .max_compute_work_group_size_y = 1024,
-    .max_compute_work_group_size_z = 64,
-    .max_compute_uniform_components = 1024,
-    .max_compute_texture_image_units = 16,
-    .max_compute_image_uniforms = 8,
-    .max_compute_atomic_counters = 8,
-    .max_compute_atomic_counter_buffers = 1,
-    .max_varying_components = 60,
-    .max_vertex_output_components = 64,
-    .max_geometry_input_components = 64,
-    .max_geometry_output_components = 128,
-    .max_fragment_input_components = 128,
-    .max_image_units = 8,
-    .max_combined_image_units_and_fragment_outputs = 8,
-    .max_combined_shader_output_resources = 8,
-    .max_image_samples = 0,
-    .max_vertex_image_uniforms = 0,
-    .max_tess_control_image_uniforms = 0,
-    .max_tess_evaluation_image_uniforms = 0,
-    .max_geometry_image_uniforms = 0,
-    .max_fragment_image_uniforms = 8,
-    .max_combined_image_uniforms = 8,
-    .max_geometry_texture_image_units = 16,
-    .max_geometry_output_vertices = 256,
-    .max_geometry_total_output_components = 1024,
-    .max_geometry_uniform_components = 1024,
-    .max_geometry_varying_components = 64,
-    .max_tess_control_input_components = 128,
-    .max_tess_control_output_components = 128,
-    .max_tess_control_texture_image_units = 16,
-    .max_tess_control_uniform_components = 1024,
-    .max_tess_control_total_output_components = 4096,
-    .max_tess_evaluation_input_components = 128,
-    .max_tess_evaluation_output_components = 128,
-    .max_tess_evaluation_texture_image_units = 16,
-    .max_tess_evaluation_uniform_components = 1024,
-    .max_tess_patch_components = 120,
-    .max_patch_vertices = 32,
-    .max_tess_gen_level = 64,
-    .max_viewports = 16,
-    .max_vertex_atomic_counters = 0,
-    .max_tess_control_atomic_counters = 0,
-    .max_tess_evaluation_atomic_counters = 0,
-    .max_geometry_atomic_counters = 0,
-    .max_fragment_atomic_counters = 8,
-    .max_combined_atomic_counters = 8,
-    .max_atomic_counter_bindings = 1,
-    .max_vertex_atomic_counter_buffers = 0,
-    .max_tess_control_atomic_counter_buffers = 0,
-    .max_tess_evaluation_atomic_counter_buffers = 0,
-    .max_geometry_atomic_counter_buffers = 0,
-    .max_fragment_atomic_counter_buffers = 1,
-    .max_combined_atomic_counter_buffers = 1,
-    .max_atomic_counter_buffer_size = 16384,
-    .max_transform_feedback_buffers = 4,
-    .max_transform_feedback_interleaved_components = 64,
-    .max_cull_distances = 8,
-    .max_combined_clip_and_cull_distances = 8,
-    .max_samples = 4,
-    .max_mesh_output_vertices_nv = 256,
-    .max_mesh_output_primitives_nv = 512,
-    .max_mesh_work_group_size_x_nv = 32,
-    .max_mesh_work_group_size_y_nv = 1,
-    .max_mesh_work_group_size_z_nv = 1,
-    .max_task_work_group_size_x_nv = 32,
-    .max_task_work_group_size_y_nv = 1,
-    .max_task_work_group_size_z_nv = 1,
-    .max_mesh_view_count_nv = 4,
-    .maxDualSourceDrawBuffersEXT = 1,
-
-    .limits = {
-        .non_inductive_for_loops = 1,
-        .while_loops = 1,
-        .do_while_loops = 1,
-        .general_uniform_indexing = 1,
-        .general_attribute_matrix_vector_indexing = 1,
-        .general_varying_indexing = 1,
-        .general_sampler_indexing = 1,
-        .general_variable_indexing = 1,
-        .general_constant_matrix_vector_indexing = 1,
-    }
-};
-
-static int glslc_shader_compile(FFVulkanContext *s, FFVkSPIRVCompiler *ctx,
-                                FFVulkanShader *shd, uint8_t **data,
-                                size_t *size, const char *entrypoint,
-                                void **opaque)
-{
-    const char *messages;
-    glslang_shader_t *glslc_shader;
-    glslang_program_t *glslc_program;
-
-    static const glslang_stage_t glslc_stage[] = {
-        [VK_SHADER_STAGE_VERTEX_BIT]   = GLSLANG_STAGE_VERTEX,
-        [VK_SHADER_STAGE_FRAGMENT_BIT] = GLSLANG_STAGE_FRAGMENT,
-        [VK_SHADER_STAGE_COMPUTE_BIT]  = GLSLANG_STAGE_COMPUTE,
-#if ((GLSLANG_VERSION_MAJOR) > 12)
-        [VK_SHADER_STAGE_TASK_BIT_EXT] = GLSLANG_STAGE_TASK,
-        [VK_SHADER_STAGE_MESH_BIT_EXT] = GLSLANG_STAGE_MESH,
-        [VK_SHADER_STAGE_RAYGEN_BIT_KHR] = GLSLANG_STAGE_RAYGEN,
-        [VK_SHADER_STAGE_INTERSECTION_BIT_KHR] = GLSLANG_STAGE_INTERSECT,
-        [VK_SHADER_STAGE_ANY_HIT_BIT_KHR] = GLSLANG_STAGE_ANYHIT,
-        [VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR] = GLSLANG_STAGE_CLOSESTHIT,
-        [VK_SHADER_STAGE_MISS_BIT_KHR] = GLSLANG_STAGE_MISS,
-        [VK_SHADER_STAGE_CALLABLE_BIT_KHR] = GLSLANG_STAGE_CALLABLE,
-#endif
-    };
-
-    const glslang_input_t glslc_input = {
-        .language                          = GLSLANG_SOURCE_GLSL,
-        .stage                             = glslc_stage[shd->stage],
-        .client                            = GLSLANG_CLIENT_VULKAN,
-#if ((GLSLANG_VERSION_MAJOR) >= 12)
-        .client_version                    = GLSLANG_TARGET_VULKAN_1_3,
-        .target_language_version           = GLSLANG_TARGET_SPV_1_6,
-#else
-        .client_version                    = GLSLANG_TARGET_VULKAN_1_2,
-        .target_language_version           = GLSLANG_TARGET_SPV_1_5,
-#endif
-        .target_language                   = GLSLANG_TARGET_SPV,
-        .code                              = shd->src.str,
-        .default_version                   = 460,
-        .default_profile                   = GLSLANG_NO_PROFILE,
-        .force_default_version_and_profile = false,
-        .forward_compatible                = false,
-        .messages                          = GLSLANG_MSG_DEFAULT_BIT,
-        .resource                          = &glslc_resource_limits,
-    };
-
-#if ((GLSLANG_VERSION_MAJOR) >= 12)
-    glslang_spv_options_t glslc_opts = {
-        .generate_debug_info = !!(s->extensions & (FF_VK_EXT_DEBUG_UTILS | FF_VK_EXT_RELAXED_EXTENDED_INSTR)),
-        .emit_nonsemantic_shader_debug_info = !!(s->extensions & FF_VK_EXT_RELAXED_EXTENDED_INSTR),
-        .emit_nonsemantic_shader_debug_source = !!(s->extensions & FF_VK_EXT_RELAXED_EXTENDED_INSTR),
-        .disable_optimizer = !!(s->extensions & FF_VK_EXT_DEBUG_UTILS),
-        .strip_debug_info = !(s->extensions & (FF_VK_EXT_DEBUG_UTILS | FF_VK_EXT_RELAXED_EXTENDED_INSTR)),
-        .optimize_size = 0,
-        .disassemble = 0,
-        .validate = 1,
-        /* .compile_only = 0, */
-    };
-#endif
-
-    av_assert0(glslc_refcount);
-
-    *opaque = NULL;
-
-    if (!(glslc_shader = glslang_shader_create(&glslc_input)))
-        return AVERROR(ENOMEM);
-
-    if (!glslang_shader_preprocess(glslc_shader, &glslc_input)) {
-        ff_vk_shader_print(s, shd, AV_LOG_WARNING);
-        av_log(s, AV_LOG_ERROR, "Unable to preprocess shader: %s (%s)!\n",
-               glslang_shader_get_info_log(glslc_shader),
-               glslang_shader_get_info_debug_log(glslc_shader));
-        glslang_shader_delete(glslc_shader);
-        return AVERROR(EINVAL);
-    }
-
-    if (!glslang_shader_parse(glslc_shader, &glslc_input)) {
-        ff_vk_shader_print(s, shd, AV_LOG_WARNING);
-        av_log(s, AV_LOG_ERROR, "Unable to parse shader: %s (%s)!\n",
-               glslang_shader_get_info_log(glslc_shader),
-               glslang_shader_get_info_debug_log(glslc_shader));
-        glslang_shader_delete(glslc_shader);
-        return AVERROR(EINVAL);
-    }
-
-    if (!(glslc_program = glslang_program_create())) {
-        glslang_shader_delete(glslc_shader);
-        return AVERROR(EINVAL);
-    }
-
-    glslang_program_add_shader(glslc_program, glslc_shader);
-
-    if (!glslang_program_link(glslc_program, GLSLANG_MSG_SPV_RULES_BIT |
-                                             GLSLANG_MSG_VULKAN_RULES_BIT)) {
-        ff_vk_shader_print(s, shd, AV_LOG_WARNING);
-        av_log(s, AV_LOG_ERROR, "Unable to link shader: %s (%s)!\n",
-               glslang_program_get_info_log(glslc_program),
-               glslang_program_get_info_debug_log(glslc_program));
-        glslang_program_delete(glslc_program);
-        glslang_shader_delete(glslc_shader);
-        return AVERROR(EINVAL);
-    }
-
-#if ((GLSLANG_VERSION_MAJOR) >= 12)
-    glslang_program_SPIRV_generate_with_options(glslc_program, glslc_input.stage, &glslc_opts);
-#else
-    glslang_program_SPIRV_generate(glslc_program, glslc_input.stage);
-#endif
-
-    messages = glslang_program_SPIRV_get_messages(glslc_program);
-    if (messages) {
-        ff_vk_shader_print(s, shd, AV_LOG_WARNING);
-        av_log(s, AV_LOG_WARNING, "%s\n", messages);
-    } else {
-        ff_vk_shader_print(s, shd, AV_LOG_DEBUG);
-    }
-
-    glslang_shader_delete(glslc_shader);
-
-    *size = glslang_program_SPIRV_get_size(glslc_program) * sizeof(unsigned int);
-    *data = (void *)glslang_program_SPIRV_get_ptr(glslc_program);
-    *opaque = glslc_program;
-
-    return 0;
-}
-
-static void glslc_shader_free(FFVkSPIRVCompiler *ctx, void **opaque)
-{
-    if (!opaque || !*opaque)
-        return;
-
-    av_assert0(glslc_refcount);
-    glslang_program_delete(*opaque);
-    *opaque = NULL;
-}
-
-static void glslc_uninit(FFVkSPIRVCompiler **ctx)
-{
-    if (!ctx || !*ctx)
-        return;
-
-    ff_mutex_lock(&glslc_mutex);
-    if (glslc_refcount && (--glslc_refcount == 0))
-        glslang_finalize_process();
-    ff_mutex_unlock(&glslc_mutex);
-
-    av_freep(ctx);
-}
-
-FFVkSPIRVCompiler *ff_vk_glslang_init(void)
-{
-    FFVkSPIRVCompiler *ret = av_mallocz(sizeof(*ret));
-    if (!ret)
-        return NULL;
-
-    ret->compile_shader = glslc_shader_compile;
-    ret->free_shader    = glslc_shader_free;
-    ret->uninit         = glslc_uninit;
-
-    ff_mutex_lock(&glslc_mutex);
-    if (!glslc_refcount++) {
-        if (!glslang_initialize_process()) {
-            av_freep(&ret);
-            glslc_refcount--;
-        }
-    }
-    ff_mutex_unlock(&glslc_mutex);
-
-    return ret;
-}
diff --git a/libavutil/vulkan_shaderc.c b/libavutil/vulkan_shaderc.c
deleted file mode 100644
index 3f7d52743c..0000000000
--- a/libavutil/vulkan_shaderc.c
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * This file is part of FFmpeg.
- *
- * FFmpeg is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * FFmpeg is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with FFmpeg; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
-#include <shaderc/shaderc.h>
-
-#include "libavutil/mem.h"
-#include "vulkan_spirv.h"
-
-static int shdc_shader_compile(FFVulkanContext *s, FFVkSPIRVCompiler *ctx,
-                               FFVulkanShader *shd, uint8_t **data,
-                               size_t *size, const char *entrypoint,
-                               void **opaque)
-{
-    int loglevel, err, warn, ret;
-    const char *status, *message;
-    shaderc_compilation_result_t res;
-    static const char *shdc_result[] = {
-        [shaderc_compilation_status_success]            = "success",
-        [shaderc_compilation_status_invalid_stage]      = "invalid stage",
-        [shaderc_compilation_status_compilation_error]  = "error",
-        [shaderc_compilation_status_internal_error]     = "internal error",
-        [shaderc_compilation_status_null_result_object] = "no result",
-        [shaderc_compilation_status_invalid_assembly]   = "invalid assembly",
-    };
-    static const shaderc_shader_kind shdc_kind[] = {
-        [VK_SHADER_STAGE_VERTEX_BIT]   = shaderc_glsl_vertex_shader,
-        [VK_SHADER_STAGE_FRAGMENT_BIT] = shaderc_glsl_fragment_shader,
-        [VK_SHADER_STAGE_COMPUTE_BIT]  = shaderc_glsl_compute_shader,
-        [VK_SHADER_STAGE_MESH_BIT_EXT] = shaderc_mesh_shader,
-        [VK_SHADER_STAGE_TASK_BIT_EXT] = shaderc_task_shader,
-        [VK_SHADER_STAGE_RAYGEN_BIT_KHR] = shaderc_raygen_shader,
-        [VK_SHADER_STAGE_ANY_HIT_BIT_KHR] = shaderc_anyhit_shader,
-        [VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR] = shaderc_closesthit_shader,
-        [VK_SHADER_STAGE_MISS_BIT_KHR] = shaderc_miss_shader,
-        [VK_SHADER_STAGE_INTERSECTION_BIT_KHR] = shaderc_intersection_shader,
-        [VK_SHADER_STAGE_CALLABLE_BIT_KHR] = shaderc_callable_shader,
-    };
-
-    shaderc_compile_options_t opts = shaderc_compile_options_initialize();
-    *opaque = NULL;
-    if (!opts)
-        return AVERROR(ENOMEM);
-
-    shaderc_compile_options_set_target_env(opts, shaderc_target_env_vulkan,
-                                           shaderc_env_version_vulkan_1_3);
-    shaderc_compile_options_set_target_spirv(opts, shaderc_spirv_version_1_6);
-
-    /* If either extension is set, turn on debug info */
-    if (s->extensions & (FF_VK_EXT_DEBUG_UTILS | FF_VK_EXT_RELAXED_EXTENDED_INSTR))
-        shaderc_compile_options_set_generate_debug_info(opts);
-
-    if (s->extensions & FF_VK_EXT_DEBUG_UTILS)
-        shaderc_compile_options_set_optimization_level(opts,
-                                                       shaderc_optimization_level_zero);
-    else
-        shaderc_compile_options_set_optimization_level(opts,
-                                                       shaderc_optimization_level_performance);
-
-    res = shaderc_compile_into_spv((shaderc_compiler_t)ctx->priv,
-                                   shd->src.str, strlen(shd->src.str),
-                                   shdc_kind[shd->stage],
-                                   shd->name, entrypoint, opts);
-    shaderc_compile_options_release(opts);
-
-    ret = shaderc_result_get_compilation_status(res);
-    err = shaderc_result_get_num_errors(res);
-    warn = shaderc_result_get_num_warnings(res);
-    message = shaderc_result_get_error_message(res);
-
-    if (ret != shaderc_compilation_status_success && !err)
-        err = 1;
-
-    loglevel = err ? AV_LOG_ERROR : warn ? AV_LOG_WARNING : AV_LOG_DEBUG;
-
-    ff_vk_shader_print(s, shd, loglevel);
-    if (message && (err || warn))
-        av_log(s, loglevel, "%s\n", message);
-    status = ret < FF_ARRAY_ELEMS(shdc_result) ? shdc_result[ret] : "unknown";
-    av_log(s, loglevel, "shaderc compile status '%s' (%d errors, %d warnings)\n",
-           status, err, warn);
-
-    if (err > 0)
-        return AVERROR(EINVAL);
-
-    *data = (uint8_t *)shaderc_result_get_bytes(res);
-    *size = shaderc_result_get_length(res);
-    *opaque = res;
-
-    return 0;
-}
-
-static void shdc_shader_free(FFVkSPIRVCompiler *ctx, void **opaque)
-{
-    if (!opaque || !*opaque)
-        return;
-
-    shaderc_result_release((shaderc_compilation_result_t)*opaque);
-    *opaque = NULL;
-}
-
-static void shdc_uninit(FFVkSPIRVCompiler **ctx)
-{
-    FFVkSPIRVCompiler *s;
-
-    if (!ctx || !*ctx)
-        return;
-
-    s = *ctx;
-
-    shaderc_compiler_release((shaderc_compiler_t)s->priv);
-    av_freep(ctx);
-}
-
-FFVkSPIRVCompiler *ff_vk_shaderc_init(void)
-{
-    FFVkSPIRVCompiler *ret = av_mallocz(sizeof(*ret));
-    if (!ret)
-        return NULL;
-
-    ret->compile_shader = shdc_shader_compile;
-    ret->free_shader    = shdc_shader_free;
-    ret->uninit         = shdc_uninit;
-
-    ret->priv           = (void *)shaderc_compiler_initialize();
-    if (!ret->priv)
-        av_freep(&ret);
-
-    return ret;
-}
diff --git a/libavutil/vulkan_spirv.h b/libavutil/vulkan_spirv.h
deleted file mode 100644
index c13b50f8e7..0000000000
--- a/libavutil/vulkan_spirv.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * This file is part of FFmpeg.
- *
- * FFmpeg is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * FFmpeg is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with FFmpeg; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
-#ifndef AVUTIL_VULKAN_SPIRV_H
-#define AVUTIL_VULKAN_SPIRV_H
-
-#include "vulkan.h"
-
-#include "config.h"
-
-typedef struct FFVkSPIRVCompiler {
-    void *priv;
-    int (*compile_shader)(FFVulkanContext *s, struct FFVkSPIRVCompiler *ctx,
-                          FFVulkanShader *shd, uint8_t **data,
-                          size_t *size, const char *entrypoint, void **opaque);
-    void (*free_shader)(struct FFVkSPIRVCompiler *ctx, void **opaque);
-    void (*uninit)(struct FFVkSPIRVCompiler **ctx);
-} FFVkSPIRVCompiler;
-
-#if CONFIG_LIBGLSLANG
-FFVkSPIRVCompiler *ff_vk_glslang_init(void);
-#define ff_vk_spirv_init ff_vk_glslang_init
-#endif
-#if CONFIG_LIBSHADERC
-FFVkSPIRVCompiler *ff_vk_shaderc_init(void);
-#define ff_vk_spirv_init ff_vk_shaderc_init
-#endif
-
-#endif /* AVUTIL_VULKAN_H */
-- 
2.52.0


From d124bfeada90097f728679118d75d23bd4a27296 Mon Sep 17 00:00:00 2001
From: Lynne <[email protected]>
Date: Sun, 12 Jul 2026 14:43:16 +0900
Subject: [PATCH 037/180] vf_libplacebo: add missing avstring.h include

Otherwise compilation fails due to vulkan.h no longer including it.

(cherry picked from commit 90436de5e10ebeac98a3c5e1fb8b2f509e3f96e5)
---
 libavfilter/vf_libplacebo.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/libavfilter/vf_libplacebo.c b/libavfilter/vf_libplacebo.c
index a7b8a38f7b..4ee8c36d7d 100644
--- a/libavfilter/vf_libplacebo.c
+++ b/libavfilter/vf_libplacebo.c
@@ -19,6 +19,7 @@
 #include <math.h>
 
 #include "libavutil/avassert.h"
+#include "libavutil/avstring.h"
 #include "libavutil/eval.h"
 #include "libavutil/fifo.h"
 #include "libavutil/file.h"
-- 
2.52.0


From 60aca6ace74384709bd921da7a735ec1cb6b36a2 Mon Sep 17 00:00:00 2001
From: Timo Rothenpieler <[email protected]>
Date: Wed, 8 Jul 2026 15:19:33 +0200
Subject: [PATCH 038/180] avcodec/d3d12va_av1: check size of frame bitstream
 against available buffer size

(cherry picked from commit e1ef95c779a3757d21ab970c06aac3e9e1b71974)
---
 libavcodec/d3d12va_av1.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/libavcodec/d3d12va_av1.c b/libavcodec/d3d12va_av1.c
index fdc7195d12..a3ddf0495a 100644
--- a/libavcodec/d3d12va_av1.c
+++ b/libavcodec/d3d12va_av1.c
@@ -112,6 +112,7 @@ static int d3d12va_av1_decode_slice(AVCodecContext *avctx,
 
 static int update_input_arguments(AVCodecContext *avctx, D3D12_VIDEO_DECODE_INPUT_STREAM_ARGUMENTS *input_args, ID3D12Resource *buffer)
 {
+    D3D12VADecodeContext    *ctx          = D3D12VA_DECODE_CONTEXT(avctx);
     const AV1DecContext     *h            = avctx->priv_data;
     AV1DecodePictureContext *ctx_pic      = h->cur_frame.hwaccel_picture_private;
     void *mapped_data;
@@ -121,6 +122,11 @@ static int update_input_arguments(AVCodecContext *avctx, D3D12_VIDEO_DECODE_INPU
     args->Size  = sizeof(DXVA_Tile_AV1) * ctx_pic->tile_count;
     args->pData = ctx_pic->tiles;
 
+    if (ctx_pic->bitstream_size > ctx->bitstream_size) {
+        av_log(avctx, AV_LOG_ERROR, "Input frame bitstream size exceeds internal buffer!\n");
+        return AVERROR(EINVAL);
+    }
+
     input_args->CompressedBitstream = (D3D12_VIDEO_DECODE_COMPRESSED_BITSTREAM){
         .pBuffer = buffer,
         .Offset  = 0,
-- 
2.52.0


From a90fbb6bc9fcf1df87bfbfc8a7818fd945d5397e Mon Sep 17 00:00:00 2001
From: Timo Rothenpieler <[email protected]>
Date: Thu, 9 Jul 2026 00:29:01 +0200
Subject: [PATCH 039/180] avcodec/d3d12va_av1: check slice bitstream size
 against available buffer size

(cherry picked from commit 7309ad9fd78d875e60427daa52260834e27ef2ea)
---
 libavcodec/d3d12va_av1.c | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/libavcodec/d3d12va_av1.c b/libavcodec/d3d12va_av1.c
index a3ddf0495a..bcf28ed590 100644
--- a/libavcodec/d3d12va_av1.c
+++ b/libavcodec/d3d12va_av1.c
@@ -33,6 +33,7 @@
 typedef struct D3D12AV1DecodeContext {
     D3D12VADecodeContext ctx;
     uint8_t *bitstream_buffer;
+    size_t bitstream_size;
 } D3D12AV1DecodeContext;
 
 #define D3D12_AV1_DECODE_CONTEXT(avctx) ((D3D12AV1DecodeContext *)D3D12VA_DECODE_CONTEXT(avctx))
@@ -77,6 +78,7 @@ static int d3d12va_av1_decode_slice(AVCodecContext *avctx,
     const AV1DecContext     *h            = avctx->priv_data;
     const AV1RawFrameHeader *frame_header = h->raw_frame_header;
     AV1DecodePictureContext *ctx_pic      = h->cur_frame.hwaccel_picture_private;
+    D3D12AV1DecodeContext   *av1_ctx      = D3D12_AV1_DECODE_CONTEXT(avctx);
     int offset = 0;
     uint32_t tg_start, tg_end;
 
@@ -91,7 +93,11 @@ static int d3d12va_av1_decode_slice(AVCodecContext *avctx,
         ctx_pic->bitstream      = (uint8_t *)buffer;
         ctx_pic->bitstream_size = size;
     } else {
-        ctx_pic->bitstream = D3D12_AV1_DECODE_CONTEXT(avctx)->bitstream_buffer;
+        if (ctx_pic->bitstream_size + (uint64_t)size > av1_ctx->bitstream_size) {
+            av_log(avctx, AV_LOG_ERROR, "Slice bitstream size exceeds internal buffer!\n");
+            return AVERROR(EINVAL);
+        }
+        ctx_pic->bitstream = av1_ctx->bitstream_buffer;
         memcpy(ctx_pic->bitstream + ctx_pic->bitstream_size, buffer, size);
         tg_start = h->tg_start;
         tg_end   = h->tg_end;
@@ -180,7 +186,8 @@ static av_cold int d3d12va_av1_decode_init(AVCodecContext *avctx)
         return ret;
 
     if (!av1_ctx->bitstream_buffer) {
-        av1_ctx->bitstream_buffer = av_malloc(ff_d3d12va_get_suitable_max_bitstream_size(avctx));
+        av1_ctx->bitstream_size = ff_d3d12va_get_suitable_max_bitstream_size(avctx);
+        av1_ctx->bitstream_buffer = av_malloc(av1_ctx->bitstream_size);
         if (!av1_ctx->bitstream_buffer)
             return AVERROR(ENOMEM);
     }
@@ -194,6 +201,7 @@ static av_cold int d3d12va_av1_decode_uninit(AVCodecContext *avctx)
 
     if (ctx->bitstream_buffer)
         av_freep(&ctx->bitstream_buffer);
+    ctx->bitstream_size = 0;
 
     return ff_d3d12va_decode_uninit(avctx);
 }
-- 
2.52.0


From 3a44bddfb9ad1416cd7bb02e4977009cfc2dfde9 Mon Sep 17 00:00:00 2001
From: Timo Rothenpieler <[email protected]>
Date: Thu, 9 Jul 2026 00:38:37 +0200
Subject: [PATCH 040/180] avcodec/d3d12va_h264: check size of frame bitstream
 against available buffer size

(cherry picked from commit 19e81f9260e64c43604556cbd4079fadee4ac4f1)
---
 libavcodec/d3d12va_h264.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/libavcodec/d3d12va_h264.c b/libavcodec/d3d12va_h264.c
index dec9344aad..5087e480f8 100644
--- a/libavcodec/d3d12va_h264.c
+++ b/libavcodec/d3d12va_h264.c
@@ -105,6 +105,7 @@ static int d3d12va_h264_decode_slice(AVCodecContext *avctx, const uint8_t *buffe
 #define START_CODE_SIZE 3
 static int update_input_arguments(AVCodecContext *avctx, D3D12_VIDEO_DECODE_INPUT_STREAM_ARGUMENTS *input_args, ID3D12Resource *buffer)
 {
+    D3D12VADecodeContext     *ctx             = D3D12VA_DECODE_CONTEXT(avctx);
     const H264Context        *h               = avctx->priv_data;
     const H264Picture        *current_picture = h->cur_pic_ptr;
     H264DecodePictureContext *ctx_pic         = current_picture->hwaccel_picture_private;
@@ -113,6 +114,7 @@ static int update_input_arguments(AVCodecContext *avctx, D3D12_VIDEO_DECODE_INPU
     uint8_t *mapped_data, *mapped_ptr;
     DXVA_Slice_H264_Short *slice;
     D3D12_VIDEO_DECODE_FRAME_ARGUMENT *args;
+    UINT bitstream_size = ctx->bitstream_size;
 
     if (FAILED(ID3D12Resource_Map(buffer, 0, NULL, (void **)&mapped_data))) {
         av_log(avctx, AV_LOG_ERROR, "Failed to map D3D12 Buffer resource!\n");
@@ -127,14 +129,22 @@ static int update_input_arguments(AVCodecContext *avctx, D3D12_VIDEO_DECODE_INPU
         position = slice->BSNALunitDataLocation;
         size     = slice->SliceBytesInBuffer;
 
+        if (START_CODE_SIZE + (uint64_t)size > bitstream_size) {
+            av_log(avctx, AV_LOG_ERROR, "Input frame bitstream size exceeds internal buffer!\n");
+            ID3D12Resource_Unmap(buffer, 0, NULL);
+            return AVERROR(EINVAL);
+        }
+
         slice->SliceBytesInBuffer += START_CODE_SIZE;
         slice->BSNALunitDataLocation = mapped_ptr - mapped_data;
 
         *(uint32_t *)mapped_ptr = START_CODE;
         mapped_ptr += START_CODE_SIZE;
+        bitstream_size -= START_CODE_SIZE;
 
         memcpy(mapped_ptr, &ctx_pic->bitstream[position], size);
         mapped_ptr += size;
+        bitstream_size -= size;
     }
 
     ID3D12Resource_Unmap(buffer, 0, NULL);
-- 
2.52.0


From b19b023f2a4e7269c7761d653f96ba46bccb24ab Mon Sep 17 00:00:00 2001
From: Timo Rothenpieler <[email protected]>
Date: Thu, 9 Jul 2026 00:40:32 +0200
Subject: [PATCH 041/180] avcodec/d3d12va_hevc: check size of frame bitstream
 against available buffer size

(cherry picked from commit fea3f13a1cc5395b0ab6db95dd20e7cf6023c311)
---
 libavcodec/d3d12va_hevc.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/libavcodec/d3d12va_hevc.c b/libavcodec/d3d12va_hevc.c
index e72d49b7d9..1bf1faf9cb 100644
--- a/libavcodec/d3d12va_hevc.c
+++ b/libavcodec/d3d12va_hevc.c
@@ -101,6 +101,7 @@ static int d3d12va_hevc_decode_slice(AVCodecContext *avctx, const uint8_t *buffe
 #define START_CODE_SIZE 3
 static int update_input_arguments(AVCodecContext *avctx, D3D12_VIDEO_DECODE_INPUT_STREAM_ARGUMENTS *input_args, ID3D12Resource *buffer)
 {
+    D3D12VADecodeContext     *ctx             = D3D12VA_DECODE_CONTEXT(avctx);
     const HEVCContext        *h               = avctx->priv_data;
     const HEVCFrame          *current_picture = h->cur_frame;
     HEVCDecodePictureContext *ctx_pic         = current_picture->hwaccel_picture_private;
@@ -109,6 +110,7 @@ static int update_input_arguments(AVCodecContext *avctx, D3D12_VIDEO_DECODE_INPU
     uint8_t *mapped_data, *mapped_ptr;
     DXVA_Slice_HEVC_Short *slice;
     D3D12_VIDEO_DECODE_FRAME_ARGUMENT *args;
+    UINT bitstream_size = ctx->bitstream_size;
 
     if (FAILED(ID3D12Resource_Map(buffer, 0, NULL, (void **)&mapped_data))) {
         av_log(avctx, AV_LOG_ERROR, "Failed to map D3D12 Buffer resource!\n");
@@ -123,14 +125,22 @@ static int update_input_arguments(AVCodecContext *avctx, D3D12_VIDEO_DECODE_INPU
         position = slice->BSNALunitDataLocation;
         size = slice->SliceBytesInBuffer;
 
+        if (START_CODE_SIZE + (uint64_t)size > bitstream_size) {
+            av_log(avctx, AV_LOG_ERROR, "Input frame bitstream size exceeds internal buffer!\n");
+            ID3D12Resource_Unmap(buffer, 0, NULL);
+            return AVERROR(EINVAL);
+        }
+
         slice->SliceBytesInBuffer += START_CODE_SIZE;
         slice->BSNALunitDataLocation = mapped_ptr - mapped_data;
 
         *(uint32_t *)mapped_ptr = START_CODE;
         mapped_ptr += START_CODE_SIZE;
+        bitstream_size -= START_CODE_SIZE;
 
         memcpy(mapped_ptr, &ctx_pic->bitstream[position], size);
         mapped_ptr += size;
+        bitstream_size -= size;
     }
 
     ID3D12Resource_Unmap(buffer, 0, NULL);
-- 
2.52.0


From 865374b1abc444df702aee831ca404ebf9d71d13 Mon Sep 17 00:00:00 2001
From: Timo Rothenpieler <[email protected]>
Date: Thu, 9 Jul 2026 00:42:38 +0200
Subject: [PATCH 042/180] avcodec/d3d12va_mpeg2: check size of frame bitstream
 against available buffer size

(cherry picked from commit 26a9f9b3aeb054901b69518223af0d6f4f991fd9)
---
 libavcodec/d3d12va_mpeg2.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/libavcodec/d3d12va_mpeg2.c b/libavcodec/d3d12va_mpeg2.c
index 47e453dd5e..de9f0c71d1 100644
--- a/libavcodec/d3d12va_mpeg2.c
+++ b/libavcodec/d3d12va_mpeg2.c
@@ -90,6 +90,7 @@ static int d3d12va_mpeg2_decode_slice(AVCodecContext *avctx, const uint8_t *buff
 
 static int update_input_arguments(AVCodecContext *avctx, D3D12_VIDEO_DECODE_INPUT_STREAM_ARGUMENTS *input_args, ID3D12Resource *buffer)
 {
+    D3D12VADecodeContext      *ctx          = D3D12VA_DECODE_CONTEXT(avctx);
     const MpegEncContext      *s            = avctx->priv_data;
     D3D12DecodePictureContext *ctx_pic      = s->cur_pic.ptr->hwaccel_picture_private;
 
@@ -105,6 +106,11 @@ static int update_input_arguments(AVCodecContext *avctx, D3D12_VIDEO_DECODE_INPU
         .End = ctx_pic->bitstream_size,
     };
 
+    if (ctx_pic->bitstream_size > ctx->bitstream_size) {
+        av_log(avctx, AV_LOG_ERROR, "Input frame bitstream size exceeds internal buffer!\n");
+        return AVERROR(EINVAL);
+    }
+
     if (FAILED(ID3D12Resource_Map(buffer, 0, &range, &mapped_data))) {
         av_log(avctx, AV_LOG_ERROR, "Failed to map D3D12 Buffer resource!\n");
         return AVERROR(EINVAL);
-- 
2.52.0


From ae0feeac8d2f87370eaf09bd8e01ecffd01373de Mon Sep 17 00:00:00 2001
From: Timo Rothenpieler <[email protected]>
Date: Thu, 9 Jul 2026 00:50:20 +0200
Subject: [PATCH 043/180] avcodec/d3d12va_vc1: check size of frame bitstream
 against available buffer size

(cherry picked from commit 1307db3d3c95223b1d2164844e0542cddbb2a401)
---
 libavcodec/d3d12va_vc1.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/libavcodec/d3d12va_vc1.c b/libavcodec/d3d12va_vc1.c
index e64a8e0630..2386d2a29b 100644
--- a/libavcodec/d3d12va_vc1.c
+++ b/libavcodec/d3d12va_vc1.c
@@ -94,6 +94,7 @@ static int d3d12va_vc1_decode_slice(AVCodecContext *avctx, const uint8_t *buffer
 
 static int update_input_arguments(AVCodecContext *avctx, D3D12_VIDEO_DECODE_INPUT_STREAM_ARGUMENTS *input_args, ID3D12Resource *buffer)
 {
+    D3D12VADecodeContext      *ctx          = D3D12VA_DECODE_CONTEXT(avctx);
     const VC1Context *v                     = avctx->priv_data;
     const MpegEncContext      *s            = &v->s;
     D3D12DecodePictureContext *ctx_pic      = s->cur_pic.ptr->hwaccel_picture_private;
@@ -101,6 +102,7 @@ static int update_input_arguments(AVCodecContext *avctx, D3D12_VIDEO_DECODE_INPU
 
     const unsigned mb_count = s->mb_width * (s->mb_height >> v->field_mode);
     uint8_t *mapped_data, *mapped_ptr;
+    UINT bitstream_size = ctx->bitstream_size;
 
     static const uint8_t start_code[] = { 0, 0, 1, 0x0d };
 
@@ -115,6 +117,12 @@ static int update_input_arguments(AVCodecContext *avctx, D3D12_VIDEO_DECODE_INPU
         unsigned position     = slice->dwSliceDataLocation;
         unsigned size         = slice->dwSliceBitsInBuffer / 8;
 
+        if ((uint64_t)size + ((avctx->codec_id == AV_CODEC_ID_VC1) ? sizeof(start_code) : 0) > bitstream_size) {
+            av_log(avctx, AV_LOG_ERROR, "Input frame bitstream size exceeds internal buffer!\n");
+            ID3D12Resource_Unmap(buffer, 0, NULL);
+            return AVERROR(EINVAL);
+        }
+
         slice->dwSliceDataLocation = mapped_ptr - mapped_data;
         if (i < ctx_pic->slice_count - 1)
             slice->wNumberMBsInSlice = slice[1].wNumberMBsInSlice - slice[0].wNumberMBsInSlice;
@@ -129,11 +137,13 @@ static int update_input_arguments(AVCodecContext *avctx, D3D12_VIDEO_DECODE_INPU
                 mapped_ptr[3] = 0x0b;
 
             mapped_ptr += sizeof(start_code);
+            bitstream_size -= sizeof(start_code);
             slice->dwSliceBitsInBuffer += sizeof(start_code) * 8;
         }
 
         memcpy(mapped_ptr, &ctx_pic->bitstream[position], size);
         mapped_ptr += size;
+        bitstream_size -= size;
     }
 
     ID3D12Resource_Unmap(buffer, 0, NULL);
-- 
2.52.0


From 3abec89a4535fd564a179be8387ea0df6a4f4ab0 Mon Sep 17 00:00:00 2001
From: Timo Rothenpieler <[email protected]>
Date: Thu, 9 Jul 2026 00:50:32 +0200
Subject: [PATCH 044/180] avcodec/d3d12va_vp9: check size of frame bitstream
 against available buffer size

(cherry picked from commit a633df56a455ffb75cd97af784cb1f2d2ec2bca9)
---
 libavcodec/d3d12va_vp9.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/libavcodec/d3d12va_vp9.c b/libavcodec/d3d12va_vp9.c
index 6f1f933fdd..f224044279 100644
--- a/libavcodec/d3d12va_vp9.c
+++ b/libavcodec/d3d12va_vp9.c
@@ -88,12 +88,18 @@ static int d3d12va_vp9_decode_slice(AVCodecContext *avctx, const uint8_t *buffer
 
 static int update_input_arguments(AVCodecContext *avctx, D3D12_VIDEO_DECODE_INPUT_STREAM_ARGUMENTS *input_args, ID3D12Resource *buffer)
 {
+    D3D12VADecodeContext    *ctx     = D3D12VA_DECODE_CONTEXT(avctx);
     const VP9SharedContext  *h       = avctx->priv_data;
     VP9DecodePictureContext *ctx_pic = h->frames[CUR_FRAME].hwaccel_picture_private;
 
     void *mapped_data;
     D3D12_VIDEO_DECODE_FRAME_ARGUMENT *args;
 
+    if (ctx_pic->slice.SliceBytesInBuffer > ctx->bitstream_size) {
+        av_log(avctx, AV_LOG_ERROR, "Input frame bitstream size exceeds internal buffer!\n");
+        return AVERROR(EINVAL);
+    }
+
     if (FAILED(ID3D12Resource_Map(buffer, 0, NULL, &mapped_data))) {
         av_log(avctx, AV_LOG_ERROR, "Failed to map D3D12 Buffer resource!\n");
         return AVERROR(EINVAL);
-- 
2.52.0


From 2864ce5e28e5627aa03aad645e25deaa98c39889 Mon Sep 17 00:00:00 2001
From: Marco Reimann <[email protected]>
Date: Tue, 23 Jun 2026 04:14:59 +0200
Subject: [PATCH 045/180] avcodec/nellymoserdec: Check block count to avoid
 integer overflow

Fixes: out of array access
Fixes: nelly.avi / gen_nelly_overflow.py
Fixes: pgc86PfE7ZpA
Fixes: 0eea21294354 (Add avcodec_decode_audio4().)
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit f9482e1d01d7ae9395194f3c84b1d67dfb65c645)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/nellymoserdec.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/libavcodec/nellymoserdec.c b/libavcodec/nellymoserdec.c
index 36477173ff..7037d6b0ba 100644
--- a/libavcodec/nellymoserdec.c
+++ b/libavcodec/nellymoserdec.c
@@ -156,8 +156,8 @@ static int decode_tag(AVCodecContext *avctx, AVFrame *frame,
 
     blocks     = buf_size / NELLY_BLOCK_LEN;
 
-    if (blocks <= 0) {
-        av_log(avctx, AV_LOG_ERROR, "Packet is too small\n");
+    if (blocks <= 0 || blocks > INT_MAX / NELLY_SAMPLES) {
+        av_log(avctx, AV_LOG_ERROR, "Packet is too small or too large\n");
         return AVERROR_INVALIDDATA;
     }
 
-- 
2.52.0


From 8af6c71d96f42d277e645d4d9f34e2b39855e3f3 Mon Sep 17 00:00:00 2001
From: Jiale Yao <[email protected]>
Date: Tue, 23 Jun 2026 04:15:11 +0200
Subject: [PATCH 046/180] avfilter/vf_vif: Fix out of array access with small
 dimensions

The hand written boundary mirroring reflected an out of range index only
once, which is insufficient when the image dimension is smaller than the
filter half width (filt_w/2 == 8). A 1x1 input made the index reach 8
and -7, reading out of the src[]/temp[] arrays. Use avpriv_mirror(),
which mirrors repeatedly and stays in range for any dimension.

Fixes: out of array access
Fixes: repro.sh
Fixes: HuQn51lLiVJX
Fixes: 38aea9b041 (avfilter: add vif filter)
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit 56309e476ab385881b15fe52634ba2208bb7f4ba)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavfilter/vf_vif.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/libavfilter/vf_vif.c b/libavfilter/vf_vif.c
index 64eb39699f..412019a8cf 100644
--- a/libavfilter/vf_vif.c
+++ b/libavfilter/vf_vif.c
@@ -27,6 +27,7 @@
 
 #include <float.h>
 
+#include "libavutil/internal.h"
 #include "libavutil/mem.h"
 #include "libavutil/opt.h"
 #include "libavutil/pixdesc.h"
@@ -238,7 +239,7 @@ static int vif_filter1d(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
                     int ii = i - filt_w / 2 + filt_i;
                     float img_coeff;
 
-                    ii = ii < 0 ? -ii : (ii >= h ? 2 * h - ii - 1 : ii);
+                    ii = avpriv_mirror(ii, h - 1);
 
                     img_coeff = src[ii * src_stride + j];
                     sum += filt_coeff * img_coeff;
@@ -267,7 +268,7 @@ static int vif_filter1d(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
                     int jj = j - filt_w / 2 + filt_j;
                     float img_coeff;
 
-                    jj = jj < 0 ? -jj : (jj >= w ? 2 * w - jj - 1 : jj);
+                    jj = avpriv_mirror(jj, w - 1);
 
                     img_coeff = temp[jj];
                     sum += filt_coeff * img_coeff;
-- 
2.52.0


From 21e25bd1ab7a1190bb01bc7e0d2b262a24f50c08 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Wed, 24 Jun 2026 03:43:32 +0200
Subject: [PATCH 047/180] avcodec/mpeg4videodec: Copy studio_profile in
 frame-thread context update

Fixes: out of array access
Fixes: poc.m4v / make_poc.py
Fixes: 93KU7grvT7G1
Fixes: f9d3841ae6 (mpeg4video: Add support for MPEG-4 Simple Studio Profile.)
Found-by: VulnForge Security Research Team <[email protected]>
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit 19035c35fcd332ed4025159fb1ae423f982fa19f)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/mpeg4videodec.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/libavcodec/mpeg4videodec.c b/libavcodec/mpeg4videodec.c
index 3d20f7c389..9cfb3cde2d 100644
--- a/libavcodec/mpeg4videodec.c
+++ b/libavcodec/mpeg4videodec.c
@@ -3923,6 +3923,7 @@ static int mpeg4_update_thread_context(AVCodecContext *dst,
     s->cplx_estimation_trash_p   = s1->cplx_estimation_trash_p;
     s->cplx_estimation_trash_b   = s1->cplx_estimation_trash_b;
     s->rgb                       = s1->rgb;
+    s->h.c.studio_profile        = s1->h.c.studio_profile;
 
     s->h.skipped_last_frame      = s1->h.skipped_last_frame;
     s->h.padding_bug_score       = s1->h.padding_bug_score; // FIXME: racy
-- 
2.52.0


From 0511c27041518c5ecfabda4aaf6b83ecfbcc0d41 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Fri, 26 Jun 2026 20:58:49 +0200
Subject: [PATCH 048/180] avcodec/ffv1dec: Reject bayer with unaligned slice
 coordinates

Bayer was introduced (4f509c9e43) with combined_version 0x4000A, so no real
bayer stream uses the old unaligned coordinates; reject it. Odd picture
dimensions remain valid and are handled by aligning the bayer allocation to
2x2.

Fixes: out of array write
Fixes: fa6F4c0xA8el
Fixes: 4f509c9e43 (ffv1dec: implement Bayer pixel format encoding)
Found-by: Anonymous
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit cc17277c36cd7a87ccc99ae0e1b9eb88cbaa43ca)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/ffv1dec.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/libavcodec/ffv1dec.c b/libavcodec/ffv1dec.c
index 6677e5c8b4..0c0689c81f 100644
--- a/libavcodec/ffv1dec.c
+++ b/libavcodec/ffv1dec.c
@@ -583,6 +583,12 @@ static int read_header(FFV1Context *f, RangeCoder *c)
     if (ret < 0)
         return ret;
 
+    if (f->bayer && f->combined_version <= 0x40002) {
+        av_log(f->avctx, AV_LOG_ERROR,
+               "Bayer requires aligned slice coordinates (combined_version > 0x40002)\n");
+        return AVERROR_INVALIDDATA;
+    }
+
     if (f->configured_pix_fmt != f->pix_fmt ||
         f->configured_width != f->width ||
         f->configured_height != f->height ||
-- 
2.52.0


From a1712ef81627b118e394e718b5077778d95cc3ed Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Fri, 26 Jun 2026 20:58:49 +0200
Subject: [PATCH 049/180] avcodec/ffv1enc: Reject odd dimensions for bayer

Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit 87bd15dc3c2176ea4708d585a1d278a8fb2c05c0)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/ffv1enc.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/libavcodec/ffv1enc.c b/libavcodec/ffv1enc.c
index eb55d3ba32..e6b7506557 100644
--- a/libavcodec/ffv1enc.c
+++ b/libavcodec/ffv1enc.c
@@ -1016,6 +1016,11 @@ static av_cold int encode_init_internal(AVCodecContext *avctx)
     if (ret < 0)
         return ret;
 
+    if (s->bayer && (avctx->width & 1 || avctx->height & 1)) {
+        av_log(avctx, AV_LOG_ERROR, "bayer requires even dimensions\n");
+        return AVERROR(EINVAL);
+    }
+
     if (s->bits_per_raw_sample > (s->version > 3 ? 16 : 8) && !s->remap_mode) {
         if (s->ac == AC_GOLOMB_RICE) {
             av_log(avctx, AV_LOG_INFO,
-- 
2.52.0


From e41e53e5ae484a09561d787f9bdb366214b55fdf Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Fri, 26 Jun 2026 20:58:49 +0200
Subject: [PATCH 050/180] avcodec: Align bayer pixel formats to 2x2

Fixes: out of array access with odd dimensioned bayer
Fixes: fa6F4c0xA8el
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit 11afa309fb4b71059f145d887f8013b960399533)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/utils.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/libavcodec/utils.c b/libavcodec/utils.c
index 550d818572..256fb4ed39 100644
--- a/libavcodec/utils.c
+++ b/libavcodec/utils.c
@@ -315,6 +315,21 @@ void avcodec_align_dimensions2(AVCodecContext *s, int *width, int *height,
             h_align = 8;
         }
         break;
+    case AV_PIX_FMT_BAYER_BGGR8:
+    case AV_PIX_FMT_BAYER_RGGB8:
+    case AV_PIX_FMT_BAYER_GBRG8:
+    case AV_PIX_FMT_BAYER_GRBG8:
+    case AV_PIX_FMT_BAYER_BGGR16LE:
+    case AV_PIX_FMT_BAYER_BGGR16BE:
+    case AV_PIX_FMT_BAYER_RGGB16LE:
+    case AV_PIX_FMT_BAYER_RGGB16BE:
+    case AV_PIX_FMT_BAYER_GBRG16LE:
+    case AV_PIX_FMT_BAYER_GBRG16BE:
+    case AV_PIX_FMT_BAYER_GRBG16LE:
+    case AV_PIX_FMT_BAYER_GRBG16BE:
+        w_align = FFMAX(w_align, 2);
+        h_align = FFMAX(h_align, 2);
+        break;
     default:
         break;
     }
-- 
2.52.0


From b76ad17ab401f696890316a4a8b154455f4178cb Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Fri, 5 Jun 2026 04:16:30 +0200
Subject: [PATCH 051/180] avformat/mov: check current_dts for overflow

This includes a bunch of semi related checks that Fairy wanted added

Fixes: 498236160/clusterfuzz-testcase-minimized-ffmpeg_dem_MOV_fuzzer-4526018559016960
Found-by: continuous fuzzing process https://github.com/google/oss-fuzz/tree/master/projects/ffmpeg
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit 7e4366cf51e4cf9f428a2517e4ae03650e11d607)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavformat/mov.c | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/libavformat/mov.c b/libavformat/mov.c
index 0915b35a7f..90b01f9499 100644
--- a/libavformat/mov.c
+++ b/libavformat/mov.c
@@ -4876,7 +4876,7 @@ static void mov_build_index(MOVContext *mov, AVStream *st)
             sc->time_offset = start_time -  (uint64_t)empty_duration;
             sc->min_corrected_pts = start_time;
             if (!mov->advanced_editlist)
-                current_dts = -sc->time_offset;
+                current_dts = -av_clip64(sc->time_offset, -INT64_MAX, INT64_MAX);
         }
 
         if (!multiple_edits && !mov->advanced_editlist &&
@@ -4897,8 +4897,10 @@ static void mov_build_index(MOVContext *mov, AVStream *st)
         int rap_group_present = sc->rap_group_count && sc->rap_group;
         int key_off = (sc->keyframe_count && sc->keyframes[0] > 0) || (sc->stps_count && sc->stps_data[0] > 0);
 
+        av_assert0(sc->dts_shift >= 0);
+        if (current_dts < INT64_MIN + sc->dts_shift)
+            return;
         current_dts -= sc->dts_shift;
-
         if (!sc->sample_count || sti->nb_index_entries || sc->tts_count)
             return;
         if (sc->sample_count >= UINT_MAX / sizeof(*sti->index_entries) - sti->nb_index_entries)
@@ -4994,6 +4996,8 @@ static void mov_build_index(MOVContext *mov, AVStream *st)
                 current_offset += sample_size;
                 stream_size += sample_size;
 
+                if (current_dts > INT64_MAX - sc->tts_data[stts_index].duration)
+                    return;
                 current_dts += sc->tts_data[stts_index].duration;
 
                 distance++;
@@ -5103,6 +5107,8 @@ static void mov_build_index(MOVContext *mov, AVStream *st)
                        size, samples);
 
                 current_offset += size;
+                if (current_dts > INT64_MAX - samples)
+                    return;
                 current_dts += samples;
                 chunk_samples -= samples;
             }
@@ -5120,9 +5126,9 @@ static void mov_build_index(MOVContext *mov, AVStream *st)
 
     // Update start time of the stream.
     if (st->start_time == AV_NOPTS_VALUE && st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && sti->nb_index_entries > 0) {
-        st->start_time = sti->index_entries[0].timestamp + sc->dts_shift;
+        st->start_time = av_sat_add64(sti->index_entries[0].timestamp, sc->dts_shift);
         if (sc->tts_data) {
-            st->start_time += sc->tts_data[0].offset;
+            st->start_time = av_sat_add64(st->start_time, sc->tts_data[0].offset);
         }
     }
 
-- 
2.52.0


From f8d7795dcca36a4dd412e89cbd83e3dfec1e0d81 Mon Sep 17 00:00:00 2001
From: Umar Pathan <[email protected]>
Date: Sun, 28 Jun 2026 23:02:52 +0200
Subject: [PATCH 052/180] avcodec/rasc: Check that 32-bit DLTA accesses stay
 within the row

Found-by: bikini (github.com/bikini/exploitarium)
Fixes: out of array access
Fixes: rowspill_128x1.avi / gen_rowspill_avi.py
Fixes: xGV79bIb7uAJ
(cherry picked from commit 11ff18a6c80187405fc492f9bb07ba9f2f663f76)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/rasc.c | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/libavcodec/rasc.c b/libavcodec/rasc.c
index 5f956a9b2c..d784a44063 100644
--- a/libavcodec/rasc.c
+++ b/libavcodec/rasc.c
@@ -320,6 +320,11 @@ static int decode_move(AVCodecContext *avctx,
     return 0;
 }
 
+static inline int dlta_room(unsigned cx, unsigned w, unsigned bpp, unsigned need)
+{
+    return cx + need <= w * bpp;
+}
+
 #define NEXT_LINE                        \
     if (cx >= w * s->bpp) {              \
         cx = 0;                          \
@@ -418,6 +423,8 @@ static int decode_dlta(AVCodecContext *avctx,
         case 4:
             fill = bytestream2_get_byte(&dc);
             while (len > 0 && cy > 0) {
+                if (!dlta_room(cx, w, s->bpp, 4))
+                    return AVERROR_INVALIDDATA;
                 AV_WL32(b1 + cx, AV_RL32(b2 + cx));
                 AV_WL32(b2 + cx, fill);
                 cx++;
@@ -427,6 +434,8 @@ static int decode_dlta(AVCodecContext *avctx,
         case 7:
             fill = bytestream2_get_le32(&dc);
             while (len > 0 && cy > 0) {
+                if (!dlta_room(cx, w, s->bpp, 4))
+                    return AVERROR_INVALIDDATA;
                 AV_WL32(b1 + cx, AV_RL32(b2 + cx));
                 AV_WL32(b2 + cx, fill);
                 cx += 4;
@@ -443,6 +452,8 @@ static int decode_dlta(AVCodecContext *avctx,
             while (len > 0 && cy > 0) {
                 unsigned v0, v1;
 
+                if (!dlta_room(cx, w, s->bpp, 4))
+                    return AVERROR_INVALIDDATA;
                 v0 = AV_RL32(b2 + cx);
                 v1 = AV_RL32(b1 + cx);
                 AV_WL32(b2 + cx, v1);
@@ -454,6 +465,8 @@ static int decode_dlta(AVCodecContext *avctx,
         case 13:
             while (len > 0 && cy > 0) {
                 fill = bytestream2_get_le32(&dc);
+                if (!dlta_room(cx, w, s->bpp, 4))
+                    return AVERROR_INVALIDDATA;
                 AV_WL32(b1 + cx, AV_RL32(b2 + cx));
                 AV_WL32(b2 + cx, fill);
                 cx += 4;
-- 
2.52.0


From f15e730cd225763bbae68614af777560aeb449dd Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Sun, 28 Jun 2026 22:05:28 +0200
Subject: [PATCH 053/180] avformat/iamf_parse: check count_label against the
 available bytes

Fixes: unbounded allocation / denial of service
Fixes: tP59h4cpaFyg
Fixes: 4ee05182b7 (avformat: Immersive Audio Model and Formats demuxer)
Found-by: Adrian Junge (vurlo)
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit 86708357d126af84c16f80d9c57335d1e8c845c5)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavformat/iamf_parse.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/libavformat/iamf_parse.c b/libavformat/iamf_parse.c
index d74a8677d6..4c2df2c9e6 100644
--- a/libavformat/iamf_parse.c
+++ b/libavformat/iamf_parse.c
@@ -1009,6 +1009,11 @@ static int mix_presentation_obu(void *s, IAMFContext *c, AVIOContext *pb, int le
     mix_presentation->cmix = mix;
 
     mix_presentation->count_label = ffio_read_leb(pbc);
+    if (mix_presentation->count_label > len - avio_tell(pbc)) {
+        mix_presentation->count_label = 0;
+        ret = AVERROR_INVALIDDATA;
+        goto fail;
+    }
     mix_presentation->language_label = av_calloc(mix_presentation->count_label,
                                                  sizeof(*mix_presentation->language_label));
     if (!mix_presentation->language_label) {
-- 
2.52.0


From 83c2e80f804e5f65be0266b5da587b6010a9940a Mon Sep 17 00:00:00 2001
From: Jeongkeun Kim <[email protected]>
Date: Tue, 28 Apr 2026 13:51:25 +0900
Subject: [PATCH 054/180] tests/checkasm/llviddsp: fix add_left_pred_int16
 buffer compare size
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

dst0/dst1 are uint16_t* allocated as width * sizeof(uint16_t), but
the memcmp at the end of check_add_left_pred_16 only compared `width`
bytes — missing the second half of each buffer. Same pattern used
correctly in tests/checkasm/huffyuvdsp.c (memcmp with width * sizeof()).

While at it, fix missing whitespace around & and || on the same line.

Fixes: fbe91487797c ("checkasm/llviddsp : add test for other dsp func")
Signed-off-by: Jeongkeun Kim <[email protected]>
(cherry picked from commit 2be6bf55079ea512717153f6df673391e9563e78)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 tests/checkasm/llviddsp.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/checkasm/llviddsp.c b/tests/checkasm/llviddsp.c
index a8245b0d94..1094638229 100644
--- a/tests/checkasm/llviddsp.c
+++ b/tests/checkasm/llviddsp.c
@@ -145,7 +145,7 @@ static void check_add_left_pred_16(LLVidDSPContext *c, unsigned mask, int width,
 
     res0 = call_ref(dst0, src0, mask, width, acc);
     res1 = call_new(dst1, src1, mask, width, acc);
-    if ((res0 &0xFFFF) != (res1 &0xFFFF)|| memcmp(dst0, dst1, width))
+    if ((res0 & 0xFFFF) != (res1 & 0xFFFF) || memcmp(dst0, dst1, width * sizeof(*dst0)))
         fail();
     bench_new(dst1, src1, mask, width, acc);
 
-- 
2.52.0


From 4423d1b59e870ae874c1bfe49e41ec6df32c8b96 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Mon, 29 Jun 2026 01:35:05 +0200
Subject: [PATCH 055/180] avfilter/af_adeclick: free transform contexts and
 buffers on error

Found-by: Ao Xijie
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit 59a5090070d094029cc328f6160b87a9f214c01d)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavfilter/af_adeclick.c | 18 ++++++++++++------
 1 file changed, 12 insertions(+), 6 deletions(-)

diff --git a/libavfilter/af_adeclick.c b/libavfilter/af_adeclick.c
index 043435b186..198e7fab63 100644
--- a/libavfilter/af_adeclick.c
+++ b/libavfilter/af_adeclick.c
@@ -130,8 +130,8 @@ static int config_input(AVFilterLink *inlink)
         return AVERROR(ENOMEM);
 
     {
-        double *tx_in[2], *tx_out[2];
-        AVTXContext *tx, *itx;
+        double *tx_in[2] = { NULL }, *tx_out[2] = { NULL };
+        AVTXContext *tx = NULL, *itx = NULL;
         av_tx_fn tx_fn, itx_fn;
         int ret, tx_size;
         double scale;
@@ -141,19 +141,21 @@ static int config_input(AVFilterLink *inlink)
         scale = 1.0;
         ret = av_tx_init(&tx, &tx_fn, AV_TX_DOUBLE_RDFT, 0, tx_size, &scale, 0);
         if (ret < 0)
-            return ret;
+            goto tx_end;
 
         scale = 1.0 / tx_size;
         ret = av_tx_init(&itx, &itx_fn, AV_TX_DOUBLE_RDFT, 1, tx_size, &scale, 0);
         if (ret < 0)
-            return ret;
+            goto tx_end;
 
         tx_in[0]  = av_calloc(tx_size + 2, sizeof(*tx_in[0]));
         tx_in[1]  = av_calloc(tx_size + 2, sizeof(*tx_in[1]));
         tx_out[0] = av_calloc(tx_size + 2, sizeof(*tx_out[0]));
         tx_out[1] = av_calloc(tx_size + 2, sizeof(*tx_out[1]));
-        if (!tx_in[0] || !tx_in[1] || !tx_out[0] || !tx_out[1])
-            return AVERROR(ENOMEM);
+        if (!tx_in[0] || !tx_in[1] || !tx_out[0] || !tx_out[1]) {
+            ret = AVERROR(ENOMEM);
+            goto tx_end;
+        }
 
         for (int n = 0; n < s->window_size - s->hop_size; n++)
             tx_in[0][n] = 1.0;
@@ -180,6 +182,7 @@ static int config_input(AVFilterLink *inlink)
         for (int n = 0; n < s->window_size; n++)
             s->window_func_lut[n] = tx_out[0][n] * scale;
 
+tx_end:
         av_tx_uninit(&tx);
         av_tx_uninit(&itx);
 
@@ -187,6 +190,9 @@ static int config_input(AVFilterLink *inlink)
         av_freep(&tx_in[1]);
         av_freep(&tx_out[0]);
         av_freep(&tx_out[1]);
+
+        if (ret < 0)
+            return ret;
     }
 
     av_frame_free(&s->in);
-- 
2.52.0


From eaf524a746f93804f5f1b1f1657e0ac8e98a8496 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Mon, 29 Jun 2026 01:35:05 +0200
Subject: [PATCH 056/180] avfilter/vf_removelogo: free the whole mask on
 allocation failure

Found-by: Ao Xijie
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit 9a01c1cb6a4cf87529fe9898b66ec55c5b032639)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavfilter/vf_removelogo.c | 28 +++++++++++++++++-----------
 1 file changed, 17 insertions(+), 11 deletions(-)

diff --git a/libavfilter/vf_removelogo.c b/libavfilter/vf_removelogo.c
index 1da5f5d0c7..7285f5336a 100644
--- a/libavfilter/vf_removelogo.c
+++ b/libavfilter/vf_removelogo.c
@@ -311,22 +311,18 @@ static av_cold int init(AVFilterContext *ctx)
        the filter is applied, the mask size is determined on a pixel
        by pixel basis, with pixels nearer the edge of the logo getting
        smaller mask sizes. */
-    mask = (int ***)av_malloc_array(s->max_mask_size + 1, sizeof(int **));
+    mask = av_calloc(s->max_mask_size + 1, sizeof(*mask));
     if (!mask)
         return AVERROR(ENOMEM);
 
     for (a = 0; a <= s->max_mask_size; a++) {
-        mask[a] = (int **)av_malloc_array((a * 2) + 1, sizeof(int *));
-        if (!mask[a]) {
-            av_free(mask);
-            return AVERROR(ENOMEM);
-        }
+        mask[a] = av_calloc((a * 2) + 1, sizeof(*mask[a]));
+        if (!mask[a])
+            goto mask_fail;
         for (b = -a; b <= a; b++) {
-            mask[a][b + a] = (int *)av_malloc_array((a * 2) + 1, sizeof(int));
-            if (!mask[a][b + a]) {
-                av_free(mask);
-                return AVERROR(ENOMEM);
-            }
+            mask[a][b + a] = av_malloc_array((a * 2) + 1, sizeof(*mask[a][b + a]));
+            if (!mask[a][b + a])
+                goto mask_fail;
             for (c = -a; c <= a; c++) {
                 if ((b * b) + (c * c) <= (a * a)) /* Circular 0/1 mask. */
                     mask[a][b + a][c + a] = 1;
@@ -351,6 +347,16 @@ static av_cold int init(AVFilterContext *ctx)
     SHOW_LOGO_INFO(half);
 
     return 0;
+
+mask_fail:
+    for (a = 0; a <= s->max_mask_size; a++) {
+        if (mask[a])
+            for (b = 0; b < (a * 2) + 1; b++)
+                av_free(mask[a][b]);
+        av_free(mask[a]);
+    }
+    av_free(mask);
+    return AVERROR(ENOMEM);
 }
 
 static int config_props_input(AVFilterLink *inlink)
-- 
2.52.0


From 5464915e6966363d957ccc795f127b9c2b932af0 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Mon, 29 Jun 2026 01:35:05 +0200
Subject: [PATCH 057/180] avformat/movenc: free sgpd_entries on the
 roll-distance error path

Found-by: Ao Xijie
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit b553b810c460e484f70a7368ed7abbdcecbe845b)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavformat/movenc.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/libavformat/movenc.c b/libavformat/movenc.c
index c14f05ecc8..66752f35dc 100644
--- a/libavformat/movenc.c
+++ b/libavformat/movenc.c
@@ -3345,8 +3345,10 @@ static int mov_preroll_write_stbl_atoms(AVIOContext *pb, MOVTrack *track)
             if (roll_samples_remaining > 0)
                 distance = 0;
             /* Verify distance is a maximum of 32 (2.5ms) packets. */
-            if (distance > 32)
+            if (distance > 32) {
+                av_freep(&sgpd_entries);
                 return AVERROR_INVALIDDATA;
+            }
             if (i && distance == sgpd_entries[entries].roll_distance) {
                 sgpd_entries[entries].count++;
             } else {
-- 
2.52.0


From 07af5c70debfc28ef75fd7bddf806471b117eee2 Mon Sep 17 00:00:00 2001
From: Thomas Guilbert <[email protected]>
Date: Fri, 15 May 2026 00:56:45 +0000
Subject: [PATCH 058/180] Guard against loop underflow

This commits adds an early returns if `len <= pred_order` for RISC-V
architectures, matching the logic on other architectures.

(cherry picked from commit f956c41d7b2ecb5802a24dfe158d319b71c8a95a)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/riscv/flacdsp_rvv.S | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/libavcodec/riscv/flacdsp_rvv.S b/libavcodec/riscv/flacdsp_rvv.S
index a927f188d3..0db81726f1 100644
--- a/libavcodec/riscv/flacdsp_rvv.S
+++ b/libavcodec/riscv/flacdsp_rvv.S
@@ -26,6 +26,7 @@ func ff_flac_lpc16_rvv, zve32x, b
         vsetvl  zero, a2, t0
         vle32.v v8, (a1)
         sub     a4, a4, a2
+        blez    a4, 2f
         vle32.v v16, (a0)
         sh2add  a0, a2, a0
         vmv.s.x v0, zero
@@ -41,7 +42,7 @@ func ff_flac_lpc16_rvv, zve32x, b
         sw      t0, (a0)
         addi    a0, a0, 4
         bnez    a4, 1b
-
+2:
         ret
 endfunc
 
-- 
2.52.0


From c7d69b3a80af47db2b85df7ced9fa7ca8cefbf51 Mon Sep 17 00:00:00 2001
From: Thomas Guilbert <[email protected]>
Date: Thu, 21 May 2026 01:22:37 +0000
Subject: [PATCH 059/180] Guard lpc methods in flacdsp and add tests

This commit adds additional guards for the RISC-V lpc variants.

It also adds tests to exercise the guarded paths in checkasm/flacdsp.c.

(cherry picked from commit b3fb13e8dd6ef2451acaffbe84f178d859f50f68)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/riscv/flacdsp_rvv.S | 11 +++++---
 tests/checkasm/flacdsp.c       | 50 ++++++++++++++++++++++++++--------
 2 files changed, 45 insertions(+), 16 deletions(-)

diff --git a/libavcodec/riscv/flacdsp_rvv.S b/libavcodec/riscv/flacdsp_rvv.S
index 0db81726f1..b04e7c7d98 100644
--- a/libavcodec/riscv/flacdsp_rvv.S
+++ b/libavcodec/riscv/flacdsp_rvv.S
@@ -22,11 +22,11 @@
 
 func ff_flac_lpc16_rvv, zve32x, b
         lpad    0
+        ble     a4, a2, 2f
         vtype_vli t0, a2, t2, e32, ta, ma
         vsetvl  zero, a2, t0
         vle32.v v8, (a1)
         sub     a4, a4, a2
-        blez    a4, 2f
         vle32.v v16, (a0)
         sh2add  a0, a2, a0
         vmv.s.x v0, zero
@@ -49,6 +49,7 @@ endfunc
 #if (__riscv_xlen == 64)
 func ff_flac_lpc32_rvv, zve64x, zba
         lpad    0
+        ble     a4, a2, 2f
         addi    t2, a2, -16
         ble     t2, zero, ff_flac_lpc32_rvv_simple
         vsetivli zero, 1, e64, m1, ta, ma
@@ -75,12 +76,13 @@ func ff_flac_lpc32_rvv, zve64x, zba
         sw      t0, (a0)
         addi    a0, a0, 4
         bnez    a4, 1b
-
+2:
         ret
 endfunc
 
 func ff_flac_lpc32_rvv_simple, zve64x, b
         lpad    0
+        ble     a4, a2, 2f
         vtype_vli t3, a2, t1, e64, ta, ma
         vntypei t2, t3
         vsetvl  zero, a2, t3 // e64
@@ -104,12 +106,13 @@ func ff_flac_lpc32_rvv_simple, zve64x, b
         sw      t0, (a0)
         addi    a0, a0, 4
         bnez    a4, 1b
-
+2:
         ret
 endfunc
 
 func ff_flac_lpc33_rvv, zve64x, b
         lpad    0
+        ble     a5, a3, 2f
         vtype_vli t0, a3, t1, e64, ta, ma
         vsetvl  zero, a3, t0
         vmv.s.x v0, zero
@@ -132,7 +135,7 @@ func ff_flac_lpc33_rvv, zve64x, b
         sd      t0, (a0)
         addi    a0, a0, 8
         bnez    a5, 1b
-
+2:
         ret
 endfunc
 #endif
diff --git a/tests/checkasm/flacdsp.c b/tests/checkasm/flacdsp.c
index 6f8e8817b5..d8a10a6b57 100644
--- a/tests/checkasm/flacdsp.c
+++ b/tests/checkasm/flacdsp.c
@@ -74,12 +74,25 @@ static void check_lpc(int pred_order, int bps)
     for (int i = 0; i < BUF_SIZE; i++)
         dst[i] = sign_extend(rnd(), bps);
 
-    memcpy(dst0, dst, BUF_SIZE * sizeof (int32_t));
-    memcpy(dst1, dst, BUF_SIZE * sizeof (int32_t));
-    call_ref(dst0, coeffs, pred_order, qlevel, BUF_SIZE);
-    call_new(dst1, coeffs, pred_order, qlevel, BUF_SIZE);
-    if (memcmp(dst0, dst1, BUF_SIZE * sizeof (int32_t)) != 0)
-       fail();
+    const int test_lens[] = {
+        0,
+        pred_order - 1,
+        pred_order,
+        pred_order + 1,
+        BUF_SIZE,
+    };
+
+    for (int k = 0; k < FF_ARRAY_ELEMS(test_lens); k++) {
+        int len = test_lens[k];
+        if (len < 0 || len > BUF_SIZE) continue;
+
+        memcpy(dst0, dst, BUF_SIZE * sizeof (int32_t));
+        memcpy(dst1, dst, BUF_SIZE * sizeof (int32_t));
+        call_ref(dst0, coeffs, pred_order, qlevel, len);
+        call_new(dst1, coeffs, pred_order, qlevel, len);
+        if (memcmp(dst0, dst1, BUF_SIZE * sizeof (int32_t)) != 0)
+           fail();
+    }
     bench_new(dst, coeffs, pred_order, qlevel, BUF_SIZE);
 }
 
@@ -103,12 +116,25 @@ static void check_lpc33(int pred_order)
         dst[i] = sign_extend64(((int64_t)rnd() << 1) | (rnd() & 1), 33);
     }
 
-    memcpy(dst0, dst, BUF_SIZE * sizeof (int64_t));
-    memcpy(dst1, dst, BUF_SIZE * sizeof (int64_t));
-    call_ref(dst0, residuals, coeffs, pred_order, qlevel, BUF_SIZE);
-    call_new(dst1, residuals, coeffs, pred_order, qlevel, BUF_SIZE);
-    if (memcmp(dst0, dst1, BUF_SIZE * sizeof (int64_t)) != 0)
-       fail();
+    const int test_lens[] = {
+        0,
+        pred_order - 1,
+        pred_order,
+        pred_order + 1,
+        BUF_SIZE,
+    };
+
+    for (int k = 0; k < FF_ARRAY_ELEMS(test_lens); k++) {
+        int len = test_lens[k];
+        if (len < 0 || len > BUF_SIZE) continue;
+
+        memcpy(dst0, dst, BUF_SIZE * sizeof (int64_t));
+        memcpy(dst1, dst, BUF_SIZE * sizeof (int64_t));
+        call_ref(dst0, residuals, coeffs, pred_order, qlevel, len);
+        call_new(dst1, residuals, coeffs, pred_order, qlevel, len);
+        if (memcmp(dst0, dst1, BUF_SIZE * sizeof (int64_t)) != 0)
+           fail();
+    }
     bench_new(dst, residuals, coeffs, pred_order, qlevel, BUF_SIZE);
 }
 
-- 
2.52.0


From 2ec918330eee04f6069e9561a0d3950b7b52fc5e Mon Sep 17 00:00:00 2001
From: Pavel Kohout <[email protected]>
Date: Tue, 30 Jun 2026 21:52:47 +0200
Subject: [PATCH 060/180] avfilter/boxblur: reject pixel formats deeper than 16
 bits

Fixes: heap buffer overflow
Fixes: CRufFb6TVikL
Fixes: dee7440531 (vf_boxblur: Templatize blur{8,16})
Found-by: Pavel Kohout (Aisle Research)
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit 0dac41d2e5e0f4251ed89eedb03a847d5d59ef67)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavfilter/vf_boxblur.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/libavfilter/vf_boxblur.c b/libavfilter/vf_boxblur.c
index 3cb42471a7..8d9649172a 100644
--- a/libavfilter/vf_boxblur.c
+++ b/libavfilter/vf_boxblur.c
@@ -25,6 +25,7 @@
  * Ported from MPlayer libmpcodecs/vf_boxblur.c.
  */
 
+#include "libavutil/avassert.h"
 #include "libavutil/common.h"
 #include "libavutil/mem.h"
 #include "libavutil/opt.h"
@@ -65,6 +66,7 @@ static int query_formats(const AVFilterContext *ctx,
     for (fmt = 0; av_pix_fmt_desc_get(fmt); fmt++) {
         const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(fmt);
         if (!(desc->flags & (AV_PIX_FMT_FLAG_HWACCEL | AV_PIX_FMT_FLAG_BITSTREAM | AV_PIX_FMT_FLAG_PAL)) &&
+            desc->comp[0].depth <= 16 &&
             (desc->flags & AV_PIX_FMT_FLAG_PLANAR || desc->nb_components == 1) &&
             (!(desc->flags & AV_PIX_FMT_FLAG_BE) == !HAVE_BIGENDIAN || desc->comp[0].depth == 8) &&
             (ret = ff_add_format(&formats, fmt)) < 0)
@@ -163,7 +165,10 @@ static inline void blur(uint8_t *dst, int dst_step, const uint8_t *src, int src_
                         int len, int radius, int pixsize)
 {
     if (pixsize == 1) blur8 (dst, dst_step   , src, src_step   , len, radius);
-    else              blur16((uint16_t*)dst, dst_step>>1, (const uint16_t*)src, src_step>>1, len, radius);
+    else if (pixsize == 2)
+                      blur16((uint16_t*)dst, dst_step>>1, (const uint16_t*)src, src_step>>1, len, radius);
+    else
+        av_assert0(0);
 }
 
 static inline void blur_power(uint8_t *dst, int dst_step, const uint8_t *src, int src_step,
-- 
2.52.0


From 385ac2fadcb4394ec4f65e5c4d3d24003e090f36 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Tue, 30 Jun 2026 00:11:50 +0200
Subject: [PATCH 061/180] avformat/spdifenc: bound DTS core_size against the
 packet size in the HD path

Fixes: out of array read
Fixes: yBSax492UIB9
Fixes: 482d98f69b2 (spdifenc: IEC 61937 encapsulation of DTS-HD for HDMI)
Found-by: Pavel Kohout (Aisle Research)
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit 6f80e2765492700622596af720534cef33dd31b4)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavformat/spdifenc.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libavformat/spdifenc.c b/libavformat/spdifenc.c
index ab3f73da0d..16eebda01c 100644
--- a/libavformat/spdifenc.c
+++ b/libavformat/spdifenc.c
@@ -225,7 +225,7 @@ static int spdif_header_dts4(AVFormatContext *s, AVPacket *pkt, int core_size,
              * (dtshd_fallback == 0) */
             ctx->dtshd_skip = 1;
     }
-    if (ctx->dtshd_skip && core_size) {
+    if (ctx->dtshd_skip && core_size && core_size <= pkt->size) {
         pkt_size = core_size;
         if (ctx->dtshd_fallback >= 0)
             --ctx->dtshd_skip;
-- 
2.52.0


From 3c441711a343ccf50196c70a3f0b554b52f8d9de Mon Sep 17 00:00:00 2001
From: Pavel Kohout <[email protected]>
Date: Tue, 30 Jun 2026 21:55:16 +0200
Subject: [PATCH 062/180] avformat/rtpdec_asf: reject ASF objects smaller than
 their header

Fixes: infinite loop
Fixes: MzWwJdpZF2Ls
Fixes: c2f3eec445389d67afc8c699ba23915a20cae51c (Implement RTSP-MS/ASF packet parsing.)
Found-by: Pavel Kohout (Aisle Research)
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit 11d5f475be95d22d5f0692220cc772b116abc632)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavformat/rtpdec_asf.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/libavformat/rtpdec_asf.c b/libavformat/rtpdec_asf.c
index b3b346f3cc..f7fa69e27f 100644
--- a/libavformat/rtpdec_asf.c
+++ b/libavformat/rtpdec_asf.c
@@ -56,6 +56,8 @@ static int rtp_asf_fix_header(uint8_t *buf, int len)
         uint64_t chunksize = AV_RL64(p + sizeof(ff_asf_guid));
         int skip = 6 * 8 + 3 * 4 + sizeof(ff_asf_guid) * 2;
         if (memcmp(p, ff_asf_file_header, sizeof(ff_asf_guid))) {
+            if (chunksize < sizeof(ff_asf_guid) + 8)
+                return -1;
             if (chunksize > end - p)
                 return -1;
             p += chunksize;
-- 
2.52.0


From dcf8ce2802bf2be1b0c6b8d4996abca7b669978d Mon Sep 17 00:00:00 2001
From: Pavel Kohout <[email protected]>
Date: Mon, 29 Jun 2026 23:30:41 +0200
Subject: [PATCH 063/180] avformat/vobsub: reuse subtitle streams and bound the
 stream count

Fixes: heap buffer overflow
Fixes: lqaO5R1BaZGO
Fixes: dbfe61100b (avformat/vobsub: fix several issues.)
Found-by: Pavel Kohout (Aisle Research)
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit dbd495f066a85ba96b17433f4306582aa37c3951)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavformat/mpeg.c | 18 ++++++++++++++++--
 1 file changed, 16 insertions(+), 2 deletions(-)

diff --git a/libavformat/mpeg.c b/libavformat/mpeg.c
index ff5ced8107..29abe329b9 100644
--- a/libavformat/mpeg.c
+++ b/libavformat/mpeg.c
@@ -841,6 +841,20 @@ static int vobsub_read_header(AVFormatContext *s)
             }
 
             if (!st || st->id != stream_id) {
+                st = NULL;
+                for (i = 0; i < s->nb_streams; i++) {
+                    if (s->streams[i]->id == stream_id) {
+                        st = s->streams[i];
+                        break;
+                    }
+                }
+            }
+            if (!st) {
+                if (s->nb_streams >= FF_ARRAY_ELEMS(vobsub->q)) {
+                    av_log(s, AV_LOG_ERROR, "Maximum number of subtitle streams reached\n");
+                    ret = AVERROR_INVALIDDATA;
+                    goto end;
+                }
                 st = avformat_new_stream(s, NULL);
                 if (!st) {
                     ret = AVERROR(ENOMEM);
@@ -865,14 +879,14 @@ static int vobsub_read_header(AVFormatContext *s)
             timestamp = (hh*3600LL + mm*60LL + ss) * 1000LL + ms + delay;
             timestamp = av_rescale_q(timestamp, av_make_q(1, 1000), st->time_base);
 
-            sub = ff_subtitles_queue_insert(&vobsub->q[s->nb_streams - 1], "", 0, 0);
+            sub = ff_subtitles_queue_insert(&vobsub->q[st->index], "", 0, 0);
             if (!sub) {
                 ret = AVERROR(ENOMEM);
                 goto end;
             }
             sub->pos = pos;
             sub->pts = timestamp;
-            sub->stream_index = s->nb_streams - 1;
+            sub->stream_index = st->index;
 
         } else if (!strncmp(line, "alt:", 4)) {
             const char *p = line + 4;
-- 
2.52.0


From c10e7f5dc12367d0dfcc52983d427c6766425fa8 Mon Sep 17 00:00:00 2001
From: Pavel Kohout <[email protected]>
Date: Mon, 29 Jun 2026 23:46:16 +0200
Subject: [PATCH 064/180] avcodec/adx: sync decoder channel state on
 NEW_EXTRADATA

Fixes: out of array access
Fixes: heaNtmHvklpe
Fixes: 92396cee602320c714713ca2d93b53684ad57000 (avformat: add CRI AAX demuxer)
Found-by: Pavel Kohout (Aisle Research)
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit 1836ef96846937a6cc2443698a693104f5c0b21e)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/adxdec.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/libavcodec/adxdec.c b/libavcodec/adxdec.c
index 019fc1a90d..a79ae430aa 100644
--- a/libavcodec/adxdec.c
+++ b/libavcodec/adxdec.c
@@ -173,6 +173,7 @@ static int adx_decode_frame(AVCodecContext *avctx, AVFrame *frame,
     new_extradata = av_packet_get_side_data(avpkt, AV_PKT_DATA_NEW_EXTRADATA,
                                             &new_extradata_size);
     if (new_extradata && new_extradata_size > 0) {
+        int old_channels = c->channels;
         int header_size;
         if ((ret = adx_decode_header(avctx, new_extradata,
                                      new_extradata_size, &header_size,
@@ -181,6 +182,10 @@ static int adx_decode_frame(AVCodecContext *avctx, AVFrame *frame,
             return AVERROR_INVALIDDATA;
         }
 
+        c->channels      = avctx->ch_layout.nb_channels;
+        c->header_parsed = 1;
+        if (old_channels != c->channels)
+            memset(c->prev, 0, sizeof(c->prev));
         c->eof = 0;
     }
 
-- 
2.52.0


From 92880ab4a44a1a868ec217d6e5b8899d81e971a7 Mon Sep 17 00:00:00 2001
From: Anxious Koisi <[email protected]>
Date: Sun, 28 Jun 2026 23:03:25 +0200
Subject: [PATCH 065/180] avcodec/apac: Check bitstream_size to avoid integer
 overflow

Found-by: k00shi_
Fixes: OOM
Fixes: poc.c
Fixes: XMkAe1I9uFro
(cherry picked from commit 63958ce2b5dd1b22f534fc968c86329cbbceef07)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/apac.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/libavcodec/apac.c b/libavcodec/apac.c
index 00e48e7635..ea3b93359d 100644
--- a/libavcodec/apac.c
+++ b/libavcodec/apac.c
@@ -140,6 +140,9 @@ static int apac_decode(AVCodecContext *avctx, AVFrame *frame,
     buf_size = pkt->size;
     input_buf_size = buf_size;
 
+    if ((int64_t)s->bitstream_size + buf_size > INT_MAX / (16 * 8))
+        return AVERROR_INVALIDDATA;
+
     if (s->bitstream_index > 0 && s->bitstream_size > 0) {
         memmove(s->bitstream, &s->bitstream[s->bitstream_index], s->bitstream_size);
         s->bitstream_index = 0;
-- 
2.52.0


From 947c57d9e68800dd4d39f110140f8f6c34cedf80 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Mon, 29 Jun 2026 01:16:44 +0200
Subject: [PATCH 066/180] avcodec/mace: reject sample counts that overflow int

Fixes: heap buffer overflow
Fixes: FmXBI2dbgvgD
Fixes: 0eea212943544d40f99b05571aa7159d78667154 (Add avcodec_decode_audio4().)
Found-by: Adrian Junge (vurlo)
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit aafb5c655edc76a753275c383ebb139feb032718)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/mace.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/libavcodec/mace.c b/libavcodec/mace.c
index 299e5f5cfe..87e802684a 100644
--- a/libavcodec/mace.c
+++ b/libavcodec/mace.c
@@ -252,7 +252,10 @@ static int mace_decode_frame(AVCodecContext *avctx, AVFrame *frame,
     }
 
     /* get output buffer */
-    frame->nb_samples = 3 * (buf_size << (1 - is_mace3)) / channels;
+    int64_t nb_samples = 3 * ((int64_t)buf_size << (1 - is_mace3)) / channels;
+    if (nb_samples > INT_MAX)
+        return AVERROR_INVALIDDATA;
+    frame->nb_samples = nb_samples;
     if ((ret = ff_get_buffer(avctx, frame, 0)) < 0)
         return ret;
     samples = (int16_t **)frame->extended_data;
-- 
2.52.0


From aba17b5dc3e278650264f0f371873b04ca025fe9 Mon Sep 17 00:00:00 2001
From: Pavel Kohout <[email protected]>
Date: Tue, 30 Jun 2026 21:54:03 +0200
Subject: [PATCH 067/180] avformat/imfdec: reject virtual tracks with no
 resources

Fixes: NULL pointer dereference
Fixes: kia6rom3mZdr
Fixes: 73f6cce9361 (avformat/imf: Demuxer)
Found-by: Pavel Kohout (Aisle Research)
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit a76190152d0d1e90f38a761e0a1443c1dcb4e7a8)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavformat/imfdec.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/libavformat/imfdec.c b/libavformat/imfdec.c
index def9b2b7a7..2d1f73c450 100644
--- a/libavformat/imfdec.c
+++ b/libavformat/imfdec.c
@@ -525,6 +525,12 @@ static int open_virtual_track(AVFormatContext *s,
     track->index = track_index;
     track->duration = av_make_q(0, 1);
 
+    if (!virtual_track->resource_count) {
+        av_log(s, AV_LOG_ERROR, "Virtual track has no resources\n");
+        ret = AVERROR_INVALIDDATA;
+        goto clean_up;
+    }
+
     for (uint32_t i = 0; i < virtual_track->resource_count; i++) {
         av_log(s,
                AV_LOG_DEBUG,
-- 
2.52.0


From bd108f4026597c0dc6c99d2bfa9f484e9eb748fb Mon Sep 17 00:00:00 2001
From: Pavel Kohout <[email protected]>
Date: Tue, 30 Jun 2026 21:53:37 +0200
Subject: [PATCH 068/180] avformat/imfdec: check for a missing or empty Path in
 the asset map

Fixes: NULL pointer dereference
Fixes: vIIX7oqa3coO
Fixes: 73f6cce9361 (avformat/imf: Demuxer)
Found-by: Pavel Kohout (Aisle Research)
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit 0fe4bd4b435b1de950890d37b1e4df7bae3655b1)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavformat/imfdec.c | 15 ++++++++++++++-
 1 file changed, 14 insertions(+), 1 deletion(-)

diff --git a/libavformat/imfdec.c b/libavformat/imfdec.c
index 2d1f73c450..761960e544 100644
--- a/libavformat/imfdec.c
+++ b/libavformat/imfdec.c
@@ -255,7 +255,20 @@ static int parse_imf_asset_map_from_xml_dom(AVFormatContext *s,
             return AVERROR_INVALIDDATA;
         }
 
-        uri = xmlNodeGetContent(ff_imf_xml_get_child_element_by_name(node, "Path"));
+        xmlNodePtr path_node = ff_imf_xml_get_child_element_by_name(node, "Path");
+        if (!path_node) {
+            av_log(s, AV_LOG_ERROR, "Unable to parse asset map XML - missing Path element in Chunk\n");
+            ret = AVERROR_INVALIDDATA;
+            break;
+        }
+        uri = xmlNodeGetContent(path_node);
+        if (!uri || !uri[0]) {
+            av_log(s, AV_LOG_ERROR, "Unable to parse asset map XML - empty Path element in Chunk\n");
+            xmlFree(uri);
+            ret = AVERROR_INVALIDDATA;
+            break;
+        }
+
         if (!imf_uri_is_url(uri) && !imf_uri_is_unix_abs_path(uri) && !imf_uri_is_dos_abs_path(uri))
             asset->absolute_uri = av_append_path_component(base_url, uri);
         else
-- 
2.52.0


From 3d5ad47c40436dbdeaaa6601af0bb4575d5aa3c5 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Tue, 30 Jun 2026 00:24:07 +0200
Subject: [PATCH 069/180] avcodec/nvdec: don't double free the fdd-owned
 context on the sep_ref error path

Fixes: double free
Fixes: rpSz7v3yq2u8
Fixes: 72982f8cb5dad6252a14226d28128313eed4a5ff (avcodec/nvdec: add support for separate reference frame)
Found-by: Pavel Kohout (Aisle Research)
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit 4c6217477fc64305055b37d9d1d0d76d30e37f97)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/nvdec.c | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/libavcodec/nvdec.c b/libavcodec/nvdec.c
index 6420483f38..a986e9be16 100644
--- a/libavcodec/nvdec.c
+++ b/libavcodec/nvdec.c
@@ -627,8 +627,7 @@ int ff_nvdec_start_frame_sep_ref(AVCodecContext *avctx, AVFrame *frame, int has_
             cf->ref_idx_ref = av_refstruct_pool_get(ctx->decoder_pool);
             if (!cf->ref_idx_ref) {
                 av_log(avctx, AV_LOG_ERROR, "No decoder surfaces left\n");
-                ret = AVERROR(ENOMEM);
-                goto fail;
+                return AVERROR(ENOMEM);
             }
         }
         cf->ref_idx = *cf->ref_idx_ref;
@@ -638,9 +637,6 @@ int ff_nvdec_start_frame_sep_ref(AVCodecContext *avctx, AVFrame *frame, int has_
     }
 
     return 0;
-fail:
-    nvdec_fdd_priv_free(cf);
-    return ret;
 }
 
 int ff_nvdec_end_frame(AVCodecContext *avctx)
-- 
2.52.0


From eac6965d9c9c3ad921e87d10a61944cbe3e9e9e7 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Fri, 3 Jul 2026 04:47:13 +0200
Subject: [PATCH 070/180] avformat/wavdec: fix integer overflow in W64 chunk
 size check

Fixes: 523658585/clusterfuzz-testcase-minimized-ffmpeg_dem_W64_fuzzer-5454488994643968
Found-by: continuous fuzzing process https://github.com/google/oss-fuzz/tree/master/projects/ffmpeg
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit 3b28a08a32c3599410c7f7cd3c1032c58f34dbe0)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavformat/wavdec.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libavformat/wavdec.c b/libavformat/wavdec.c
index b980bebf0e..247681e265 100644
--- a/libavformat/wavdec.c
+++ b/libavformat/wavdec.c
@@ -908,7 +908,7 @@ static int w64_read_header(AVFormatContext *s)
         if (avio_read(pb, guid, 16) != 16)
             break;
         size = avio_rl64(pb);
-        if (size <= 24 || INT64_MAX - size < avio_tell(pb)) {
+        if (size <= 24 || INT64_MAX - size - 7 < avio_tell(pb)) {
             if (data_ofs)
                 break;
             return AVERROR_INVALIDDATA;
-- 
2.52.0


From 97e169f6ced2246e70e4e05702e9f81e3587e7d4 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Wed, 1 Jul 2026 06:09:33 +0200
Subject: [PATCH 071/180] swscale/output: avoid signed overflow in
 yuv2rgb_write_full luma scaling

Fixes: 523522305/clusterfuzz-testcase-minimized-ffmpeg_SWS_fuzzer-4574409678716928
Found-by: continuous fuzzing process https://github.com/google/oss-fuzz/tree/master/projects/ffmpeg
(cherry picked from commit f50e22c68d1e579a3e2818a7d93d6eed092ad387)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libswscale/output.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/libswscale/output.c b/libswscale/output.c
index cea7fb1185..760dca642e 100644
--- a/libswscale/output.c
+++ b/libswscale/output.c
@@ -2003,8 +2003,8 @@ static av_always_inline void yuv2rgb_write_full(SwsInternal *c,
     int isrgb8 = target == AV_PIX_FMT_BGR8 || target == AV_PIX_FMT_RGB8;
 
     Y -= c->yuv2rgb_y_offset;
-    Y *= c->yuv2rgb_y_coeff;
-    Y += 1 << 21;
+    Y *= (unsigned)c->yuv2rgb_y_coeff;
+    Y += 1U << 21;
     R = (unsigned)Y + V*(unsigned)c->yuv2rgb_v2r_coeff;
     G = (unsigned)Y + V*(unsigned)c->yuv2rgb_v2g_coeff + U*(unsigned)c->yuv2rgb_u2g_coeff;
     B = (unsigned)Y +                                    U*(unsigned)c->yuv2rgb_u2b_coeff;
-- 
2.52.0


From c20d78c6838ae62e9954163833d24fc0ed1b74a3 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Sun, 28 Jun 2026 15:33:38 +0200
Subject: [PATCH 072/180] avfilter/vf_quirc: resize the quirc buffers when the
 input size changes

Fixes: out of array access
Fixes: JbvzNObhorBp
Fixes: 030e140145 (lavfi: add quirc filter)
Found-by: Adrian Junge (vurlo)
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit 4da9812e25894fb51d62a8875cfa8eb39b5e20f5)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavfilter/vf_quirc.c | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/libavfilter/vf_quirc.c b/libavfilter/vf_quirc.c
index 59dc84caa8..d2ba48e7bc 100644
--- a/libavfilter/vf_quirc.c
+++ b/libavfilter/vf_quirc.c
@@ -36,6 +36,7 @@ typedef struct QuircContext {
     const AVClass *class;
 
     struct quirc *quirc;
+    int width, height;
 } QuircContext;
 
 static av_cold int init(AVFilterContext *ctx)
@@ -67,6 +68,8 @@ static int config_input(AVFilterLink *inlink)
     if (err == -1) {
         return AVERROR(ENOMEM);
     }
+    quirc->width  = inlink->w;
+    quirc->height = inlink->h;
 
     return 0;
 }
@@ -80,6 +83,15 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
     int codes_count;
     uint8_t *image;
 
+    if (quirc->width != inlink->w || quirc->height != inlink->h) {
+        if (quirc_resize(quirc->quirc, inlink->w, inlink->h) < 0) {
+            av_frame_free(&frame);
+            return AVERROR(ENOMEM);
+        }
+        quirc->width  = inlink->w;
+        quirc->height = inlink->h;
+    }
+
     /* copy input image to quirc buffer */
     image = quirc_begin(quirc->quirc, NULL, NULL);
     av_image_copy_plane(image, inlink->w,
-- 
2.52.0


From 7c0b8c8594501dd904ebbb3b45bb3f6e2c745b7d Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Sun, 28 Jun 2026 19:04:07 +0200
Subject: [PATCH 073/180] avcodec/lcldec: zero the not-decoded tail to avoid
 heap disclosure

Fixes: use of uninitialized memory
Fixes: CsNDKB1K1U0C
Fixes: e2c3aa8e2b (avcodec/lcldec: More space for rgb24)
Found-by: Adrian Junge (vurlo)
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit e7cbfd1c507b57a806a5825b87d609963e862c8c)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/lcldec.c | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/libavcodec/lcldec.c b/libavcodec/lcldec.c
index 29b1d85be3..5023243017 100644
--- a/libavcodec/lcldec.c
+++ b/libavcodec/lcldec.c
@@ -120,6 +120,9 @@ static unsigned int mszh_decomp(const unsigned char * srcptr, int srclen, unsign
         }
     }
 
+    if (destptr < destptr_end)
+        memset(destptr, 0, destptr_end - destptr);
+
     return destptr - destptr_bak;
 }
 
@@ -153,8 +156,11 @@ static int zlib_decomp(AVCodecContext *avctx, const uint8_t *src, int src_len, i
     if (expected != (unsigned int)zstream->total_out) {
         av_log(avctx, AV_LOG_ERROR, "Decoded size differs (%d != %lu)\n",
                expected, zstream->total_out);
-        if (expected > (unsigned int)zstream->total_out)
+        if (expected > (unsigned int)zstream->total_out) {
+            memset(c->decomp_buf + offset + zstream->total_out, 0,
+                   c->decomp_size - offset - zstream->total_out);
             return (unsigned int)zstream->total_out;
+        }
         return AVERROR_UNKNOWN;
     }
     return zstream->total_out;
-- 
2.52.0


From 1f51b7b87fd8184ecff4fd0a6bacbf4ee28ce543 Mon Sep 17 00:00:00 2001
From: Omkhar Arasaratnam <[email protected]>
Date: Tue, 30 Jun 2026 21:58:38 +0200
Subject: [PATCH 074/180] avformat/mov: bound the iref thmb/cdsc entry count by
 the sub-box size

Fixes: excessive parsing past the iref sub-box
Fixes: TMwMAJfgy3l9
Fixes: 74e430202d933284fc38b591d4b3a12464e8aec6 (avformat/mov: make items referencing items generic)
Found-by: Omkhar Arasaratnam
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit dcc18b60779b0d228b37621b6485cb74b2d6f352)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavformat/mov.c | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)

diff --git a/libavformat/mov.c b/libavformat/mov.c
index 90b01f9499..4154038d9e 100644
--- a/libavformat/mov.c
+++ b/libavformat/mov.c
@@ -9444,10 +9444,11 @@ fail:
     return ret;
 }
 
-static int mov_read_iref_cdsc(MOVContext *c, AVIOContext *pb, uint32_t type, int version)
+static int mov_read_iref_cdsc(MOVContext *c, AVIOContext *pb, uint32_t type, int version, uint32_t size)
 {
     HEIFItem *from_item = NULL;
     int entries;
+    int item_id_size = version ? 4 : 2;
     int from_item_id = version ? avio_rb32(pb) : avio_rb16(pb);
     const HEIFItemRef ref = { type, from_item_id };
 
@@ -9458,6 +9459,11 @@ static int mov_read_iref_cdsc(MOVContext *c, AVIOContext *pb, uint32_t type, int
     }
 
     entries = avio_rb16(pb);
+    if ((int64_t)entries * item_id_size > (int64_t)size - item_id_size - 2) {
+        av_log(c->fc, AV_LOG_ERROR, "iref %s entry count %d exceeds the sub-box size\n",
+               av_fourcc2str(type), entries);
+        return AVERROR_INVALIDDATA;
+    }
     /* 'to' item ids */
     for (int i = 0; i < entries; i++) {
         HEIFItem *item = get_heif_item(c, version ? avio_rb32(pb) : avio_rb16(pb));
@@ -9496,13 +9502,13 @@ static int mov_read_iref(MOVContext *c, AVIOContext *pb, MOVAtom atom)
     }
 
     while (atom.size) {
-        uint32_t type, size = avio_rb32(pb);
         int64_t next = avio_tell(pb);
+        uint32_t type, size = avio_rb32(pb);
 
-        if (size < 14 || next < 0 || next > INT64_MAX - size)
+        if (size < 14 || size > atom.size || next > INT64_MAX - size)
             return AVERROR_INVALIDDATA;
 
-        next += size - 4;
+        next += size;
         type = avio_rl32(pb);
         switch (type) {
         case MKTAG('d','i','m','g'):
@@ -9512,7 +9518,7 @@ static int mov_read_iref(MOVContext *c, AVIOContext *pb, MOVAtom atom)
             break;
         case MKTAG('c','d','s','c'):
         case MKTAG('t','h','m','b'):
-            ret = mov_read_iref_cdsc(c, pb, type, version);
+            ret = mov_read_iref_cdsc(c, pb, type, version, size - 8);
             if (ret < 0)
                 return ret;
             break;
-- 
2.52.0


From 0e6eef35517af086419c5157980e926a81070c2a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Sat, 27 Jun 2026 17:43:33 +0200
Subject: [PATCH 075/180] avformat/hls: reject Master Playlist tags in a Media
 Playlist
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Fixes infinite loop on crafted HLS playlists.

Fixes: recursion.m3u8
Fixes: cEy2cxyyPaLH
Fixes: cd223e0b4e (Add Apple HTTP Live Streaming demuxer)
Found-by: BapToutatis
Signed-off-by: Kacper Michajłow <[email protected]>
(cherry picked from commit c364ab176f722bdabc886845e770c9eacbc1e3e5)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavformat/hls.c | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/libavformat/hls.c b/libavformat/hls.c
index b3b8f3a80a..e76e68ed51 100644
--- a/libavformat/hls.c
+++ b/libavformat/hls.c
@@ -890,6 +890,12 @@ static int parse_playlist(HLSContext *c, const char *url,
     while (!avio_feof(in)) {
         ff_get_chomp_line(in, line, sizeof(line));
         if (av_strstart(line, "#EXT-X-STREAM-INF:", &ptr)) {
+            if (pls) {
+                av_log(c->ctx, AV_LOG_ERROR,
+                       "Master Playlist tag found in a Media Playlist\n");
+                ret = AVERROR_INVALIDDATA;
+                goto fail;
+            }
             is_variant = 1;
             memset(&variant_info, 0, sizeof(variant_info));
             ff_parse_key_value(ptr, handle_variant_args, &variant_info);
@@ -909,6 +915,12 @@ static int parse_playlist(HLSContext *c, const char *url,
             av_strlcpy(key, info.uri, sizeof(key));
         } else if (av_strstart(line, "#EXT-X-MEDIA:", &ptr)) {
             struct rendition_info info = {{0}};
+            if (pls) {
+                av_log(c->ctx, AV_LOG_ERROR,
+                       "Master Playlist tag found in a Media Playlist\n");
+                ret = AVERROR_INVALIDDATA;
+                goto fail;
+            }
             ff_parse_key_value(ptr, handle_rendition_args, &info);
             new_rendition(c, &info, url);
         } else if (av_strstart(line, "#EXT-X-TARGETDURATION:", &ptr)) {
-- 
2.52.0


From 6727eb54e858a3fe398f04cf46b2b4e71f3e8096 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Wed, 1 Jul 2026 06:18:07 +0200
Subject: [PATCH 076/180] avformat/iff: reject invalid data_size in ANIM

Fixes: 520277418/clusterfuzz-testcase-minimized-ffmpeg_dem_IFF_fuzzer-6733106211258368
Found-by: continuous fuzzing process https://github.com/google/oss-fuzz/tree/master/projects/ffmpeg
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit 14604ec4b797248222cdd72a74d39abbdad9b343)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavformat/iff.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/libavformat/iff.c b/libavformat/iff.c
index 74362d29f2..456b7e35da 100644
--- a/libavformat/iff.c
+++ b/libavformat/iff.c
@@ -992,6 +992,8 @@ static int iff_read_packet(AVFormatContext *s,
         if (pb->eof_reached)
             return AVERROR_EOF;
 
+        if (!data_size || data_size > INT_MAX)
+            return AVERROR_INVALIDDATA;
         ret = av_get_packet(pb, pkt, data_size);
         if (ret < 0)
             return ret;
-- 
2.52.0


From 9d5704e4d94905feba96b5dd02b37b59ed127e6c Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Tue, 23 Jun 2026 04:15:24 +0200
Subject: [PATCH 077/180] avformat/hls_sample_encryption: Validate ADTS frame
 length against packet

Fixes: out of array access
Fixes: playlist.m3u8 / make_poc.py
Fixes: rJ50u41V7ctk
Fixes: ff958b3846 (libavformat/hls: add support for decryption of HLS media segments encrypted using SAMPLE-AES encryption method)
Found-by: Clouditera Security Research Team <[email protected]>
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit 976490dcc3dbe0684851c30eff88a85d4d386d1a)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavformat/hls_sample_encryption.c | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/libavformat/hls_sample_encryption.c b/libavformat/hls_sample_encryption.c
index 26de098dda..c41de3143b 100644
--- a/libavformat/hls_sample_encryption.c
+++ b/libavformat/hls_sample_encryption.c
@@ -374,6 +374,13 @@ static int decrypt_audio_frame(enum AVCodecID codec_id, HLSCryptoContext *crypto
         ret = get_next_sync_frame(codec_id, &ctx, &frame);
         if (ret < 0)
             return ret;
+        if (frame.length < frame.header_length ||
+            frame.length > ctx.buf_end - frame.data) {
+            av_log(NULL, AV_LOG_ERROR,
+                   "Sample-AES: declared frame length %d exceeds packet data\n",
+                   frame.length);
+            return AVERROR_INVALIDDATA;
+        }
         if (frame.length - frame.header_length > 31) {
             ret = decrypt_sync_frame(codec_id, crypto_ctx, &frame);
             if (ret < 0)
-- 
2.52.0


From d7abdc8c8572d50a17bb14e4afa6faab01ffd842 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Tue, 7 Jul 2026 02:45:49 +0200
Subject: [PATCH 078/180] avcodec/vvc/dec: merge the SAO and ALF pixel buffer
 tab loops

Both loops iterate over the same components and compute the same
per-component width and height. The order of tabs within a TabList
does not matter, so build all pixel buffer tabs in a single loop.

(cherry picked from commit df2649ffb128c6846c523a5cb755e8b789009e4e)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/vvc/dec.c | 7 +------
 1 file changed, 1 insertion(+), 6 deletions(-)

diff --git a/libavcodec/vvc/dec.c b/libavcodec/vvc/dec.c
index 561323f123..ebe8e96272 100644
--- a/libavcodec/vvc/dec.c
+++ b/libavcodec/vvc/dec.c
@@ -248,14 +248,9 @@ static void pixel_buffer_nz_tl_init(TabList *l, VVCFrameContext *fc)
     for (int c_idx = 0; c_idx < c_end; c_idx++) {
         const int w = width  >> (sps ? sps->hshift[c_idx] : 0);
         const int h = height >> (sps ? sps->vshift[c_idx] : 0);
+        const int border_pixels = c_idx ? ALF_BORDER_CHROMA : ALF_BORDER_LUMA;
         TL_ADD(sao_pixel_buffer_h[c_idx], (w * 2 * ctu_height) << ps);
         TL_ADD(sao_pixel_buffer_v[c_idx], (h * 2 * ctu_width)  << ps);
-    }
-
-    for (int c_idx = 0; c_idx < c_end; c_idx++) {
-        const int w = width  >> (sps ? sps->hshift[c_idx] : 0);
-        const int h = height >> (sps ? sps->vshift[c_idx] : 0);
-        const int border_pixels = c_idx ? ALF_BORDER_CHROMA : ALF_BORDER_LUMA;
         for (int i = 0; i < 2; i++) {
             TL_ADD(alf_pixel_buffer_h[c_idx][i], (w * border_pixels * ctu_height) << ps);
             TL_ADD(alf_pixel_buffer_v[c_idx][i], h * ALF_PADDING_SIZE * ctu_width);
-- 
2.52.0


From 8ce7d391aa54455abb041c53d5f9cf303f84c260 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Tue, 7 Jul 2026 02:46:20 +0200
Subject: [PATCH 079/180] avcodec/vvc/dec: fix pixel buffer tab leak on chroma
 format change

Fixes: memleak
Fixes: 518575619/clusterfuzz-testcase-minimized-ffmpeg_AV_CODEC_ID_VVC_fuzzer-5161396783611904
Found-by: continuous fuzzing process https://github.com/google/oss-fuzz/tree/master/projects/ffmpeg
(cherry picked from commit a41f5431137029215bc444af9cf5f16ce33e0400)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/vvc/dec.c | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/libavcodec/vvc/dec.c b/libavcodec/vvc/dec.c
index ebe8e96272..926288e07b 100644
--- a/libavcodec/vvc/dec.c
+++ b/libavcodec/vvc/dec.c
@@ -86,6 +86,8 @@ static int tl_create(TabList *l)
 
         for (int i = 0; i < l->nb_tabs; i++) {
             Tab *t = l->tabs + i;
+            if (!t->size)
+                continue;
             *t->tab = l->zero ? av_mallocz(t->size) : av_malloc(t->size);
             if (!*t->tab)
                 return AVERROR(ENOMEM);
@@ -245,9 +247,12 @@ static void pixel_buffer_nz_tl_init(TabList *l, VVCFrameContext *fc)
 
     tl_init(l, 0, changed);
 
-    for (int c_idx = 0; c_idx < c_end; c_idx++) {
-        const int w = width  >> (sps ? sps->hshift[c_idx] : 0);
-        const int h = height >> (sps ? sps->vshift[c_idx] : 0);
+    /* Add size 0 tabs for the components beyond c_end, so tl_free() frees
+     * tabs allocated under a previous, larger chroma format. */
+    for (int c_idx = 0; c_idx < VVC_MAX_SAMPLE_ARRAYS; c_idx++) {
+        const int active = c_idx < c_end;
+        const int w = active ? width  >> (sps ? sps->hshift[c_idx] : 0) : 0;
+        const int h = active ? height >> (sps ? sps->vshift[c_idx] : 0) : 0;
         const int border_pixels = c_idx ? ALF_BORDER_CHROMA : ALF_BORDER_LUMA;
         TL_ADD(sao_pixel_buffer_h[c_idx], (w * 2 * ctu_height) << ps);
         TL_ADD(sao_pixel_buffer_v[c_idx], (h * 2 * ctu_width)  << ps);
-- 
2.52.0


From bf316c15356ee0ae468b4f6d3f7cd957dcfe13cb Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Wed, 1 Jul 2026 06:09:34 +0200
Subject: [PATCH 080/180] avcodec/hevc/ps: avoid signed overflow reading act qp
 offsets

Fixes: 523734452/clusterfuzz-testcase-minimized-ffmpeg_dem_HEVC_fuzzer-5246801451220992
Found-by: continuous fuzzing process https://github.com/google/oss-fuzz/tree/master/projects/ffmpeg
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit c5150ad48b2c6e14b01a4f73b5922f00ca200869)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/hevc/ps.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/libavcodec/hevc/ps.c b/libavcodec/hevc/ps.c
index 8884192375..c303ae4c13 100644
--- a/libavcodec/hevc/ps.c
+++ b/libavcodec/hevc/ps.c
@@ -2020,9 +2020,9 @@ static int pps_scc_extension(GetBitContext *gb, AVCodecContext *avctx,
     pps->pps_curr_pic_ref_enabled_flag = get_bits1(gb);
     if (pps->residual_adaptive_colour_transform_enabled_flag = get_bits1(gb)) {
         pps->pps_slice_act_qp_offsets_present_flag = get_bits1(gb);
-        pps->pps_act_y_qp_offset  = get_se_golomb(gb) - 5;
-        pps->pps_act_cb_qp_offset = get_se_golomb(gb) - 5;
-        pps->pps_act_cr_qp_offset = get_se_golomb(gb) - 3;
+        pps->pps_act_y_qp_offset  = get_se_golomb(gb) - 5U;
+        pps->pps_act_cb_qp_offset = get_se_golomb(gb) - 5U;
+        pps->pps_act_cr_qp_offset = get_se_golomb(gb) - 3U;
 
 #define CHECK_QP_OFFSET(name) (pps->pps_act_ ## name ## _qp_offset <= -12 || \
                                pps->pps_act_ ## name ## _qp_offset >= 12)
-- 
2.52.0


From 89f146e6dbfc61a0a1d89361ce0f2d7dfbab77fb Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Wed, 1 Jul 2026 06:09:34 +0200
Subject: [PATCH 081/180] avcodec/jpeg2000dec: avoid signed overflow in ROI
 shift

Fixes: 522402419/clusterfuzz-testcase-minimized-ffmpeg_AV_CODEC_ID_JPEG2000_DEC_fuzzer-5987463315914752
Found-by: continuous fuzzing process https://github.com/google/oss-fuzz/tree/master/projects/ffmpeg
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit 90a285ca782ebe633ba40ab123b6ff0cde9085a8)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/jpeg2000dec.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libavcodec/jpeg2000dec.c b/libavcodec/jpeg2000dec.c
index c705f04c44..369377e4fc 100644
--- a/libavcodec/jpeg2000dec.c
+++ b/libavcodec/jpeg2000dec.c
@@ -2104,7 +2104,7 @@ static int decode_cblk(const Jpeg2000DecoderContext *s, Jpeg2000CodingStyle *cod
             val &= INT32_MAX;
             /* ROI shift, if necessary */
             if (roi_shift && (((uint32_t)val & ~mask) == 0))
-                val <<= roi_shift;
+                val = (uint32_t)val << roi_shift;
             t1->data[n] = val | sign; /* NOTE: Binary point for reconstruction value is located in 31 - M_b */
         }
     }
-- 
2.52.0


From ac507eac6382f49e89e67748a76c8ba7f86bc52f Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Sun, 5 Jul 2026 22:55:50 +0200
Subject: [PATCH 082/180] avcodec/lzf: pad the decompressed buffer
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Fixes: out of array read
Fixes: oddlist.mov / poc.mov
Found-by: Clouditera Security; Z.ai Security; NSFOCUS
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit fe47696aa1eb3b4f2359b9c4416c529912deee38)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/lzf.c | 12 ++++++++----
 libavcodec/lzf.h |  5 +++++
 2 files changed, 13 insertions(+), 4 deletions(-)

diff --git a/libavcodec/lzf.c b/libavcodec/lzf.c
index 5d6e9925d4..1c38c98982 100644
--- a/libavcodec/lzf.c
+++ b/libavcodec/lzf.c
@@ -32,6 +32,7 @@
 #include "libavutil/mem.h"
 
 #include "bytestream.h"
+#include "defs.h"
 #include "lzf.h"
 
 #define LZF_LITERAL_MAX (1 << 5)
@@ -62,8 +63,8 @@ int ff_lzf_uncompress(GetByteContext *gb, uint8_t **buf, size_t *size, unsigned
 
         if (s < LZF_LITERAL_MAX) {
             s++;
-            if (s > *allocated_size - len) {
-                ret = lzf_realloc(buf, len + s, allocated_size);
+            if (s + AV_INPUT_BUFFER_PADDING_SIZE > *allocated_size - len) {
+                ret = lzf_realloc(buf, len + s + AV_INPUT_BUFFER_PADDING_SIZE, allocated_size);
                 if (ret < 0)
                     return ret;
                 p = *buf + len;
@@ -87,8 +88,8 @@ int ff_lzf_uncompress(GetByteContext *gb, uint8_t **buf, size_t *size, unsigned
             if (off > len)
                 return AVERROR_INVALIDDATA;
 
-            if (l > *allocated_size - len) {
-                ret = lzf_realloc(buf, len + l, allocated_size);
+            if (l + AV_INPUT_BUFFER_PADDING_SIZE > *allocated_size - len) {
+                ret = lzf_realloc(buf, len + l + AV_INPUT_BUFFER_PADDING_SIZE, allocated_size);
                 if (ret < 0)
                     return ret;
                 p = *buf + len;
@@ -101,6 +102,9 @@ int ff_lzf_uncompress(GetByteContext *gb, uint8_t **buf, size_t *size, unsigned
         }
     }
 
+    if (*buf)
+        memset(*buf + len, 0, AV_INPUT_BUFFER_PADDING_SIZE);
+
     *size = len;
 
     return 0;
diff --git a/libavcodec/lzf.h b/libavcodec/lzf.h
index e61ebff727..48f99a452c 100644
--- a/libavcodec/lzf.h
+++ b/libavcodec/lzf.h
@@ -24,6 +24,11 @@
 
 #include "bytestream.h"
 
+/**
+ * Decompress LZF data into *buf, reallocating it as needed.
+ * On success the output is followed by AV_INPUT_BUFFER_PADDING_SIZE
+ * zeroed bytes.
+ */
 int ff_lzf_uncompress(GetByteContext *gb, uint8_t **buf, size_t *size, unsigned *allocated_size);
 
 #endif /* AVCODEC_LZF_H */
-- 
2.52.0


From 85d7bb2280da0a65090af9a2ce2ac529d2548d9c Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Sat, 27 Jun 2026 21:31:23 +0200
Subject: [PATCH 083/180] avfilter/vf_v360: reject dimensions too small for the
 projection

Fixes: out of array read
Fixes: assertion failure
Fixes: mQzloVqnivHQ
Found-by: Anthony Hurtado
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit b3712addc9636f428a751586c903b4b1c7be7fb3)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavfilter/vf_v360.c | 46 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 46 insertions(+)

diff --git a/libavfilter/vf_v360.c b/libavfilter/vf_v360.c
index c502bd5587..6cc3419b07 100644
--- a/libavfilter/vf_v360.c
+++ b/libavfilter/vf_v360.c
@@ -4309,6 +4309,20 @@ static int get_output_dimension(AVFilterContext *ctx, const char *name,
     return 0;
 }
 
+static void projection_min_size(int projection, int *min_w, int *min_h)
+{
+    switch (projection) {
+    case CUBEMAP_3_2:  *min_w = 3; *min_h = 2; break;
+    case CUBEMAP_1_6:  *min_w = 1; *min_h = 6; break;
+    case CUBEMAP_6_1:  *min_w = 6; *min_h = 1; break;
+    case EQUIANGULAR:  *min_w = 5; *min_h = 9; break;
+    case BARREL:       *min_w = 5; *min_h = 2; break;
+    case BARREL_SPLIT: *min_w = 3; *min_h = 4; break;
+    case DUAL_FISHEYE: *min_w = 2; *min_h = 1; break;
+    default:           *min_w = 1; *min_h = 1; break;
+    }
+}
+
 static int config_output(AVFilterLink *outlink)
 {
     AVFilterContext *ctx = outlink->src;
@@ -4489,6 +4503,22 @@ static int config_output(AVFilterLink *outlink)
         return AVERROR(EINVAL);
     }
 
+    {
+        int min_w, min_h;
+        const int pw = s->in_transpose ? AV_CEIL_RSHIFT(h, desc->log2_chroma_h)
+                                       : AV_CEIL_RSHIFT(w, desc->log2_chroma_w);
+        const int ph = s->in_transpose ? AV_CEIL_RSHIFT(w, desc->log2_chroma_w)
+                                       : AV_CEIL_RSHIFT(h, desc->log2_chroma_h);
+
+        projection_min_size(s->in, &min_w, &min_h);
+        if (pw < min_w || ph < min_h) {
+            av_log(ctx, AV_LOG_ERROR,
+                   "Input %dx%d is too small for the input projection "
+                   "(requires at least %dx%d per plane).\n", pw, ph, min_w, min_h);
+            return AVERROR(EINVAL);
+        }
+    }
+
     switch (s->in) {
     case EQUIRECTANGULAR:
         s->in_transform = xyz_to_equirect;
@@ -4876,6 +4906,22 @@ static int config_output(AVFilterLink *outlink)
 
     set_dimensions(s->pr_width, s->pr_height, w, h, desc);
 
+    {
+        int min_w, min_h;
+        const int pw = s->out_transpose ? AV_CEIL_RSHIFT(h, desc->log2_chroma_h)
+                                        : AV_CEIL_RSHIFT(w, desc->log2_chroma_w);
+        const int ph = s->out_transpose ? AV_CEIL_RSHIFT(w, desc->log2_chroma_w)
+                                        : AV_CEIL_RSHIFT(h, desc->log2_chroma_h);
+
+        projection_min_size(s->out, &min_w, &min_h);
+        if (pw < min_w || ph < min_h) {
+            av_log(ctx, AV_LOG_ERROR,
+                   "Output %dx%d is too small for the output projection "
+                   "(requires at least %dx%d per plane).\n", pw, ph, min_w, min_h);
+            return AVERROR(EINVAL);
+        }
+    }
+
     switch (s->out_stereo) {
     case STEREO_2D:
         out_offset_w = out_offset_h = 0;
-- 
2.52.0


From e6bcc30dcdcd75590094b3f7095829159b85a60f Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Sat, 27 Jun 2026 21:31:23 +0200
Subject: [PATCH 084/180] avfilter/vf_v360: keep remap source coordinates in
 bounds

Two runtime paths could compute out-of-range source coordinates for
degenerate projection geometry, causing heap-buffer-overflow reads

Fixes: out of array read
Fixes: assertion failure
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit a73d648f6e3e5224dfbee73005a59c2dc161445f)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavfilter/vf_v360.c | 28 +++++++++++++++++++++-------
 1 file changed, 21 insertions(+), 7 deletions(-)

diff --git a/libavfilter/vf_v360.c b/libavfilter/vf_v360.c
index 6cc3419b07..367c217a79 100644
--- a/libavfilter/vf_v360.c
+++ b/libavfilter/vf_v360.c
@@ -806,7 +806,7 @@ static inline int ereflectx(int x, int y, int w, int h)
 static inline int reflectx(int x, int y, int w, int h)
 {
     if (y < 0 || y >= h)
-        return w - 1 - x;
+        return av_clip(w - 1 - x, 0, w - 1);
 
     return mod(x, w);
 }
@@ -1867,9 +1867,14 @@ static int stereographic_to_xyz(const V360Context *s,
     const float theta = atanf(r) * 2.f;
     const float sin_theta = sinf(theta);
 
-    vec[0] = x / r * sin_theta;
-    vec[1] = y / r * sin_theta;
-    vec[2] = cosf(theta);
+    if (r > 0.f) {
+        vec[0] = x / r * sin_theta;
+        vec[1] = y / r * sin_theta;
+        vec[2] = cosf(theta);
+    } else {
+        vec[0] = vec[1] = 0.f;
+        vec[2] = 1.f;
+    }
 
     return 1;
 }
@@ -1971,9 +1976,14 @@ static int equisolid_to_xyz(const V360Context *s,
     const float theta = asinf(r) * 2.f;
     const float sin_theta = sinf(theta);
 
-    vec[0] = x / r * sin_theta;
-    vec[1] = y / r * sin_theta;
-    vec[2] = cosf(theta);
+    if (r > 0.f) {
+        vec[0] = x / r * sin_theta;
+        vec[1] = y / r * sin_theta;
+        vec[2] = cosf(theta);
+    } else {
+        vec[0] = vec[1] = 0.f;
+        vec[2] = 1.f;
+    }
 
     return 1;
 }
@@ -4266,6 +4276,10 @@ static int v360_slice(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
                     out_mask = s->out_transform(s, j, i, height, width, vec);
                 else
                     out_mask = s->out_transform(s, i, j, width, height, vec);
+                if (!isfinite(vec[0]) || !isfinite(vec[1]) || !isfinite(vec[2])) {
+                    vec[0] = vec[1] = 0.f;
+                    vec[2] = 1.f;
+                }
                 offset_vector(vec, s->h_offset, s->v_offset);
                 normalize_vector(vec);
                 av_assert1(!isnan(vec[0]) && !isnan(vec[1]) && !isnan(vec[2]));
-- 
2.52.0


From 27f503c90ec981765ade6ef8727e547ced154297 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Sun, 5 Jul 2026 03:22:13 +0200
Subject: [PATCH 085/180] avformat/spdif: fix reading past the buffer when
 probing AAC in IEC 61937

Fixes: read of uninitialized memory
Fixes: 528525234/clusterfuzz-testcase-minimized-ffmpeg_dem_W64_fuzzer-6429204661731328
Found-by: continuous fuzzing process https://github.com/google/oss-fuzz/tree/master/projects/ffmpeg
(cherry picked from commit 15bbf3a21d11847de21aa429ec22acf9424953ec)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavformat/spdifdec.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/libavformat/spdifdec.c b/libavformat/spdifdec.c
index dcfe471f45..fe45deb157 100644
--- a/libavformat/spdifdec.c
+++ b/libavformat/spdifdec.c
@@ -142,7 +142,10 @@ int ff_spdif_probe(const uint8_t *p_buf, int buf_size, enum AVCodecID *codec)
             } else
                 consecutive_codes = 0;
 
-            if (buf + 4 + AV_AAC_ADTS_HEADER_SIZE > p_buf + buf_size)
+            /* spdif_get_offset_and_codec() parses AV_AAC_ADTS_HEADER_SIZE
+             * bytes starting at buf[5] (the payload after the 4 byte sync and
+             * the Pc/Pd burst header), so that many bytes must be available. */
+            if (buf + 5 + AV_AAC_ADTS_HEADER_SIZE > p_buf + buf_size)
                 break;
 
             /* continue probing to find more sync codes */
-- 
2.52.0


From e18d1b7972c50354a197e45132dcf7a3fa89de27 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Wed, 1 Jul 2026 06:18:07 +0200
Subject: [PATCH 086/180] avcodec/tdsc: reject out-of-frame cursor position

Fixes: signed integer overflow
Fixes: 519649309/clusterfuzz-testcase-minimized-ffmpeg_AV_CODEC_ID_TDSC_fuzzer-6322382028734464
Found-by: continuous fuzzing process https://github.com/google/oss-fuzz/tree/master/projects/ffmpeg
(cherry picked from commit 8de8405796df0c11c52772dd14bf6ed552d25c07)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/tdsc.c | 19 +++++++++++--------
 1 file changed, 11 insertions(+), 8 deletions(-)

diff --git a/libavcodec/tdsc.c b/libavcodec/tdsc.c
index e8d37a4e3f..ca9dd0f0a6 100644
--- a/libavcodec/tdsc.c
+++ b/libavcodec/tdsc.c
@@ -146,8 +146,7 @@ static void tdsc_paint_cursor(AVCodecContext *avctx, uint8_t *dst, int stride)
 {
     TDSCContext *ctx = avctx->priv_data;
     const uint8_t *cursor = ctx->cursor;
-    int x = ctx->cursor_x - ctx->cursor_hot_x;
-    int y = ctx->cursor_y - ctx->cursor_hot_y;
+    int x, y;
     int w = ctx->cursor_w;
     int h = ctx->cursor_h;
     int i, j;
@@ -155,6 +154,16 @@ static void tdsc_paint_cursor(AVCodecContext *avctx, uint8_t *dst, int stride)
     if (!ctx->cursor)
         return;
 
+    /* A cursor position outside the frame is invalid; skip drawing it.
+     * cursor_x/y come straight from the bitstream, so bound them before
+     * the (16 bit) hot spot shift to avoid overflowing the clip math. */
+    if ((unsigned)ctx->cursor_x >= ctx->width ||
+        (unsigned)ctx->cursor_y >= ctx->height)
+        return;
+
+    x = ctx->cursor_x - ctx->cursor_hot_x;
+    y = ctx->cursor_y - ctx->cursor_hot_y;
+
     if (x + w > ctx->width)
         w = ctx->width - x;
     if (y + h > ctx->height)
@@ -201,12 +210,6 @@ static int tdsc_load_cursor(AVCodecContext *avctx)
     ctx->cursor_stride = FFALIGN(ctx->cursor_w, 32) * 4;
     cursor_fmt = bytestream2_get_le32(&ctx->gbc);
 
-    if (ctx->cursor_x >= avctx->width || ctx->cursor_y >= avctx->height) {
-        av_log(avctx, AV_LOG_ERROR,
-               "Invalid cursor position (%d.%d outside %dx%d).\n",
-               ctx->cursor_x, ctx->cursor_y, avctx->width, avctx->height);
-        return AVERROR_INVALIDDATA;
-    }
     if (ctx->cursor_w < 1 || ctx->cursor_w > 256 ||
         ctx->cursor_h < 1 || ctx->cursor_h > 256) {
         av_log(avctx, AV_LOG_ERROR,
-- 
2.52.0


From 7bd23e2cb221f014eb6b2ae1c5bb69bb1c259ca0 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Sun, 5 Jul 2026 01:43:49 +0200
Subject: [PATCH 087/180] avcodec/mjpegdec: reject flipping of bayer images

Fixes: assertion failure
Fixes: 523522308/clusterfuzz-testcase-minimized-ffmpeg_AV_CODEC_ID_TIFF_DEC_fuzzer-4771843818192896
Found-by: continuous fuzzing process https://github.com/google/oss-fuzz/tree/master/projects/ffmpeg
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit 40ce1513c6a883bc2b96d33d968013b89dfe76af)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/mjpegdec.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libavcodec/mjpegdec.c b/libavcodec/mjpegdec.c
index 0853d76372..d8430a260b 100644
--- a/libavcodec/mjpegdec.c
+++ b/libavcodec/mjpegdec.c
@@ -2750,7 +2750,7 @@ the_end:
             }
         }
     }
-    if (s->flipped && !s->rgb) {
+    if (s->flipped && !s->rgb && !s->bayer) {
         ret = av_pix_fmt_get_chroma_sub_sample(avctx->pix_fmt, &hshift, &vshift);
         if (ret)
             return ret;
-- 
2.52.0


From a303fb045931897df290d4d3547ce779fa14c628 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Wed, 8 Jul 2026 02:11:44 +0200
Subject: [PATCH 088/180] avcodec/mjpegdec: reject redundant scans of a
 sequential image

Fixes: Timeout
Fixes: 527085539/clusterfuzz-testcase-minimized-ffmpeg_AV_CODEC_ID_AMV_DEC_fuzzer-6502692927897600
Fixes: 523663551/clusterfuzz-testcase-minimized-ffmpeg_AV_CODEC_ID_MJPEG_DEC_fuzzer-5493410811936768
Found-by: continuous fuzzing process https://github.com/google/oss-fuzz/tree/master/projects/ffmpeg
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit a7c116b536d0d64c96dd15b096aaab1a76cce3b0)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/mjpegdec.c | 11 +++++++++++
 libavcodec/mjpegdec.h |  1 +
 2 files changed, 12 insertions(+)

diff --git a/libavcodec/mjpegdec.c b/libavcodec/mjpegdec.c
index d8430a260b..3dc8662abb 100644
--- a/libavcodec/mjpegdec.c
+++ b/libavcodec/mjpegdec.c
@@ -386,6 +386,7 @@ int ff_mjpeg_decode_sof(MJpegDecodeContext *s)
     }
 
     s->nb_components = nb_components;
+    s->nb_seq_component_scans = 0;
     s->h_max         = 1;
     s->v_max         = 1;
     for (i = 0; i < nb_components; i++) {
@@ -1770,6 +1771,16 @@ int ff_mjpeg_decode_sos(MJpegDecodeContext *s)
     if (s->mjpb_skiptosod)
         bytestream2_skip(&s->gB, s->mjpb_skiptosod);
 
+    if (!s->progressive && !s->lossless &&
+        s->avctx->codec_id != AV_CODEC_ID_MXPEG) {
+        s->nb_seq_component_scans += s->nb_components_sos;
+        if (s->nb_seq_component_scans > s->nb_components) {
+            av_log(s->avctx, AV_LOG_ERROR,
+                   "too many scans for a sequential image\n");
+            return AVERROR_INVALIDDATA;
+        }
+    }
+
     if (s->avctx->hwaccel) {
         const uint8_t *buf_ptr;
         size_t buf_size;
diff --git a/libavcodec/mjpegdec.h b/libavcodec/mjpegdec.h
index 4733db3603..5ba96eaa3e 100644
--- a/libavcodec/mjpegdec.h
+++ b/libavcodec/mjpegdec.h
@@ -117,6 +117,7 @@ typedef struct MJpegDecodeContext {
     AVFrame *picture; /* picture structure */
     AVFrame *picture_ptr; /* pointer to picture structure */
     int got_picture;                                ///< we found a SOF and picture is valid, too.
+    int nb_seq_component_scans;                     ///< component scans decoded since the SOF (sequential images: <= nb_components)
     int linesize[MAX_COMPONENTS];                   ///< linesize << interlaced
     DECLARE_ALIGNED(32, int16_t, block)[64];
     int16_t (*blocks[MAX_COMPONENTS])[64]; ///< intermediate sums (progressive mode)
-- 
2.52.0


From 60892bbbb90b0f54e9899559960adca7bc3faa93 Mon Sep 17 00:00:00 2001
From: Frank Plowman <[email protected]>
Date: Sat, 11 Jul 2026 15:23:17 +0100
Subject: [PATCH 089/180] lavc/hevc: Always generate missing refs for foll
 pictures

Since bc1a3bfd2cbc01ffa386312662af8a014890d861, missing reference
pictures are not replaced with generated pictures unless `-flags
show_corrupt` is used.  For `ST_FOLL`/`LT_FOLL` reference pictures,
however, generation of missing references is required by the spec per
8.3.3.  We should not require the `show_corrupt` flag to be used in
order to be spec-conformant, so this patch removes the `show_corrupt`
behaviour added in bc1a3bfd2cbc01ffa386312662af8a014890d861 for
`ST_FOLL`/`LT_FOLL` references and instead unconditionally generates
reference pictures in these sets when unavailable

Fixes: NUT_A_ericsson_5
Fixes: RPS_D_ericsson_6
(cherry picked from commit 265d39e551956d911a0c1c52bff5186a6bae660e)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/hevc/refs.c | 11 ++++++-----
 tests/fate/hevc.mak    |  2 +-
 2 files changed, 7 insertions(+), 6 deletions(-)

diff --git a/libavcodec/hevc/refs.c b/libavcodec/hevc/refs.c
index 2acffd72d9..005f416bf3 100644
--- a/libavcodec/hevc/refs.c
+++ b/libavcodec/hevc/refs.c
@@ -497,15 +497,16 @@ static HEVCFrame *generate_missing_ref(HEVCContext *s, HEVCLayerContext *l, int
 
 /* add a reference with the given poc to the list and mark it as used in DPB */
 static int add_candidate_ref(HEVCContext *s, HEVCLayerContext *l,
-                             RefPicList *list,
+                             RefPicList *rps, int list_idx,
                              int poc, int ref_flag, uint8_t use_msb)
 {
+    RefPicList *list = &rps[list_idx];
     HEVCFrame *ref = find_ref_idx(s, l, poc, use_msb);
 
     if (ref == s->cur_frame || list->nb_refs >= HEVC_MAX_REFS)
         return AVERROR_INVALIDDATA;
 
-    if (!IS_IRAP(s)) {
+    if (!IS_IRAP(s) && list_idx != ST_FOLL && list_idx != LT_FOLL) {
         int ref_corrupt = !ref || ref->flags & (HEVC_FRAME_FLAG_CORRUPT |
                                                 HEVC_FRAME_FLAG_UNAVAILABLE);
         int recovering = HEVC_IS_RECOVERING(s);
@@ -570,7 +571,7 @@ int ff_hevc_frame_rps(HEVCContext *s, HEVCLayerContext *l)
         else
             list = ST_CURR_AFT;
 
-        ret = add_candidate_ref(s, l, &rps[list], poc,
+        ret = add_candidate_ref(s, l, rps, list, poc,
                                 HEVC_FRAME_FLAG_SHORT_REF, 1);
         if (ret < 0)
             goto fail;
@@ -581,7 +582,7 @@ int ff_hevc_frame_rps(HEVCContext *s, HEVCLayerContext *l)
         int poc  = long_rps->poc[i];
         int list = long_rps->used[i] ? LT_CURR : LT_FOLL;
 
-        ret = add_candidate_ref(s, l, &rps[list], poc,
+        ret = add_candidate_ref(s, l, rps, list, poc,
                                 HEVC_FRAME_FLAG_LONG_REF, long_rps->poc_msb_present[i]);
         if (ret < 0)
             goto fail;
@@ -598,7 +599,7 @@ inter_layer:
          * always 1, so only RefPicSetInterLayer0 can ever contain a frame. */
         if (l0->cur_frame) {
             // inter-layer refs are treated as short-term here, cf. F.8.1.6
-            ret = add_candidate_ref(s, l0, &rps[INTER_LAYER0], l0->cur_frame->poc,
+            ret = add_candidate_ref(s, l0, rps, INTER_LAYER0, l0->cur_frame->poc,
                                     HEVC_FRAME_FLAG_SHORT_REF, 1);
             if (ret < 0)
                 goto fail;
diff --git a/tests/fate/hevc.mak b/tests/fate/hevc.mak
index 7ce9ff403b..4fe25aa985 100644
--- a/tests/fate/hevc.mak
+++ b/tests/fate/hevc.mak
@@ -207,7 +207,7 @@ $(HEVC_TESTS_444_8BIT): SCALE_OPTS := -pix_fmt yuv444p
 $(HEVC_TESTS_10BIT): SCALE_OPTS := -pix_fmt yuv420p10le -vf scale
 $(HEVC_TESTS_422_10BIT) $(HEVC_TESTS_422_10BIN): SCALE_OPTS := -pix_fmt yuv422p10le -vf scale
 $(HEVC_TESTS_444_12BIT): SCALE_OPTS := -pix_fmt yuv444p12le -vf scale
-fate-hevc-conformance-%: CMD = framecrc -flags output_corrupt -i $(TARGET_SAMPLES)/hevc-conformance/$(subst fate-hevc-conformance-,,$(@)).bit $(SCALE_OPTS)
+fate-hevc-conformance-%: CMD = framecrc -i $(TARGET_SAMPLES)/hevc-conformance/$(subst fate-hevc-conformance-,,$(@)).bit $(SCALE_OPTS)
 $(HEVC_TESTS_422_10BIN): CMD = framecrc -i $(TARGET_SAMPLES)/hevc-conformance/$(subst fate-hevc-conformance-,,$(@)).bin $(SCALE_OPTS)
 $(HEVC_TESTS_MULTIVIEW): CMD = framecrc -i $(TARGET_SAMPLES)/hevc-conformance/$(subst fate-hevc-conformance-,,$(@)).bit \
 	-pix_fmt yuv420p -map "0:view:0" -map "0:view:1" -vf setpts=N:strip_fps=1
-- 
2.52.0


From 713ad3f3b174bf242b2cb4c94f7a776e468f11f1 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Sun, 5 Jul 2026 01:38:19 +0200
Subject: [PATCH 090/180] avcodec/bsf/dts2pts: chain same-gop nodes instead of
 collecting into a fixed array

hevc_queue_frame() collected all tree nodes of the current gop into a
fixed nodes[HEVC_MAX_DPB_SIZE * 2] array and asserted the count stayed
within it. For a crafted HEVC stream the tree can hold slightly more
same-gop nodes than that estimate (observed 34 vs 32), tripping the
av_assert0 and aborting. Link the matching nodes into a list through a
next pointer in the node instead; this has no size limit and needs
neither extra passes nor allocation.

Fixes: assertion failure
Fixes: 519466146/clusterfuzz-testcase-minimized-ffmpeg_BSF_DTS2PTS_fuzzer-5238235193475072
Found-by: continuous fuzzing process https://github.com/google/oss-fuzz/tree/master/projects/ffmpeg

Co-Authored-By: Fable-5
(cherry picked from commit 501d8eb62dd4f91dd19fb874f555db7ca3fde317)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/bsf/dts2pts.c | 40 +++++++++++++++++++++++-----------------
 1 file changed, 23 insertions(+), 17 deletions(-)

diff --git a/libavcodec/bsf/dts2pts.c b/libavcodec/bsf/dts2pts.c
index b620c3f837..de44eaaedd 100644
--- a/libavcodec/bsf/dts2pts.c
+++ b/libavcodec/bsf/dts2pts.c
@@ -46,6 +46,7 @@ typedef struct DTS2PTSNode {
     int64_t duration;
     int          poc;
     int          gop;
+    struct DTS2PTSNode *next; // valid only during same-gop re-keying
 } DTS2PTSNode;
 
 typedef struct DTS2PTSFrame {
@@ -415,9 +416,7 @@ static int hevc_init_nb_frame(AVBSFContext *ctx, int poc)
 
 typedef struct DTS2PTSCollect {
     int gop;
-    DTS2PTSNode **out;
-    int count;
-    int max;
+    DTS2PTSNode *head, *tail;
 } DTS2PTSCollect;
 
 static int collect_same_gop(void *opaque, void *elem)
@@ -425,9 +424,12 @@ static int collect_same_gop(void *opaque, void *elem)
     DTS2PTSCollect *c = opaque;
     DTS2PTSNode *node = elem;
     if (node->gop == c->gop) {
-        if (c->count < c->max)
-            c->out[c->count] = node;
-        c->count++;
+        if (c->tail)
+            c->tail->next = node;
+        else
+            c->head = node;
+        c->tail = node;
+        node->next = NULL;
     }
     return 0;
 }
@@ -454,22 +456,26 @@ static int hevc_queue_frame(AVBSFContext *ctx, AVPacket *pkt, int poc, bool *que
 
     if (poc < s->nb_frame && hevc->gop == s->gop) {
         int dec = s->nb_frame - poc;
-        DTS2PTSNode *nodes[HEVC_MAX_DPB_SIZE * 2];
-        DTS2PTSCollect c = { s->gop, nodes, 0, FF_ARRAY_ELEMS(nodes) };
+        DTS2PTSCollect c = { s->gop, NULL, NULL };
 
         s->nb_frame -= dec;
 
+        // Crafted streams can exceed any DPB-based estimate of the node count,
+        // so chain the matching nodes through their next pointers instead of
+        // collecting them into a fixed size array. The chain is in ascending
+        // poc order; processing it in this order keeps the new keys collision
+        // free as any potential collision partner is re-keyed first.
         av_tree_enumerate(s->root, &c, NULL, collect_same_gop);
-        av_assert0(c.count <= c.max);
-        for (int i = 0; i < c.count; i++) {
+        while (c.head) {
             struct AVTreeNode *tnode = NULL;
-            DTS2PTSNode *r;
-            av_tree_insert(&s->root, nodes[i], cmp_insert, &tnode);
-            nodes[i]->poc -= dec;
-            r = av_tree_insert(&s->root, nodes[i], cmp_insert, &tnode);
-            if (r && r != nodes[i]) {
-                *r = *nodes[i];
-                av_refstruct_unref(&nodes[i]);
+            DTS2PTSNode *node = c.head, *r;
+            c.head = node->next;
+            av_tree_insert(&s->root, node, cmp_insert, &tnode);
+            node->poc -= dec;
+            r = av_tree_insert(&s->root, node, cmp_insert, &tnode);
+            if (r && r != node) {
+                *r = *node;
+                av_refstruct_unref(&node);
                 av_free(tnode);
             }
         }
-- 
2.52.0


From 1f37509a065e87957ed4c62aaf28c3e61eed509f Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Wed, 8 Jul 2026 17:24:49 +0200
Subject: [PATCH 091/180] avcodec/bsf/dts2pts: fix removal of 2nd field nodes

alloc_and_insert_node() inserts the nodes for 2nd fields with
duration / poc_diff added to the timestamp, but the removal loop
compared all nodes of a frame against the unadjusted first timestamp,
so 2nd field nodes never matched and stayed in the tree until close.
Advance the compared timestamp the same way the insertion does.

Fixes: tree nodes leaking on every field coded frame

Co-Authored-By: Fable-5
(cherry picked from commit 4133db39b2bd1491e41457e819ed3ca96ef555ff)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/bsf/dts2pts.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/libavcodec/bsf/dts2pts.c b/libavcodec/bsf/dts2pts.c
index de44eaaedd..b0b6b0ec23 100644
--- a/libavcodec/bsf/dts2pts.c
+++ b/libavcodec/bsf/dts2pts.c
@@ -680,10 +680,14 @@ static int dts2pts_filter(AVBSFContext *ctx, AVPacket *out)
         if (!s->eof) {
             // Remove the found entry from the tree
             DTS2PTSFrame dup = (DTS2PTSFrame) { NULL, frame.poc + 1, frame.poc_diff, frame.gop };
+            int64_t dts = out->pts;
             for (; dup.poc_diff > 0; dup.poc++, dup.poc_diff--) {
                 struct AVTreeNode *node = NULL;
-                if (!poc_node || poc_node->dts != out->pts)
+                if (!poc_node || poc_node->dts != dts)
                     continue;
+                // 2nd field nodes were inserted with this offset added
+                if (dts != AV_NOPTS_VALUE)
+                    dts += poc_node->duration / frame.poc_diff;
                 av_tree_insert(&s->root, poc_node, cmp_insert, &node);
                 av_refstruct_unref(&poc_node);
                 av_free(node);
-- 
2.52.0


From 76398b2b4f9a4bc42429b8a44cf37579bb261d79 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Wed, 8 Jul 2026 16:14:48 +0200
Subject: [PATCH 092/180] avcodec/bsf/dts2pts: evict unconsumable nodes from
 the poc tree

Each pending packet consumes up to poc_diff tree nodes when it is
output, so the packets in the FIFO can consume at most nb_pending nodes
in total. Frames whose tree lookup misses on output leave their nodes
behind, and damaged or crafted streams can make that happen
indefinitely, growing the tree without limit.

Track the node count and insertion order, keep the leftovers of up to
MAX_DAMAGED_FRAMES frames and beyond that evict the nodes unconsumed
the longest. Timestamps of valid frames are unaffected unless more
frames than that are damaged. No eviction is done at EOF, where nodes
are deliberately kept to regenerate timestamps from.

Of all h264/hevc conformance samples only MR3_TANDBERG_B.264 triggers
evictions, and no sample changes output.

Fixes: unbounded memory growth with damaged streams

Co-Authored-By: Fable-5
(cherry picked from commit dd941af8ac9c97017b335832714457fe9dd7f6be)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/bsf/dts2pts.c | 51 +++++++++++++++++++++++++++++++++++++---
 1 file changed, 48 insertions(+), 3 deletions(-)

diff --git a/libavcodec/bsf/dts2pts.c b/libavcodec/bsf/dts2pts.c
index b0b6b0ec23..03e7c39983 100644
--- a/libavcodec/bsf/dts2pts.c
+++ b/libavcodec/bsf/dts2pts.c
@@ -41,11 +41,17 @@
 #include "libavcodec/h264_ps.h"
 #include "libavcodec/hevc/ps.h"
 
+// Damaged frames leave their up to 2 timestamp nodes behind unconsumed.
+// This many damaged frames are tolerated before the oldest leftovers are
+// evicted; no timestamp of a valid frame is lost below this.
+#define MAX_DAMAGED_FRAMES 32
+
 typedef struct DTS2PTSNode {
     int64_t      dts;
     int64_t duration;
     int          poc;
     int          gop;
+    int64_t   serial; // insertion order, evicting the stalest node first
     struct DTS2PTSNode *next; // valid only during same-gop re-keying
 } DTS2PTSNode;
 
@@ -90,6 +96,9 @@ typedef struct DTS2PTSContext {
         DTS2PTSHEVCContext hevc;
     } u;
 
+    int nb_nodes;
+    int nb_pending;
+    int64_t serial;
     int nb_frame;
     int gop;
     int eof;
@@ -129,11 +138,21 @@ static int free_node(void *opaque, void *elem)
     return 0;
 }
 
+static int find_stalest(void *opaque, void *elem)
+{
+    DTS2PTSNode **stalest = opaque;
+    DTS2PTSNode *node = elem;
+    if (!*stalest || node->serial < (*stalest)->serial)
+        *stalest = node;
+    return 0;
+}
+
 // Shared functions
 static int alloc_and_insert_node(AVBSFContext *ctx, int64_t ts, int64_t duration,
                                  int poc, int poc_diff, int gop)
 {
     DTS2PTSContext *s = ctx->priv_data;
+
     for (int i = 0; i < poc_diff; i++) {
         struct AVTreeNode *node = av_tree_node_alloc();
         DTS2PTSNode *poc_node, *ret;
@@ -146,13 +165,14 @@ static int alloc_and_insert_node(AVBSFContext *ctx, int64_t ts, int64_t duration
         }
         if (i && ts != AV_NOPTS_VALUE)
             ts += duration / poc_diff;
-        *poc_node = (DTS2PTSNode) { ts, duration, poc++, gop };
+        *poc_node = (DTS2PTSNode) { ts, duration, poc++, gop, s->serial++ };
         ret = av_tree_insert(&s->root, poc_node, cmp_insert, &node);
         if (ret && ret != poc_node) {
             *ret = *poc_node;
             av_refstruct_unref(&poc_node);
             av_free(node);
-        }
+        } else
+            s->nb_nodes++;
     }
     return 0;
 }
@@ -230,6 +250,7 @@ static int h264_queue_frame(AVBSFContext *ctx, AVPacket *pkt, int poc, int *queu
     frame = (DTS2PTSFrame) { pkt, poc, poc_diff, s->gop };
     ret = av_fifo_write(s->fifo, &frame, 1);
     av_assert2(ret >= 0);
+    s->nb_pending += poc_diff;
     *queued = 1;
 
     return 0;
@@ -477,6 +498,7 @@ static int hevc_queue_frame(AVBSFContext *ctx, AVPacket *pkt, int poc, bool *que
                 *r = *node;
                 av_refstruct_unref(&node);
                 av_free(tnode);
+                s->nb_nodes--;
             }
         }
     }
@@ -498,6 +520,7 @@ static int hevc_queue_frame(AVBSFContext *ctx, AVPacket *pkt, int poc, bool *que
     ret = av_fifo_write(s->fifo, &frame, 1);
     if (ret < 0)
         return ret;
+    s->nb_pending += frame.poc_diff;
 
     *queued = true;
 
@@ -665,6 +688,7 @@ static int dts2pts_filter(AVBSFContext *ctx, AVPacket *out)
     // Fetch a packet from the FIFO
     ret = av_fifo_read(s->fifo, &frame, 1);
     av_assert2(ret >= 0);
+    s->nb_pending -= frame.poc_diff;
     av_packet_move_ref(out, frame.pkt);
     av_packet_free(&frame.pkt);
 
@@ -691,6 +715,7 @@ static int dts2pts_filter(AVBSFContext *ctx, AVPacket *out)
                 av_tree_insert(&s->root, poc_node, cmp_insert, &node);
                 av_refstruct_unref(&poc_node);
                 av_free(node);
+                s->nb_nodes--;
                 poc_node = av_tree_find(s->root, &dup, cmp_find, NULL);
             }
         }
@@ -715,6 +740,24 @@ static int dts2pts_filter(AVBSFContext *ctx, AVPacket *out)
             av_log(ctx, AV_LOG_WARNING, "No timestamp for POC %d in tree\n", frame.poc);
     } else
         av_log(ctx, AV_LOG_WARNING, "No timestamp for POC %d in tree\n", frame.poc);
+
+    // The pending packets consume nb_pending nodes; frames whose lookup above
+    // missed leave nodes behind which nothing consumes anymore. Keep the
+    // leftovers of up to MAX_DAMAGED_FRAMES frames, then evict the nodes
+    // unconsumed the longest.
+    // At EOF nodes are deliberately kept to regenerate timestamps from.
+    while (!s->eof && s->nb_nodes > s->nb_pending + 2 * MAX_DAMAGED_FRAMES) {
+        DTS2PTSNode *stale = NULL;
+        struct AVTreeNode *tnode = NULL;
+        av_tree_enumerate(s->root, &stale, NULL, find_stalest);
+        av_log(ctx, AV_LOG_WARNING, "Evicting unconsumed POC %d, GOP %d\n",
+               stale->poc, stale->gop);
+        av_tree_insert(&s->root, stale, cmp_insert, &tnode);
+        av_refstruct_unref(&stale);
+        av_free(tnode);
+        s->nb_nodes--;
+    }
+
     av_log(ctx, AV_LOG_DEBUG, "Returning frame for POC %d, GOP %d, dts %"PRId64", pts %"PRId64"\n",
            frame.poc, frame.gop, out->dts, out->pts);
 
@@ -736,7 +779,9 @@ static void dts2pts_flush(AVBSFContext *ctx)
 
     av_tree_enumerate(s->root, NULL, NULL, free_node);
     av_tree_destroy(s->root);
-    s->root = NULL;
+    s->root       = NULL;
+    s->nb_nodes   = 0;
+    s->nb_pending = 0;
 
     ff_cbs_fragment_reset(&s->au);
     if (s->cbc)
-- 
2.52.0


From 39809968eb483d00f3c2614ac3d56e77821258e9 Mon Sep 17 00:00:00 2001
From: Jun Zhao <[email protected]>
Date: Sat, 11 Jul 2026 11:26:20 +0800
Subject: [PATCH 093/180] avformat/utils: fix ff_mkdir_p() swallowing
 intermediate mkdir errors

When creating nested directories (e.g. /a/b/c), a genuine mkdir()
failure for an intermediate component was overwritten by later
attempts, making the original failure harder to diagnose.

Stop immediately on intermediate errors other than EEXIST, preserving
errno for the caller. Existing path components remain non-fatal, as
required by mkdir -p semantics. Add a regression test for creating a
child below an existing parent directory.

Signed-off-by: Jun Zhao <[email protected]>
(cherry picked from commit a6ed750664d6d8760bcee88f8aacec2e7d1b3ab7)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavformat/Makefile         |  1 +
 libavformat/tests/.gitignore |  1 +
 libavformat/tests/mkdir.c    | 66 ++++++++++++++++++++++++++++++++++++
 libavformat/utils.c          |  6 ++++
 tests/fate/libavformat.mak   |  5 +++
 5 files changed, 79 insertions(+)
 create mode 100644 libavformat/tests/mkdir.c

diff --git a/libavformat/Makefile b/libavformat/Makefile
index 752436cf5f..e76dc35c31 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -778,6 +778,7 @@ SKIPHEADERS-$(CONFIG_FFRTMPCRYPT_PROTOCOL) += rtmpdh.h
 SKIPHEADERS-$(CONFIG_NETWORK)            += network.h rtsp.h
 
 TESTPROGS = id3v2                                                       \
+            mkdir                                                       \
             seek                                                        \
             url                                                         \
             seek_utils
diff --git a/libavformat/tests/.gitignore b/libavformat/tests/.gitignore
index f9aac1f9e0..951ccf0f37 100644
--- a/libavformat/tests/.gitignore
+++ b/libavformat/tests/.gitignore
@@ -1,6 +1,7 @@
 /id3v2
 /fifo_muxer
 /imf
+/mkdir
 /movenc
 /noproxy
 /rtmpdh
diff --git a/libavformat/tests/mkdir.c b/libavformat/tests/mkdir.c
new file mode 100644
index 0000000000..42e5c0562d
--- /dev/null
+++ b/libavformat/tests/mkdir.c
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2026 Jun Zhao
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include <errno.h>
+#include <stdio.h>
+
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "libavutil/random_seed.h"
+
+#include "libavformat/internal.h"
+#include "libavformat/os_support.h"
+
+int main(void)
+{
+    char parent[64];
+    char child[80];
+
+    snprintf(parent, sizeof(parent), "ff-mkdir-test-%08x", av_get_random_seed());
+    snprintf(child, sizeof(child), "%s/child", parent);
+
+    if (mkdir(parent, 0755) < 0) {
+        perror("mkdir parent");
+        return 1;
+    }
+
+    if (ff_mkdir_p(child) < 0) {
+        perror("ff_mkdir_p");
+        rmdir(parent);
+        return 1;
+    }
+
+    if (rmdir(child) < 0) {
+        perror("rmdir child");
+        rmdir(parent);
+        return 1;
+    }
+
+    if (rmdir(parent) < 0) {
+        perror("rmdir parent");
+        return 1;
+    }
+
+    return 0;
+}
diff --git a/libavformat/utils.c b/libavformat/utils.c
index c64ff27418..cb0ae7444e 100644
--- a/libavformat/utils.c
+++ b/libavformat/utils.c
@@ -443,6 +443,12 @@ int ff_mkdir_p(const char *path)
             tmp_ch = *pos;
             *pos = '\0';
             ret = mkdir(temp, 0755);
+            if (ret < 0 && errno != EEXIST) {
+                int err = errno;
+                av_free(temp);
+                errno = err;
+                return ret;
+            }
             *pos = tmp_ch;
         }
     }
diff --git a/tests/fate/libavformat.mak b/tests/fate/libavformat.mak
index 3b3a8a1177..989515a646 100644
--- a/tests/fate/libavformat.mak
+++ b/tests/fate/libavformat.mak
@@ -2,6 +2,11 @@
 #fate-async: libavformat/tests/async$(EXESUF)
 #fate-async: CMD = run libavformat/tests/async
 
+FATE_LIBAVFORMAT += fate-mkdir
+fate-mkdir: libavformat/tests/mkdir$(EXESUF)
+fate-mkdir: CMD = run libavformat/tests/mkdir$(EXESUF)
+fate-mkdir: CMP = null
+
 FATE_LIBAVFORMAT-$(CONFIG_NETWORK) += fate-noproxy
 fate-noproxy: libavformat/tests/noproxy$(EXESUF)
 fate-noproxy: CMD = run libavformat/tests/noproxy$(EXESUF)
-- 
2.52.0


From 1eb17d8ac040fa80a614b10f39853d5a9a51b01b Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Tue, 7 Jul 2026 23:48:04 +0200
Subject: [PATCH 094/180] avcodec/exr: bound total decoded pixels by max_pixels

Use the maximum block dimensions decode_block() can assign after clipping
the nominal tile or scanline block to the data window.

Fixes: Timeout
Fixes: 521392254/clusterfuzz-testcase-minimized-ffmpeg_AV_CODEC_ID_EXR_DEC_fuzzer-6740984590565376
Found-by: continuous fuzzing process https://github.com/google/oss-fuzz/tree/master/projects/ffmpeg
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit 206f2d92db2e67914cb417b9e1efac191c3b0690)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/exr.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/libavcodec/exr.c b/libavcodec/exr.c
index bef1d62485..0e9c734a10 100644
--- a/libavcodec/exr.c
+++ b/libavcodec/exr.c
@@ -2229,6 +2229,16 @@ static int decode_frame(AVCodecContext *avctx, AVFrame *picture,
     if (bytestream2_get_bytes_left(gb)/8 < nb_blocks)
         return AVERROR_INVALIDDATA;
 
+    if (avctx->max_pixels) {
+        int64_t block_pixels = s->is_tile
+            ? (int64_t)FFMIN(s->tile_attr.xSize, s->xdelta) *
+                      FFMIN(s->tile_attr.ySize, s->ydelta)
+            : (int64_t)s->xdelta *
+                      FFMIN(s->scan_lines_per_block, s->ydelta);
+        if (nb_blocks > avctx->max_pixels / FFMAX(block_pixels, 1))
+            return AVERROR_INVALIDDATA;
+    }
+
     // check offset table and recreate it if need
     if (!s->is_tile && bytestream2_peek_le64(gb) == 0) {
         PutByteContext offset_table_writer;
-- 
2.52.0


From c21583c4ba84b33501f053de496e8022b9d86599 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Fri, 10 Jul 2026 02:34:59 +0200
Subject: [PATCH 095/180] avcodec/vorbisdec: skip the MDCT for unused channels

Fixes: Timeout
Fixes: 490144337/clusterfuzz-testcase-minimized-ffmpeg_AV_CODEC_ID_VORBIS_DEC_fuzzer-4539724776931328
Found-by: continuous fuzzing process https://github.com/google/oss-fuzz/tree/master/projects/ffmpeg
(cherry picked from commit 7a39c205ae90f3691a9996a69e7a52c06312ad32)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/vorbisdec.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/libavcodec/vorbisdec.c b/libavcodec/vorbisdec.c
index ba99c01307..aeea1b4908 100644
--- a/libavcodec/vorbisdec.c
+++ b/libavcodec/vorbisdec.c
@@ -1740,6 +1740,10 @@ static int vorbis_parse_audio_packet(vorbis_context *vc, float **floor_ptr)
 
     for (j = vc->audio_channels-1;j >= 0; j--) {
         ch_res_ptr   = vc->channel_residues + res_chan[j] * blocksize / 2;
+        if (no_residue[j]) {
+            memset(ch_res_ptr, 0, (blocksize / 2) * sizeof(float));
+            continue;
+        }
         vc->fdsp->vector_fmul(floor_ptr[j], floor_ptr[j], ch_res_ptr, blocksize / 2);
         mdct_fn(mdct, ch_res_ptr, floor_ptr[j], sizeof(float));
     }
-- 
2.52.0


From 789d7b1b1dff213b4e0b349345a835147cb7c1be Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Fri, 10 Jul 2026 04:07:27 +0200
Subject: [PATCH 096/180] avcodec/ffv1dec: mask the fltmap index on the 8bit
 remap path

Fixes: out of array access
Fixes: ffv1_poc/test_ffv1_8bit_v4.avi
Fixes: 6p0ahHBxreqG
Found-by: SecBuddyF - Tencent KeenLab
(cherry picked from commit ed4f286a1049bcd394ab9a5ce71e297f6cbba002)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/ffv1dec.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libavcodec/ffv1dec.c b/libavcodec/ffv1dec.c
index 0c0689c81f..ba973ab76f 100644
--- a/libavcodec/ffv1dec.c
+++ b/libavcodec/ffv1dec.c
@@ -138,7 +138,7 @@ static int decode_plane(FFV1Context *f, FFV1SliceContext *sc,
                 return ret;
             if (sc->remap)
                 for (x = 0; x < w; x++)
-                    sample[1][x] = sc->fltmap[remap_index][sample[1][x]];
+                    sample[1][x] = sc->fltmap[remap_index][sample[1][x] & mask];
             for (x = 0; x < w; x++)
                 src[x*pixel_stride + stride * y] = sample[1][x];
         } else {
-- 
2.52.0


From bd2541b8da2cdffe76da9b67c0f22f6b0f1fc1b9 Mon Sep 17 00:00:00 2001
From: Jiale Yao <[email protected]>
Date: Sat, 11 Jul 2026 16:46:26 +0200
Subject: [PATCH 097/180] avcodec/libcodec2: reject packet sample counts that
 overflow int

Fixes: out of array access
Fixes: 2jy_poc_codec2.zip / poc_codec2.raw
Fixes: jOQASNnOm6O7
Found-by: Jiale Yao <[email protected]>
(cherry picked from commit 705ff11c2b02d2925fca5c2c78c547ccbd901182)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/libcodec2.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/libavcodec/libcodec2.c b/libavcodec/libcodec2.c
index 8895865721..ca74b2beb6 100644
--- a/libavcodec/libcodec2.c
+++ b/libavcodec/libcodec2.c
@@ -132,6 +132,8 @@ static int libcodec2_decode(AVCodecContext *avctx, AVFrame *frame,
     int16_t *output;
 
     nframes           = pkt->size / avctx->block_align;
+    if (nframes > INT_MAX / avctx->frame_size)
+        return AVERROR_INVALIDDATA;
     frame->nb_samples = avctx->frame_size * nframes;
 
     ret = ff_get_buffer(avctx, frame, 0);
-- 
2.52.0


From e596a3302a2686f65592352de2a3c90ec17da77c Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Sat, 11 Jul 2026 16:46:17 +0200
Subject: [PATCH 098/180] avcodec/cavsdec: reject invalid start-code bitreader
 sizes

Fixes: NULL pointer dereference
Fixes: 4jy_poc_cavs.zip / poc_cavs.avi
Fixes: UH66Y8Om9F8X
Found-by: Jiale Yao <[email protected]>
(cherry picked from commit 9c2aabaa34147b7a3812e5857bcb4f9f6e17a5f7)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/cavsdec.c | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/libavcodec/cavsdec.c b/libavcodec/cavsdec.c
index bc1ed60bf0..b6dd5f2d3f 100644
--- a/libavcodec/cavsdec.c
+++ b/libavcodec/cavsdec.c
@@ -1266,10 +1266,11 @@ static int cavs_decode_frame(AVCodecContext *avctx, AVFrame *rframe,
                 av_log(h->avctx, AV_LOG_WARNING, "no frame decoded\n");
             return FFMAX(0, buf_ptr - buf);
         }
-        input_size = (buf_end - buf_ptr) * 8;
+        input_size = buf_end - buf_ptr;
+        if ((ret = init_get_bits8(&h->gb, buf_ptr, input_size)) < 0)
+            return ret;
         switch (stc) {
         case CAVS_START_CODE:
-            init_get_bits(&h->gb, buf_ptr, input_size);
             decode_seq_header(h);
             break;
         case PIC_I_START_CODE:
@@ -1288,7 +1289,6 @@ static int cavs_decode_frame(AVCodecContext *avctx, AVFrame *rframe,
             *got_frame = 0;
             if (!h->got_keyframe)
                 break;
-            init_get_bits(&h->gb, buf_ptr, input_size);
             h->stc = stc;
             if (decode_pic(h))
                 break;
@@ -1312,7 +1312,6 @@ static int cavs_decode_frame(AVCodecContext *avctx, AVFrame *rframe,
             break;
         default:
             if (stc <= SLICE_MAX_START_CODE) {
-                init_get_bits(&h->gb, buf_ptr, input_size);
                 decode_slice_header(h, &h->gb);
             }
             break;
-- 
2.52.0


From 3b85fbe89025ea696988488358dfde41a09c53c5 Mon Sep 17 00:00:00 2001
From: Cloud-LHY <[email protected]>
Date: Fri, 10 Jul 2026 04:07:04 +0200
Subject: [PATCH 099/180] avcodec/tdsc: unref the reference frame before
 reallocating on size change

Fixes: out of array access
Fixes: tdsc_poc/ffmpeg-tdsc-linesize-report/poc.avi / gen_poc.py
Fixes: tdsc_resize_jpeg_oob.avi / tdsc-resize-stale-linesize-jpeg-oob-generate-poc.py
Fixes: p9xG4xGf9P7H
Fixes: HQL7a1WgTdHZ
Found-by: Cloud-LHY / Clouditera Security, Z.ai Security, NSFOCUS
Found-by: Adrian Junge (vurlo)
(cherry picked from commit fd3ee52fab34d98a95b787d0b5ff45685766200c)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/tdsc.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/libavcodec/tdsc.c b/libavcodec/tdsc.c
index ca9dd0f0a6..102b4ae966 100644
--- a/libavcodec/tdsc.c
+++ b/libavcodec/tdsc.c
@@ -485,11 +485,15 @@ static int tdsc_parse_tdsf(AVCodecContext *avctx, int number_tiles)
             return ret;
         init_refframe = 1;
     }
-    ctx->refframe->width  = ctx->width  = w;
-    ctx->refframe->height = ctx->height = h;
+    ctx->width  = w;
+    ctx->height = h;
 
     /* Allocate the reference frame if not already done or on size change */
     if (init_refframe) {
+        av_frame_unref(ctx->refframe);
+        ctx->refframe->format = avctx->pix_fmt;
+        ctx->refframe->width  = w;
+        ctx->refframe->height = h;
         ret = av_frame_get_buffer(ctx->refframe, 0);
         if (ret < 0)
             return ret;
-- 
2.52.0


From 96100b4308df1d953fff31077b4642095d435df4 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Sun, 12 Jul 2026 19:06:33 +0200
Subject: [PATCH 100/180] doc/infra.txt: add fairy

(cherry picked from commit 0c29cabc97639ccf2f3d676540c3f4d4a54c423e)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 doc/infra.txt | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/doc/infra.txt b/doc/infra.txt
index 292abb04fc..77ca7d4fe3 100644
--- a/doc/infra.txt
+++ b/doc/infra.txt
@@ -123,6 +123,13 @@ You need a VM, docker container for FFmpeg? contact root at ffmpeg.org
 
 
 
+Forgejo Fairy:
+~~~~~~~~~~~~~~
+LLM based reviewer, issue investigator and general helper. Written and maintained by Michael
+https://code.ffmpeg.org/michaelni/Fairies
+
+
+
 Multimedia Wiki:
 ~~~~~~~~~~~~~~~~
 The Multimedia Wiki http://wiki.multimedia.cx is ran by Mike Melanson.
-- 
2.52.0


From e91540f76fece625778384eeafbea37c84e103f6 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Sun, 12 Jul 2026 19:12:09 +0200
Subject: [PATCH 101/180] doc/developer.texi: update reviews section

Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit 01a9a98f353bcf9ffcdfdcc62533ef063231384d)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 doc/developer.texi | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/doc/developer.texi b/doc/developer.texi
index ced36d0663..0a0cce63f5 100644
--- a/doc/developer.texi
+++ b/doc/developer.texi
@@ -788,11 +788,16 @@ Now you'll just need to open the eml file with the email application
 and execute 'Send'.
 
 @subheading Reviews
-Your patch will be reviewed on the mailing list. You will likely be asked
+Your patch or pull request will be reviewed on the mailing list or on
+code.ffmpeg.org. You will likely be asked
 to make some changes and are expected to send in an improved version that
 incorporates the requests from the review. This process may go through
 several iterations. Once your patch is deemed good enough, some developer
 will pick it up and commit it to the official FFmpeg tree.
+If you want an LLM-based review, add
+@url{https://code.ffmpeg.org/Forgejo_Fairy, Forgejo Fairy} as a reviewer
+to your pull request. Issues with Fairy herself can be reported at her
+@url{https://code.ffmpeg.org/michaelni/Fairies, repository}.
 
 Give us a few days to react. But if some time passes without reaction,
 send a reminder by email. Your patch should eventually be dealt with.
-- 
2.52.0


From 989444060d5fbb030a1554fc47791887c9e725f6 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Fri, 10 Jul 2026 04:07:17 +0200
Subject: [PATCH 102/180] avfilter/vf_lut3d: compute size2 after the 3DLUTSIZE
 directive

Fixes: out of array access
Fixes: lut3d_poc/poc_3dlut2.dat
Fixes: 6p0ahHBxreqG
Found-by: SecBuddyF - Tencent KeenLab
(cherry picked from commit 0089694c060bf25b628d16ba6078f083f7522bae)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavfilter/vf_lut3d.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libavfilter/vf_lut3d.c b/libavfilter/vf_lut3d.c
index 6ab33790c6..9071dabeb9 100644
--- a/libavfilter/vf_lut3d.c
+++ b/libavfilter/vf_lut3d.c
@@ -644,7 +644,6 @@ static int parse_dat(AVFilterContext *ctx, FILE *f)
     int ret, i, j, k, size, size2;
 
     lut3d->lutsize = size = 33;
-    size2 = size * size;
 
     NEXT_LINE(skip_line(line));
     if (!strncmp(line, "3DLUTSIZE ", 10)) {
@@ -652,6 +651,7 @@ static int parse_dat(AVFilterContext *ctx, FILE *f)
 
         NEXT_LINE(skip_line(line));
     }
+    size2 = size * size;
 
     ret = allocate_3dlut(ctx, size, 0);
     if (ret < 0)
-- 
2.52.0


From a20608d32504d233ab2708f125b86683f1dc23e9 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Sat, 11 Jul 2026 16:47:28 +0200
Subject: [PATCH 103/180] avfilter/vf_floodfill: size the point stack for the
 current frame

Fixes: out of array access
Fixes: 8aj_floodfill_dynamic_size.pgm / 8aj_generate_floodfill_dynamic_size_pgm.py
Fixes: 3MleMXjGZvu3
Found-by: Adrian Junge (vurlo) <[email protected]>
(cherry picked from commit 24c322fdb232d0a3f3790d544dcb64e5c2138e79)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavfilter/vf_floodfill.c | 19 ++++++++++++++++---
 1 file changed, 16 insertions(+), 3 deletions(-)

diff --git a/libavfilter/vf_floodfill.c b/libavfilter/vf_floodfill.c
index 6d89963e71..e569d5f586 100644
--- a/libavfilter/vf_floodfill.c
+++ b/libavfilter/vf_floodfill.c
@@ -41,6 +41,7 @@ typedef struct FloodfillContext {
     int nb_planes;
     int back, front;
     Points *points;
+    unsigned int points_size;
 
     int (*is_same)(const AVFrame *frame, int x, int y,
                    unsigned s0, unsigned s1, unsigned s2, unsigned s3);
@@ -271,9 +272,6 @@ static int config_input(AVFilterLink *inlink)
     }
 
     s->front = s->back = 0;
-    s->points = av_calloc(inlink->w * inlink->h, 4 * sizeof(Points));
-    if (!s->points)
-        return AVERROR(ENOMEM);
 
     return 0;
 }
@@ -292,8 +290,23 @@ static int filter_frame(AVFilterLink *link, AVFrame *frame)
     int s3 = s->s[3];
     const int w = frame->width;
     const int h = frame->height;
+    size_t nb_points, points_size;
     int i, ret;
 
+    if (w > UINT16_MAX + 1 || h > UINT16_MAX + 1 ||
+        av_size_mult(w, h, &nb_points) < 0 ||
+        av_size_mult(nb_points, 4 * sizeof(*s->points), &points_size) < 0) {
+        av_frame_free(&frame);
+        return AVERROR(EINVAL);
+    }
+
+    av_fast_malloc(&s->points, &s->points_size, points_size);
+    if (!s->points) {
+        av_frame_free(&frame);
+        return AVERROR(ENOMEM);
+    }
+    s->front = s->back = 0;
+
     if (is_inside(s->x, s->y, w, h)) {
         s->pick_pixel(frame, s->x, s->y, &s0, &s1, &s2, &s3);
 
-- 
2.52.0


From 30a52276f9dff60fe732d8bb8d463e587ff69c94 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Sun, 12 Jul 2026 03:27:47 +0200
Subject: [PATCH 104/180] avfilter/vf_floodfill: remove unneeded variables

Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit f186c50cf53aec20e9a29059cb22ca3f2d59201c)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavfilter/vf_floodfill.c | 35 ++++++++++++++++-------------------
 1 file changed, 16 insertions(+), 19 deletions(-)

diff --git a/libavfilter/vf_floodfill.c b/libavfilter/vf_floodfill.c
index e569d5f586..9bc72e2a78 100644
--- a/libavfilter/vf_floodfill.c
+++ b/libavfilter/vf_floodfill.c
@@ -39,7 +39,6 @@ typedef struct FloodfillContext {
     int d[4];
 
     int nb_planes;
-    int back, front;
     Points *points;
     unsigned int points_size;
 
@@ -271,8 +270,6 @@ static int config_input(AVFilterLink *inlink)
        }
     }
 
-    s->front = s->back = 0;
-
     return 0;
 }
 
@@ -292,6 +289,7 @@ static int filter_frame(AVFilterLink *link, AVFrame *frame)
     const int h = frame->height;
     size_t nb_points, points_size;
     int i, ret;
+    int front = 0;
 
     if (w > UINT16_MAX + 1 || h > UINT16_MAX + 1 ||
         av_size_mult(w, h, &nb_points) < 0 ||
@@ -305,7 +303,6 @@ static int filter_frame(AVFilterLink *link, AVFrame *frame)
         av_frame_free(&frame);
         return AVERROR(ENOMEM);
     }
-    s->front = s->back = 0;
 
     if (is_inside(s->x, s->y, w, h)) {
         s->pick_pixel(frame, s->x, s->y, &s0, &s1, &s2, &s3);
@@ -323,9 +320,9 @@ static int filter_frame(AVFilterLink *link, AVFrame *frame)
             goto end;
 
         if (s->is_same(frame, s->x, s->y, s0, s1, s2, s3)) {
-            s->points[s->front].x = s->x;
-            s->points[s->front].y = s->y;
-            s->front++;
+            s->points[front].x = s->x;
+            s->points[front].y = s->y;
+            front++;
         }
 
         if (ret = ff_inlink_make_frame_writable(link, &frame)) {
@@ -333,34 +330,34 @@ static int filter_frame(AVFilterLink *link, AVFrame *frame)
             return ret;
         }
 
-        while (s->front > s->back) {
+        while (front > 0) {
             int x, y;
 
-            s->front--;
-            x = s->points[s->front].x;
-            y = s->points[s->front].y;
+            front--;
+            x = s->points[front].x;
+            y = s->points[front].y;
 
             if (s->is_same(frame, x, y, s0, s1, s2, s3)) {
                 s->set_pixel(frame, x, y, d0, d1, d2, d3);
 
                 if (is_inside(x + 1, y, w, h)) {
-                    s->points[s->front]  .x = x + 1;
-                    s->points[s->front++].y = y;
+                    s->points[front]  .x = x + 1;
+                    s->points[front++].y = y;
                 }
 
                 if (is_inside(x - 1, y, w, h)) {
-                    s->points[s->front]  .x = x - 1;
-                    s->points[s->front++].y = y;
+                    s->points[front]  .x = x - 1;
+                    s->points[front++].y = y;
                 }
 
                 if (is_inside(x, y + 1, w, h)) {
-                    s->points[s->front]  .x = x;
-                    s->points[s->front++].y = y + 1;
+                    s->points[front]  .x = x;
+                    s->points[front++].y = y + 1;
                 }
 
                 if (is_inside(x, y - 1, w, h)) {
-                    s->points[s->front]  .x = x;
-                    s->points[s->front++].y = y - 1;
+                    s->points[front]  .x = x;
+                    s->points[front++].y = y - 1;
                 }
             }
         }
-- 
2.52.0


From b3c7ebc1edc401fd9881277fdfae93f3f24ceb81 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Sat, 11 Jul 2026 16:46:39 +0200
Subject: [PATCH 105/180] avfilter/vf_swaprect: size the temp row buffer for
 the widest plane

Fixes: out of array access
Fixes: 7aj_swaprect_odd17_nv12.nut / 7aj_generate_swaprect_odd17_nv12.py
Fixes: VRAXYvKtmKa8
Found-by: Adrian Junge (vurlo) <[email protected]>
(cherry picked from commit a7e38b617b32f996beaa371bbf04b39907d7a527)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavfilter/vf_swaprect.c | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/libavfilter/vf_swaprect.c b/libavfilter/vf_swaprect.c
index 5d93f51c30..fe007ee5e7 100644
--- a/libavfilter/vf_swaprect.c
+++ b/libavfilter/vf_swaprect.c
@@ -200,6 +200,7 @@ static int config_input(AVFilterLink *inlink)
 {
     AVFilterContext *ctx = inlink->dst;
     SwapRectContext *s = ctx->priv;
+    int size = 0;
 
     if (!s->w  || !s->h  ||
         !s->x1 || !s->y1 ||
@@ -210,7 +211,16 @@ static int config_input(AVFilterLink *inlink)
     av_image_fill_max_pixsteps(s->pixsteps, NULL, s->desc);
     s->nb_planes = av_pix_fmt_count_planes(inlink->format);
 
-    s->temp = av_malloc_array(inlink->w, s->pixsteps[0]);
+    for (int p = 0; p < s->nb_planes; p++) {
+        int shift = p == 1 || p == 2 ? s->desc->log2_chroma_w : 0;
+        int width = AV_CEIL_RSHIFT(inlink->w, shift);
+
+        if (width > INT_MAX / s->pixsteps[p])
+            return AVERROR(EINVAL);
+        size = FFMAX(size, width * s->pixsteps[p]);
+    }
+
+    s->temp = av_malloc(size);
     if (!s->temp)
         return AVERROR(ENOMEM);
 
-- 
2.52.0


From 52f7983f15678c8a6065327760d7b6eb1c9c84ed Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Fri, 10 Jul 2026 04:07:35 +0200
Subject: [PATCH 106/180] avformat/ty: don't let the Series2 AC3 trim underflow
 the packet size

Fixes: negative-size-param
Fixes: ty-s2-ac3-negative-size-single-file.ffconcat / create_poc.py
Fixes: g0qeE6KvrjZi
Found-by: Adrian Junge (vurlo)
(cherry picked from commit de771bd52774a52d45b0e2c82e56995a1ef40df7)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavformat/ty.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libavformat/ty.c b/libavformat/ty.c
index 9be027fcca..842d97038c 100644
--- a/libavformat/ty.c
+++ b/libavformat/ty.c
@@ -578,7 +578,7 @@ static int demux_audio(AVFormatContext *s, TyRecHdr *rec_hdr, AVPacket *pkt)
         if (ty->audio_type == TIVO_AUDIO_AC3 &&
                 ty->tivo_series == TIVO_SERIES2) {
             if (ty->ac3_pkt_size + pkt->size > AC3_PKT_LENGTH) {
-                pkt->size -= 2;
+                pkt->size -= FFMIN(pkt->size, 2);
                 ty->ac3_pkt_size = 0;
             } else {
                 ty->ac3_pkt_size += pkt->size;
-- 
2.52.0


From d6db7799503ee1b3b14ce7cb9d9ee776d86a583e Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Wed, 24 Jun 2026 03:00:42 +0200
Subject: [PATCH 107/180] avcodec/hevc: Fix integer overflow in WPP entry point
 range check

Fixes: out of array access
Fixes: poc.hevc / make_poc.py
Fixes: 83WY4xnR87vb
Fixes: dad354f38d (avcodec/hevc: Check max ctb addresses for WPP)
Found-by: VulnForge Security Research Team <[email protected]>
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit ba55f425fdd889a7998fce068367e9d3a6c40af4)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/hevc/hevcdec.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libavcodec/hevc/hevcdec.c b/libavcodec/hevc/hevcdec.c
index ae064ec8af..18e22f697e 100644
--- a/libavcodec/hevc/hevcdec.c
+++ b/libavcodec/hevc/hevcdec.c
@@ -2920,7 +2920,7 @@ static int hls_slice_data_wpp(HEVCContext *s, const H2645NAL *nal)
     int64_t startheader, cmpt = 0;
     int j, res = 0;
 
-    if (s->sh.slice_ctb_addr_rs + s->sh.num_entry_point_offsets * sps->ctb_width >= sps->ctb_width * sps->ctb_height) {
+    if (s->sh.slice_ctb_addr_rs + s->sh.num_entry_point_offsets * (int64_t)sps->ctb_width >= sps->ctb_width * (int64_t)sps->ctb_height) {
         av_log(s->avctx, AV_LOG_ERROR, "WPP ctb addresses are wrong (%d %d %d %d)\n",
             s->sh.slice_ctb_addr_rs, s->sh.num_entry_point_offsets,
             sps->ctb_width, sps->ctb_height
-- 
2.52.0


From 03e62592547ddfc7acd41be62c6365a35881f806 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Mon, 7 Jul 2025 02:53:18 +0200
Subject: [PATCH 108/180] avformat/concatdec: Check recursion depth

This variant is easy backportable but is concatdec specific

Fixes: self_ref.ffconcat
Reported-by: Yuhao Jiang <[email protected]>
Rediscovered-by: Anxious Koisi
Fixes: RWHkT0ASFMNI
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit 58d39e99e26d1116f43a67d66ecfe71dba316875)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavformat/concatdec.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/libavformat/concatdec.c b/libavformat/concatdec.c
index c57d1b649a..665b4e3e66 100644
--- a/libavformat/concatdec.c
+++ b/libavformat/concatdec.c
@@ -73,6 +73,7 @@ typedef struct {
     ConcatMatchMode stream_match_mode;
     unsigned auto_convert;
     int segment_time_metadata;
+    int recursion_depth;
 } ConcatContext;
 
 static int concat_probe(const AVProbeData *probe)
@@ -357,6 +358,12 @@ static int open_file(AVFormatContext *avf, unsigned fileno)
     if (ret < 0)
         return ret;
 
+    ret = av_dict_set_int(&options, "recursion_depth", cat->recursion_depth - 1, 0);
+    if (ret < 0) {
+        av_dict_free(&options);
+        return ret;
+    }
+
     if ((ret = avformat_open_input(&cat->avf, file->url, NULL, &options)) < 0 ||
         (ret = avformat_find_stream_info(cat->avf, NULL)) < 0) {
         av_log(avf, AV_LOG_ERROR, "Impossible to open '%s'\n", file->url);
@@ -364,6 +371,7 @@ static int open_file(AVFormatContext *avf, unsigned fileno)
         avformat_close_input(&cat->avf);
         return ret;
     }
+    av_dict_set(&options, "recursion_depth", NULL, 0);
     if (options) {
         av_log(avf, AV_LOG_WARNING, "Unused options for '%s'.\n", file->url);
         /* TODO log unused options once we have a proper string API */
@@ -665,6 +673,11 @@ static int concat_read_header(AVFormatContext *avf)
     unsigned i;
     int ret;
 
+    if (cat->recursion_depth <= 0) {
+        av_log(avf, AV_LOG_ERROR, "Too deep recursion\n");
+        return AVERROR_INVALIDDATA;
+    }
+
     ret = concat_parse_script(avf);
     if (ret < 0)
         return ret;
@@ -940,6 +953,8 @@ static const AVOption options[] = {
       OFFSET(auto_convert), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, DEC },
     { "segment_time_metadata", "output file segment start time and duration as packet metadata",
       OFFSET(segment_time_metadata), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DEC },
+    { "recursion_depth", "max recursion depth",
+      OFFSET(recursion_depth), AV_OPT_TYPE_INT, {.i64 = 10}, 0, INT_MAX, DEC },
     { NULL }
 };
 
-- 
2.52.0


From 3b92cd5fa9718e2c3912d2bf58945b14d819023c Mon Sep 17 00:00:00 2001
From: Thomas Guilbert <[email protected]>
Date: Wed, 17 Jun 2026 03:18:51 +0000
Subject: [PATCH 109/180] Guard against int overflow when discarding samples

In `discard_samples()`, the rescaling operation can evaluate to
AV_NOPTS_VALUE if the rescaling operation overflows. This commit
prevents adjusting timestamp and durations by the invalid value.

(cherry picked from commit 005e963c85689f4714ea9948a6be4739fc68ebc9)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/decode.c | 20 +++++++++++++-------
 1 file changed, 13 insertions(+), 7 deletions(-)

diff --git a/libavcodec/decode.c b/libavcodec/decode.c
index 6f85e5f514..ba6ff8a9fd 100644
--- a/libavcodec/decode.c
+++ b/libavcodec/decode.c
@@ -374,12 +374,18 @@ static int discard_samples(AVCodecContext *avctx, AVFrame *frame, int64_t *disca
                 int64_t diff_ts = av_rescale_q(avci->skip_samples,
                                                (AVRational){1, avctx->sample_rate},
                                                avctx->pkt_timebase);
-                if (frame->pts != AV_NOPTS_VALUE)
-                    frame->pts += diff_ts;
-                if (frame->pkt_dts != AV_NOPTS_VALUE)
-                    frame->pkt_dts += diff_ts;
-                if (frame->duration >= diff_ts)
-                    frame->duration -= diff_ts;
+                if (diff_ts != AV_NOPTS_VALUE) {
+                    if (frame->pts != AV_NOPTS_VALUE)
+                        frame->pts = av_sat_add64(frame->pts, diff_ts);
+                    if (frame->pkt_dts != AV_NOPTS_VALUE)
+                        frame->pkt_dts = av_sat_add64(frame->pkt_dts, diff_ts);
+                    if (frame->duration >= diff_ts)
+                        frame->duration = av_sat_sub64(frame->duration, diff_ts);
+                } else {
+                    frame->pts = AV_NOPTS_VALUE;
+                    frame->pkt_dts = AV_NOPTS_VALUE;
+                    frame->duration = 0;
+                }
             } else
                 av_log(avctx, AV_LOG_WARNING, "Could not update timestamps for skipped samples.\n");
 
@@ -400,7 +406,7 @@ static int discard_samples(AVCodecContext *avctx, AVFrame *frame, int64_t *disca
                 int64_t diff_ts = av_rescale_q(frame->nb_samples - discard_padding,
                                                (AVRational){1, avctx->sample_rate},
                                                avctx->pkt_timebase);
-                frame->duration = diff_ts;
+                frame->duration = diff_ts == AV_NOPTS_VALUE ? 0 : diff_ts;
             } else
                 av_log(avctx, AV_LOG_WARNING, "Could not update timestamps for discarded samples.\n");
 
-- 
2.52.0


From 1b8b99dd459361fed8a4b99b02b74b33fee18a14 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Fri, 3 Jul 2026 04:47:40 +0200
Subject: [PATCH 110/180] avformat/mov: avoid overflow/negative discard sample
 duration

This also fixes a corner case with st->duration = INT64_MAX

This assumes pkt->duration >= 0

Fixes: 525566001/clusterfuzz-testcase-minimized-ffmpeg_dem_MOV_fuzzer-5952332261818368
Found-by: continuous fuzzing process https://github.com/google/oss-fuzz/tree/master/projects/ffmpeg
Signed-off-by: Michael Niedermayer <[email protected]>
(cherry picked from commit 6d300b473266cda5926d86fe5a9d126c9c10b3c6)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavformat/mov.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/libavformat/mov.c b/libavformat/mov.c
index 4154038d9e..98e5e4de9a 100644
--- a/libavformat/mov.c
+++ b/libavformat/mov.c
@@ -11666,8 +11666,11 @@ static int mov_finalize_packet(AVFormatContext *s, AVStream *st, AVIndexEntry *s
         int64_t total = av_rescale_q(st->duration, st->time_base, (AVRational){ 1, st->codecpar->sample_rate });
         int64_t duration = pkt->duration;
 
-        if (av_sat_add64(pkt->pts, pkt->duration) > st->duration)
-            duration = st->duration - pkt->pts;
+        if (st->duration < pkt->pts) {
+            duration = 0;
+        } else
+            duration = FFMIN(duration, (uint64_t)st->duration - pkt->pts);
+
         duration = av_rescale_q(duration, st->time_base, (AVRational){ 1, st->codecpar->sample_rate });
 
         if (!ffstream(st)->first_discard_sample)
-- 
2.52.0


From f8051f532c99543ca8e49da8070fb01040548979 Mon Sep 17 00:00:00 2001
From: Omkhar Arasaratnam <[email protected]>
Date: Mon, 6 Jul 2026 12:22:29 -0400
Subject: [PATCH 111/180] avformat/tls_mbedtls: check the certificate
 verification result in tls_handshake()

tls_mbedtls verifies the peer certificate manually via
mbedtls_ssl_get_verify_result() (it uses MBEDTLS_SSL_VERIFY_OPTIONAL), but
only in tls_open(). On the external-socket path (external_sock=1) tls_open()
skips the handshake; it then runs later through the url_handshake hook,
tls_handshake(), which never checks the verification result, so with verify=1
an untrusted peer certificate would be accepted.

The only in-tree user of this path is the WHIP muxer, which sets verify=0
(WebRTC binds the peer via the SDP a=fingerprint, not a CA), so this is not
reachable today. This change is defence-in-depth: it makes tls_handshake()
honor verify symmetrically with the existing tls_open() check.

Signed-off-by: Omkhar Arasaratnam <[email protected]>
(cherry picked from commit b96701098fd8bc33a7726b895ca92102da65202d)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavformat/tls_mbedtls.c | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/libavformat/tls_mbedtls.c b/libavformat/tls_mbedtls.c
index bfa5103596..8f7ace5e63 100644
--- a/libavformat/tls_mbedtls.c
+++ b/libavformat/tls_mbedtls.c
@@ -475,6 +475,7 @@ static int tls_handshake(URLContext *h)
     TLSContext *tls_ctx = h->priv_data;
     TLSShared *shr = &tls_ctx->tls_shared;
     URLContext *uc = shr->is_dtls ? shr->udp : shr->tcp;
+    uint32_t verify_res_flags;
     int ret;
 
     uc->flags &= ~AVIO_FLAG_NONBLOCK;
@@ -490,6 +491,18 @@ static int tls_handshake(URLContext *h)
         }
     }
 
+    if (shr->verify) {
+        // check the result of the certificate verification
+        if ((verify_res_flags = mbedtls_ssl_get_verify_result(&tls_ctx->ssl_context)) != 0) {
+            av_log(h, AV_LOG_ERROR, "mbedtls_ssl_get_verify_result reported problems "\
+                                    "with the certificate verification, returned flags: %"PRIu32"\n",
+                                    verify_res_flags);
+            if (verify_res_flags & MBEDTLS_X509_BADCERT_NOT_TRUSTED)
+                av_log(h, AV_LOG_ERROR, "The certificate is not correctly signed by the trusted CA.\n");
+            return AVERROR(EIO);
+        }
+    }
+
     return ret;
 }
 
-- 
2.52.0


From 5185caaeb8e2c05f7369cbedf0f616601bd82d5c Mon Sep 17 00:00:00 2001
From: Leo Izen <[email protected]>
Date: Sun, 12 Jul 2026 10:22:47 -0400
Subject: [PATCH 112/180] avcodec/pngenc: fix overrun caused by exif size
 discrepancy

If the estimated EXIF size based on the attached frame data is lower
than the actual EXIF size after sanitizing the IFD, then an overrun
might occur. Instead, we parse the IFD and use the parsed size as the
estimated exif size so there won't be any discrepancy between the two
values.

Signed-off-by: Leo Izen <[email protected]>
Reported-by: Adrian Junge <[email protected]>
(cherry picked from commit b506fafec9a19fcbc2be5271875fd4a63d6615bc)
---
 libavcodec/pngenc.c | 50 ++++++++++++++++++++++-----------------------
 1 file changed, 25 insertions(+), 25 deletions(-)

diff --git a/libavcodec/pngenc.c b/libavcodec/pngenc.c
index 82a9d5b835..8acd98b551 100644
--- a/libavcodec/pngenc.c
+++ b/libavcodec/pngenc.c
@@ -72,6 +72,8 @@ typedef struct PNGEncContext {
     int color_type;
     int bits_per_pixel;
 
+    AVBufferRef *exif_data;
+
     // APNG
     uint32_t palette_checksum;   // Used to ensure a single unique palette
     uint32_t sequence_number;
@@ -377,7 +379,6 @@ static int encode_headers(AVCodecContext *avctx, const AVFrame *pict)
 {
     AVFrameSideData *side_data;
     PNGEncContext *s = avctx->priv_data;
-    AVBufferRef *exif_data = NULL;
     int ret;
 
     /* write png header */
@@ -419,17 +420,10 @@ static int encode_headers(AVCodecContext *avctx, const AVFrame *pict)
         }
     }
 
-    ret = ff_exif_get_buffer(avctx, pict, &exif_data, AV_EXIF_TIFF_HEADER);
-    if (exif_data) {
-        // png_write_chunk accepts an int, not a size_t, so we have to check overflow
-        if (exif_data->size > INT_MAX - AV_INPUT_BUFFER_PADDING_SIZE)
-            // that's a very big exif chunk, probably a bug
-            av_log(avctx, AV_LOG_ERROR, "extremely large EXIF buffer detected, not writing\n");
-        else
-            png_write_chunk(&s->bytestream, MKTAG('e','X','I','f'), exif_data->data, exif_data->size);
-        av_buffer_unref(&exif_data);
-    } else if (ret < 0) {
-        av_log(avctx, AV_LOG_WARNING, "unable to attach EXIF metadata: %s\n", av_err2str(ret));
+    if (s->exif_data) {
+        /* we checked for overflow when we attached the buffer to s->exif_data */
+        png_write_chunk(&s->bytestream, MKTAG('e','X','I','f'), s->exif_data->data, s->exif_data->size);
+        av_buffer_unref(&s->exif_data);
     }
 
     side_data = av_frame_get_side_data(pict, AV_FRAME_DATA_ICC_PROFILE);
@@ -649,25 +643,30 @@ static int add_icc_profile_size(AVCodecContext *avctx, const AVFrame *pict,
 static int add_exif_profile_size(AVCodecContext *avctx, const AVFrame *pict,
                                  uint64_t *max_packet_size)
 {
-    const AVFrameSideData *sd;
     uint64_t new_pkt_size;
-    /* includes orientation tag */
-    const int base_exif_size = 92;
-    uint64_t estimated_exif_size;
+    PNGEncContext *s = avctx->priv_data;
 
-    sd = av_frame_get_side_data(pict, AV_FRAME_DATA_EXIF);
-    estimated_exif_size = sd ? sd->size : 0;
-    sd = av_frame_get_side_data(pict, AV_FRAME_DATA_DISPLAYMATRIX);
-    if (sd)
-        estimated_exif_size += base_exif_size;
-
-    if (!estimated_exif_size)
+    int result = ff_exif_get_buffer(avctx, pict, &s->exif_data, AV_EXIF_TIFF_HEADER);
+    if (!s->exif_data) {
+        if (result < 0)
+            av_log(avctx, AV_LOG_WARNING, "unable to attach EXIF metadata: %s\n", av_err2str(result));
         return 0;
+    }
+
+    /* png_write_chunk accepts an int, not a size_t, so we have to check overflow */
+    if (s->exif_data->size > INT_MAX - AV_INPUT_BUFFER_PADDING_SIZE) {
+        /* that's a very big exif chunk, probably a bug */
+        av_log(avctx, AV_LOG_ERROR, "extremely large EXIF buffer detected, not writing\n");
+        av_buffer_unref(&s->exif_data);
+        return 0;
+    }
 
     /* 12 is the png chunk header size */
-    new_pkt_size = *max_packet_size + estimated_exif_size + 12;
-    if (new_pkt_size < *max_packet_size)
+    new_pkt_size = *max_packet_size + s->exif_data->size + 12;
+    if (new_pkt_size < *max_packet_size) {
+        av_buffer_unref(&s->exif_data);
         return AVERROR_INVALIDDATA;
+    }
 
     *max_packet_size = new_pkt_size;
 
@@ -1258,6 +1257,7 @@ static av_cold int png_enc_close(AVCodecContext *avctx)
     ff_deflate_end(&s->zstream);
     av_frame_free(&s->last_frame);
     av_frame_free(&s->prev_frame);
+    av_buffer_unref(&s->exif_data);
     av_freep(&s->last_frame_packet);
     av_freep(&s->extra_data);
     s->extra_data_size = 0;
-- 
2.52.0


From af3779401a6ad2d3cf2c1c7597fa8f99846bba6b Mon Sep 17 00:00:00 2001
From: James Almer <[email protected]>
Date: Thu, 23 Jul 2026 13:56:54 -0300
Subject: [PATCH 113/180] avformat/lcevc: add a log context parameter to all
 functions

Printing to NULL should be avoided when possible.

Signed-off-by: James Almer <[email protected]>
(cherry picked from commit b3d4ce20a4608fe72af0b035b4007bba46a15279)
---
 libavformat/codecstring.c | 2 +-
 libavformat/lcevc.c       | 8 ++++----
 libavformat/lcevc.h       | 4 ++--
 libavformat/movenc.c      | 6 +++---
 4 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/libavformat/codecstring.c b/libavformat/codecstring.c
index b0ccb93e8d..6ecec13ed5 100644
--- a/libavformat/codecstring.c
+++ b/libavformat/codecstring.c
@@ -180,7 +180,7 @@ int ff_make_codec_str(void *logctx, const AVCodecParameters *par,
         int err;
         if (!par->extradata_size)
             return AVERROR(EINVAL);
-        if ((err = ff_lcvec_parse_config_record(&lvcc, par->extradata, par->extradata_size)) < 0)
+        if ((err = ff_lcvec_parse_config_record(&lvcc, par->extradata, par->extradata_size, logctx)) < 0)
             return err;
         av_bprintf(out, "lvc1.vprf%u.vlev%u", lvcc.profile_idc, lvcc.level_idc);
     } else if (par->codec_id == AV_CODEC_ID_AV1) {
diff --git a/libavformat/lcevc.c b/libavformat/lcevc.c
index 85ea96d22e..423b90a72b 100644
--- a/libavformat/lcevc.c
+++ b/libavformat/lcevc.c
@@ -178,7 +178,7 @@ static int write_nalu(LCEVCDecoderConfigurationRecord *lvcc, AVIOContext *pb,
 }
 
 int ff_lcvec_parse_config_record(LCEVCDecoderConfigurationRecord *lvcc,
-                                 const uint8_t *buf, int size)
+                                 const uint8_t *buf, int size, void *logctx)
 {
     H2645Packet h2645_pkt = { 0 };
     AVIOContext *pb;
@@ -217,7 +217,7 @@ int ff_lcvec_parse_config_record(LCEVCDecoderConfigurationRecord *lvcc,
     if (ret < 0)
         return ret;
 
-    ret = ff_h2645_packet_split(&h2645_pkt, buf, size, NULL, 0, AV_CODEC_ID_LCEVC, 0);
+    ret = ff_h2645_packet_split(&h2645_pkt, buf, size, logctx, 0, AV_CODEC_ID_LCEVC, 0);
     if (ret < 0)
         goto fail;
 
@@ -248,7 +248,7 @@ fail:
     return ret;
 }
 
-int ff_isom_write_lvcc(AVIOContext *pb, const uint8_t *data, int len)
+int ff_isom_write_lvcc(AVIOContext *pb, const uint8_t *data, int len, void *logctx)
 {
     LCEVCDecoderConfigurationRecord lvcc = { 0 };
     AVIOContext *idr_pb = NULL, *nidr_pb = NULL;
@@ -267,7 +267,7 @@ int ff_isom_write_lvcc(AVIOContext *pb, const uint8_t *data, int len)
         return 0;
     }
 
-    ret = ff_h2645_packet_split(&h2645_pkt, data, len, NULL, 0, AV_CODEC_ID_LCEVC, 0);
+    ret = ff_h2645_packet_split(&h2645_pkt, data, len, logctx, 0, AV_CODEC_ID_LCEVC, 0);
     if (ret < 0)
         return ret;
 
diff --git a/libavformat/lcevc.h b/libavformat/lcevc.h
index 9bbc0c6764..4451bcc058 100644
--- a/libavformat/lcevc.h
+++ b/libavformat/lcevc.h
@@ -34,9 +34,9 @@ typedef struct LCEVCDecoderConfigurationRecord {
     uint32_t pic_height_in_luma_samples;
 } LCEVCDecoderConfigurationRecord;
 
-int ff_isom_write_lvcc(AVIOContext *pb, const uint8_t *data, int len);
+int ff_isom_write_lvcc(AVIOContext *pb, const uint8_t *data, int len, void *logctx);
 
 int ff_lcvec_parse_config_record(LCEVCDecoderConfigurationRecord *lvcc,
-                                 const uint8_t *buf, int size);
+                                 const uint8_t *buf, int size, void *logctx);
 
 #endif /* AVFORMAT_LCEVC_H */
diff --git a/libavformat/movenc.c b/libavformat/movenc.c
index 66752f35dc..aacde4ebe7 100644
--- a/libavformat/movenc.c
+++ b/libavformat/movenc.c
@@ -1690,7 +1690,7 @@ static int mov_write_evcc_tag(AVIOContext *pb, MOVTrack *track)
     return update_size(pb, pos);
 }
 
-static int mov_write_lvcc_tag(AVIOContext *pb, MOVTrack *track)
+static int mov_write_lvcc_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *track)
 {
     int64_t pos = avio_tell(pb);
 
@@ -1698,7 +1698,7 @@ static int mov_write_lvcc_tag(AVIOContext *pb, MOVTrack *track)
     ffio_wfourcc(pb, "lvcC");
 
     ff_isom_write_lvcc(pb, track->extradata[track->last_stsd_index],
-                       track->extradata_size[track->last_stsd_index]);
+                       track->extradata_size[track->last_stsd_index], s);
 
     return update_size(pb, pos);
 }
@@ -2905,7 +2905,7 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex
     else if (track->par->codec_id ==AV_CODEC_ID_EVC) {
         mov_write_evcc_tag(pb, track);
     } else if (track->par->codec_id == AV_CODEC_ID_LCEVC) {
-        mov_write_lvcc_tag(pb, track);
+        mov_write_lvcc_tag(mov->fc, pb, track);
     } else if (track->par->codec_id ==AV_CODEC_ID_APV) {
         mov_write_apvc_tag(mov->fc, pb, track);
     } else if (track->par->codec_id == AV_CODEC_ID_VP9) {
-- 
2.52.0


From 6e85193417f898868f04f8f6fc30db92a874a8cf Mon Sep 17 00:00:00 2001
From: James Almer <[email protected]>
Date: Thu, 23 Jul 2026 10:39:44 -0300
Subject: [PATCH 114/180] avcodec/get_buffer: use frame pixel format instead of
 context

The documentation for get_buffer2() states:

"This callback must use the frame values, not the codec context values, to
calculate the required buffer size."

Fixes wrong output on hbd LCEVC enhanced samples where the base stream is
8bit.

Signed-off-by: James Almer <[email protected]>
(cherry picked from commit e1c1b6c55835d13647636340d478d370aa48fb8d)
---
 libavcodec/get_buffer.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/libavcodec/get_buffer.c b/libavcodec/get_buffer.c
index b391adf24f..1e0cfc21da 100644
--- a/libavcodec/get_buffer.c
+++ b/libavcodec/get_buffer.c
@@ -95,7 +95,7 @@ static int update_frame_pool(AVCodecContext *avctx, AVFrame *frame)
         do {
             // NOTE: do not align linesizes individually, this breaks e.g. assumptions
             // that linesize[0] == 2*linesize[1] in the MPEG-encoder for 4:2:2
-            ret = av_image_fill_linesizes(linesize, avctx->pix_fmt, w);
+            ret = av_image_fill_linesizes(linesize, frame->format, w);
             if (ret < 0)
                 goto fail;
             // increase alignment of w for next try (rhs gives the lowest bit set in w)
@@ -108,7 +108,7 @@ static int update_frame_pool(AVCodecContext *avctx, AVFrame *frame)
 
         for (i = 0; i < 4; i++)
             linesize1[i] = linesize[i];
-        ret = av_image_fill_plane_sizes(size, avctx->pix_fmt, h, linesize1);
+        ret = av_image_fill_plane_sizes(size, frame->format, h, linesize1);
         if (ret < 0)
             goto fail;
 
-- 
2.52.0


From 8d7f63675c0d185c3229fd449fbaef4b73d8e23c Mon Sep 17 00:00:00 2001
From: almogyalin <[email protected]>
Date: Wed, 29 Jul 2026 07:02:38 +0000
Subject: [PATCH 115/180] avformat/iamf_parse: fix inverted subblock duration
 validation

The check on variable subblock durations had an inverted comparison, rejecting valid multi-subblock parameters and failing to catch subblock durations exceeding the remaining time.

(cherry picked from commit 1635b49b80402c8cec39eefb12f35e2ee7c13606)
---
 libavformat/iamf_parse.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libavformat/iamf_parse.c b/libavformat/iamf_parse.c
index 4c2df2c9e6..37d7e04f7b 100644
--- a/libavformat/iamf_parse.c
+++ b/libavformat/iamf_parse.c
@@ -668,7 +668,7 @@ static int param_parse(void *s, IAMFContext *c, AVIOContext *pb,
 
         if (constant_subblock_duration == 0) {
             subblock_duration = ffio_read_leb(pb);
-            if (duration - total_duration > subblock_duration) {
+            if (subblock_duration > duration - total_duration) {
                 av_log(s, AV_LOG_ERROR, "Invalid subblock durations in parameter_id %u\n", parameter_id);
                 av_free(param);
                 return AVERROR_INVALIDDATA;
-- 
2.52.0


From c78ba95dfc8dffc1ff9759d95f6671a7414296da Mon Sep 17 00:00:00 2001
From: James Almer <[email protected]>
Date: Wed, 29 Jul 2026 21:45:37 -0300
Subject: [PATCH 116/180] avcodec/bsf/eac3_core: clear profile value on init()

The output stream no longer has Atmos metadata, so the
AV_PROFILE_EAC3_DDP_ATMOS profile, if it was set, is no longer valid.

Signed-off-by: James Almer <[email protected]>
(cherry picked from commit c77c0e580a39580c085b7182885601015b7f3da4)
---
 libavcodec/bsf/eac3_core.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/libavcodec/bsf/eac3_core.c b/libavcodec/bsf/eac3_core.c
index 4fd0d962d3..da35381951 100644
--- a/libavcodec/bsf/eac3_core.c
+++ b/libavcodec/bsf/eac3_core.c
@@ -23,6 +23,13 @@
 #include "libavcodec/get_bits.h"
 #include "libavcodec/ac3_parser_internal.h"
 
+static int eac3_core_init(AVBSFContext *ctx)
+{
+    ctx->par_out->profile = AV_PROFILE_UNKNOWN;
+
+    return 0;
+}
+
 static int eac3_core_filter(AVBSFContext *ctx, AVPacket *pkt)
 {
     AC3HeaderInfo hdr;
@@ -82,5 +89,6 @@ static const enum AVCodecID codec_ids[] = {
 const FFBitStreamFilter ff_eac3_core_bsf = {
     .p.name      = "eac3_core",
     .p.codec_ids = codec_ids,
+    .init        = eac3_core_init,
     .filter      = eac3_core_filter,
 };
-- 
2.52.0


From 5cc0e1407b9634e0148dc945f2b7f49ab25b3392 Mon Sep 17 00:00:00 2001
From: James Almer <[email protected]>
Date: Wed, 29 Jul 2026 21:45:53 -0300
Subject: [PATCH 117/180] avcodec/bsf/truehd_core: clear profile value on
 init()

The output stream no longer has Atmos metadata, so the
AV_PROFILE_TRUEHD_ATMOS profile, if it was set, is no longer valid.

Fixes issue #23195.

Signed-off-by: James Almer <[email protected]>
(cherry picked from commit 86940d45aff7d59810794df3ab2b39b7b83b478c)
---
 libavcodec/bsf/truehd_core.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/libavcodec/bsf/truehd_core.c b/libavcodec/bsf/truehd_core.c
index bb113593ad..68c95b622c 100644
--- a/libavcodec/bsf/truehd_core.c
+++ b/libavcodec/bsf/truehd_core.c
@@ -34,6 +34,13 @@ typedef struct TrueHDCoreContext {
     MLPHeaderInfo hdr;
 } TrueHDCoreContext;
 
+static int truehd_core_init(AVBSFContext *ctx)
+{
+    ctx->par_out->profile = AV_PROFILE_UNKNOWN;
+
+    return 0;
+}
+
 static int truehd_core_filter(AVBSFContext *ctx, AVPacket *pkt)
 {
     TrueHDCoreContext *s = ctx->priv_data;
@@ -177,6 +184,7 @@ const FFBitStreamFilter ff_truehd_core_bsf = {
     .p.name         = "truehd_core",
     .p.codec_ids    = codec_ids,
     .priv_data_size = sizeof(TrueHDCoreContext),
+    .init           = truehd_core_init,
     .filter         = truehd_core_filter,
     .flush          = truehd_core_flush,
 };
-- 
2.52.0


From e3d0c719fddd259709c8e275942ab56af3afc35d Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Sun, 12 Jul 2026 13:05:07 +0200
Subject: [PATCH 118/180] avfilter/vf_hqdn3d: reject unsupported frame
 parameter changes

Fixes: out of array access
Fixes: 9aj_hqdn3d_dynamic_res.mjpg / 9aj_generate_hqdn3d_dynamic_res_mjpg.py
Fixes: wWDsy2oDvMuR
Found-by: Adrian Junge (vurlo) <[email protected]>
(cherry picked from commit f0f634b6585fdc7bbb43ab3ae461499bfca9ad2e)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavfilter/vf_hqdn3d.c | 34 +++++++++++++++++++++++++---------
 libavfilter/vf_hqdn3d.h |  2 ++
 2 files changed, 27 insertions(+), 9 deletions(-)

diff --git a/libavfilter/vf_hqdn3d.c b/libavfilter/vf_hqdn3d.c
index d880c2bdda..70a2ec4628 100644
--- a/libavfilter/vf_hqdn3d.c
+++ b/libavfilter/vf_hqdn3d.c
@@ -163,12 +163,8 @@ static int denoise_depth(HQDN3DContext *s,
             case 14: ret = denoise_depth(__VA_ARGS__, 14); break;             \
             case 16: ret = denoise_depth(__VA_ARGS__, 16); break;             \
         }                                                                     \
-        if (ret < 0) {                                                        \
-            av_frame_free(&out);                                              \
-            if (!direct)                                                      \
-                av_frame_free(&in);                                           \
+        if (ret < 0)                                                          \
             return ret;                                                       \
-        }                                                                     \
     } while (0)
 
 static void precalc_coefs(double dist25, int depth, int16_t *ct)
@@ -281,12 +277,15 @@ static int config_input(AVFilterLink *inlink)
     ff_hqdn3d_init_x86(s);
 #endif
 
+    s->format = inlink->format;
+    s->width  = inlink->w;
+    s->height = inlink->h;
+
     return 0;
 }
 
 typedef struct ThreadData {
     AVFrame *in, *out;
-    int direct;
 } ThreadData;
 
 static int do_denoise(AVFilterContext *ctx, void *data, int job_nr, int n_jobs)
@@ -295,7 +294,6 @@ static int do_denoise(AVFilterContext *ctx, void *data, int job_nr, int n_jobs)
     const ThreadData *td = data;
     AVFrame *out = td->out;
     AVFrame *in = td->in;
-    int direct = td->direct;
 
     denoise(s, in->data[job_nr], out->data[job_nr],
                 s->line[job_nr], &s->frame_prev[job_nr],
@@ -312,10 +310,21 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in)
 {
     AVFilterContext *ctx  = inlink->dst;
     AVFilterLink *outlink = ctx->outputs[0];
+    HQDN3DContext *s = ctx->priv;
 
     AVFrame *out;
     int direct = av_frame_is_writable(in) && !ctx->is_disabled;
     ThreadData td;
+    int ret[3];
+
+    if (in->format != s->format ||
+        in->width  != s->width  ||
+        in->height != s->height) {
+        av_log(ctx, AV_LOG_ERROR,
+               "Frame size or format changed without filter graph reinitialization\n");
+        av_frame_free(&in);
+        return AVERROR(EINVAL);
+    }
 
     if (direct) {
         out = in;
@@ -331,9 +340,16 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in)
 
     td.in = in;
     td.out = out;
-    td.direct = direct;
     /* one thread per plane */
-    ff_filter_execute(ctx, do_denoise, &td, NULL, 3);
+    ff_filter_execute(ctx, do_denoise, &td, ret, 3);
+    for (int i = 0; i < FF_ARRAY_ELEMS(ret); i++) {
+        if (ret[i] < 0) {
+            av_frame_free(&out);
+            if (!direct)
+                av_frame_free(&in);
+            return ret[i];
+        }
+    }
 
     if (ctx->is_disabled) {
         av_frame_free(&out);
diff --git a/libavfilter/vf_hqdn3d.h b/libavfilter/vf_hqdn3d.h
index 3279bbcc77..3467f27145 100644
--- a/libavfilter/vf_hqdn3d.h
+++ b/libavfilter/vf_hqdn3d.h
@@ -36,6 +36,8 @@ typedef struct HQDN3DContext {
     double strength[4];
     int hsub, vsub;
     int depth;
+    int width, height;
+    enum AVPixelFormat format;
     void (*denoise_row[17])(uint8_t *src, uint8_t *dst, uint16_t *line_ant, uint16_t *frame_ant, ptrdiff_t w, int16_t *spatial, int16_t *temporal);
 } HQDN3DContext;
 
-- 
2.52.0


From 62294b6a8ad2370e1435bb9985ebe6b53be14c2b Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Sun, 12 Jul 2026 13:05:33 +0200
Subject: [PATCH 119/180] avfilter/vf_hqdn3d: support dynamic frame sizes

(cherry picked from commit 5d7112c60e6f0f0742ce47d448e6da0718a70f4c)
---
 libavfilter/avfilter.c  |  3 ++-
 libavfilter/vf_hqdn3d.c | 21 ++++++++++++++-------
 2 files changed, 16 insertions(+), 8 deletions(-)

diff --git a/libavfilter/avfilter.c b/libavfilter/avfilter.c
index b15f0b08b4..2715f658b3 100644
--- a/libavfilter/avfilter.c
+++ b/libavfilter/avfilter.c
@@ -1078,7 +1078,8 @@ int ff_filter_frame(AVFilterLink *link, AVFrame *frame)
             strcmp(link->dst->filter->name, "idet") &&
             strcmp(link->dst->filter->name, "null") &&
             strcmp(link->dst->filter->name, "scale") &&
-            strcmp(link->dst->filter->name, "libplacebo")) {
+            strcmp(link->dst->filter->name, "libplacebo") &&
+            strcmp(link->dst->filter->name, "hqdn3d")) {
             av_assert1(frame->format        == link->format);
             av_assert1(frame->width         == link->w);
             av_assert1(frame->height        == link->h);
diff --git a/libavfilter/vf_hqdn3d.c b/libavfilter/vf_hqdn3d.c
index 70a2ec4628..98c05e886c 100644
--- a/libavfilter/vf_hqdn3d.c
+++ b/libavfilter/vf_hqdn3d.c
@@ -315,21 +315,28 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in)
     AVFrame *out;
     int direct = av_frame_is_writable(in) && !ctx->is_disabled;
     ThreadData td;
-    int ret[3];
+    int err, ret[3];
 
-    if (in->format != s->format ||
-        in->width  != s->width  ||
-        in->height != s->height) {
-        av_log(ctx, AV_LOG_ERROR,
-               "Frame size or format changed without filter graph reinitialization\n");
+    if (in->format != s->format) {
         av_frame_free(&in);
         return AVERROR(EINVAL);
     }
 
+    if (in->width != s->width || in->height != s->height) {
+        inlink->w = in->width;
+        inlink->h = in->height;
+        if ((err = config_input(inlink)) < 0) {
+            av_frame_free(&in);
+            return err;
+        }
+        outlink->w = in->width;
+        outlink->h = in->height;
+    }
+
     if (direct) {
         out = in;
     } else {
-        out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
+        out = ff_get_video_buffer(outlink, in->width, in->height);
         if (!out) {
             av_frame_free(&in);
             return AVERROR(ENOMEM);
-- 
2.52.0


From 107072ee9b9cce91cf6ca1f66c04fccd6cfcd77f Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Fri, 10 Jul 2026 04:07:17 +0200
Subject: [PATCH 120/180] avfilter/vf_lut3d: do not compute size*size before
 the size is validated

parse_dat(), parse_cube(), and parse_cinespace() multiply an untrusted LUT size
before allocate_3dlut() validates it, which can overflow int.
Use the validated lutsize2 computed by allocate_3dlut() instead.

Fixes: signed integer overflow
(cherry picked from commit 92cd5c97817be98361137e12fdba1f13db20f5c9)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavfilter/vf_lut3d.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/libavfilter/vf_lut3d.c b/libavfilter/vf_lut3d.c
index 9071dabeb9..2c78507b7e 100644
--- a/libavfilter/vf_lut3d.c
+++ b/libavfilter/vf_lut3d.c
@@ -651,11 +651,11 @@ static int parse_dat(AVFilterContext *ctx, FILE *f)
 
         NEXT_LINE(skip_line(line));
     }
-    size2 = size * size;
 
     ret = allocate_3dlut(ctx, size, 0);
     if (ret < 0)
         return ret;
+    size2 = lut3d->lutsize2;
 
     for (k = 0; k < size; k++) {
         for (j = 0; j < size; j++) {
@@ -681,13 +681,13 @@ static int parse_cube(AVFilterContext *ctx, FILE *f)
 
     while (fgets(line, sizeof(line), f)) {
         if (!strncmp(line, "LUT_3D_SIZE", 11)) {
-            int ret, i, j, k;
+            int ret, i, j, k, size2;
             const int size = strtol(line + 12, NULL, 0);
-            const int size2 = size * size;
 
             ret = allocate_3dlut(ctx, size, 0);
             if (ret < 0)
                 return ret;
+            size2 = lut3d->lutsize2;
 
             for (k = 0; k < size; k++) {
                 for (j = 0; j < size; j++) {
@@ -999,7 +999,6 @@ static int parse_cinespace(AVFilterContext *ctx, FILE *f)
             }
 
             size = size_r;
-            size2 = size * size;
 
             if (prelut_sizes[0] && prelut_sizes[1] && prelut_sizes[2])
                 prelut = 1;
@@ -1007,6 +1006,7 @@ static int parse_cinespace(AVFilterContext *ctx, FILE *f)
             ret = allocate_3dlut(ctx, size, prelut);
             if (ret < 0)
                 goto end;
+            size2 = lut3d->lutsize2;
 
             for (int k = 0; k < size; k++) {
                 for (int j = 0; j < size; j++) {
-- 
2.52.0


From 0e5fd5f5f6f2007af6c21f7c91ca883043353dfc Mon Sep 17 00:00:00 2001
From: Steven Liu <[email protected]>
Date: Thu, 9 Jul 2026 13:12:26 +0800
Subject: [PATCH 121/180] avformat/hlsenc: Handle extensionless URIs in
 extract_segment_number()

fix: vulnerability:019f3b84-90fb-737a-ae7a-b3e0219266fc
extract_segment_number() used strrchr(filename, '.') and immediately did
dot - 1 without checking for NULL. A segment URI with no extension would
cause undefined behavior (NULL pointer arithmetic) when resuming an
append_list playlist with subtitle variants.

Add a NULL check and return -1 early when no dot is found.

Fixes a crash/UB in HLS muxer resume path.

Found-by: depthfirst
Signed-off-by: Steven Liu <[email protected]>
(cherry picked from commit 601d9ee881fbd9d9ff44466c561c480ff244eb9f)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavformat/hlsenc.c | 27 +++++++++++++++++++--------
 1 file changed, 19 insertions(+), 8 deletions(-)

diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c
index 0b69ae4b3b..9db9294d03 100644
--- a/libavformat/hlsenc.c
+++ b/libavformat/hlsenc.c
@@ -1140,20 +1140,31 @@ static int hls_append_segment(struct AVFormatContext *s, HLSContext *hls,
     return 0;
 }
 
-static int extract_segment_number(const char *filename) {
+static int extract_segment_number(const char *filename)
+{
     const char *dot = strrchr(filename, '.');
-    const char *num_start = dot - 1;
+    const char *num_start;
+    char *end;
+    long value;
 
-    while (num_start > filename && *num_start >= '0' && *num_start <= '9') {
+    if (!dot)
+        return -1;
+    if (dot == filename)
+        return -1;
+
+    num_start = dot;
+    while (num_start > filename &&
+           num_start[-1] >= '0' && num_start[-1] <= '9')
         num_start--;
-    }
-
-    num_start++;
-
     if (num_start == dot)
         return -1;
 
-    return atoi(num_start);
+    errno = 0;
+    value = strtol(num_start, &end, 10);
+    if (errno == ERANGE || end != dot || value > INT_MAX)
+        return -1;
+
+    return (int)value;
 }
 
 static int parse_playlist(AVFormatContext *s, const char *url, VariantStream *vs)
-- 
2.52.0


From f6c97aeadc33c82e17f0ef2dda916cb355f8940e Mon Sep 17 00:00:00 2001
From: Steven Liu <[email protected]>
Date: Thu, 9 Jul 2026 13:01:31 +0800
Subject: [PATCH 122/180] avformat/hlsenc: Fix heap buffer overflow in
 parse_playlist()

Fix: vulnerability:019f3b84-903b-75fb-a8de-fe6f84d6bc32

When parsing IV=0x... followed by a comma, end - ptr was passed directly
to av_strlcpy() as the destination size. Since iv_string is only 33 bytes
(KEYSIZE*2 + 1), a long IV token could overflow into adjacent heap data.

Use FFMIN(end - ptr + 1, sizeof(buf)) to cap the copy size to the actual
buffer size. Apply the same fix to key_uri parsing for consistency.

Fixes a heap buffer overflow in append_list mode when reading an existing
playlist with a crafted IV or URI token length.

Found-by: depthfirst
Signed-off-by: Steven Liu <[email protected]>
(cherry picked from commit 275e217b10057e812039c722d420b3ba996ae69a)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavformat/hlsenc.c | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c
index 9db9294d03..50f19f9694 100644
--- a/libavformat/hlsenc.c
+++ b/libavformat/hlsenc.c
@@ -30,6 +30,7 @@
 
 #include "libavutil/attributes_internal.h"
 #include "libavutil/avassert.h"
+#include "libavutil/macros.h"
 #include "libavutil/mathematics.h"
 #include "libavutil/avstring.h"
 #include "libavutil/bprint.h"
@@ -1211,11 +1212,13 @@ static int parse_playlist(AVFormatContext *s, const char *url, VariantStream *vs
             ptr = av_stristr(line, "URI=\"");
             if (ptr) {
                 ptr += strlen("URI=\"");
-                end = av_stristr(ptr, ",");
+                end = strchr(ptr, '"');
                 if (end) {
-                    av_strlcpy(vs->key_uri, ptr, end - ptr);
+                    av_strlcpy(vs->key_uri, ptr,
+                               FFMIN(end - ptr + 1, sizeof(vs->key_uri)));
                 } else {
-                    av_strlcpy(vs->key_uri, ptr, sizeof(vs->key_uri));
+                    ret = AVERROR_INVALIDDATA;
+                    goto fail;
                 }
             }
 
@@ -1224,7 +1227,7 @@ static int parse_playlist(AVFormatContext *s, const char *url, VariantStream *vs
                 ptr += strlen("IV=0x");
                 end = av_stristr(ptr, ",");
                 if (end) {
-                    av_strlcpy(vs->iv_string, ptr, end - ptr);
+                    av_strlcpy(vs->iv_string, ptr, FFMIN(end - ptr + 1, sizeof(vs->iv_string)));
                 } else {
                     av_strlcpy(vs->iv_string, ptr, sizeof(vs->iv_string));
                 }
-- 
2.52.0


From 01044d04536eec6e2f5f48ef404cf45d15feb461 Mon Sep 17 00:00:00 2001
From: Steven Liu <[email protected]>
Date: Thu, 9 Jul 2026 13:04:34 +0800
Subject: [PATCH 123/180] avformat/hls: Enforce protocol checks when opening
 child playlists

Fix: vulnerability:019f3b84-903b-75fb-a8de-fad1c2d7e99c
The parse_playlist() path for opening a new child playlist (in == NULL)
directly called c->ctx->io_open(), bypassing the protocol whitelist and
file-extension checks that open_url() enforces. This allowed child URLs
from a master playlist to use protocols other than file/http/data or to
have disallowed extensions.

Route the !in branch through open_url() so that all child playlists are
subject to the same security policy as segment URLs.

Fixes a security bypass in HLS master playlist parsing.

Found-by: depthfirst
Signed-off-by: Steven Liu <[email protected]>
(cherry picked from commit 23602df9cd1b485c45ba6f533d3b85569de3f323)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavformat/hls.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libavformat/hls.c b/libavformat/hls.c
index e76e68ed51..0e1a1ba49c 100644
--- a/libavformat/hls.c
+++ b/libavformat/hls.c
@@ -857,7 +857,7 @@ static int parse_playlist(HLSContext *c, const char *url,
         if (c->http_persistent)
             av_dict_set(&opts, "multiple_requests", "1", 0);
 
-        ret = c->ctx->io_open(c->ctx, &in, url, AVIO_FLAG_READ, &opts);
+        ret = open_url(c->ctx, &in, url, &opts, NULL, NULL);
         av_dict_free(&opts);
         if (ret < 0)
             return ret;
-- 
2.52.0


From c22667d0fd7916a33fd3e79685b7246fc48f1a62 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Wed, 22 Jul 2026 05:44:41 +0200
Subject: [PATCH 124/180] avcodec/screenpresso: reject deflate output shorter
 than the frame

Fixes: use of uninitialized memory
Fixes: screenpresso_short_zlib_heap_disclosure.avi
Fixes: ksUBwBOjJodq
Found-by: Adrian Junge (vurlo)
(cherry picked from commit 705890061467ad550ecc1dad5eea07f28ccfb43e)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/screenpresso.c | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/libavcodec/screenpresso.c b/libavcodec/screenpresso.c
index b27154991c..5864253d41 100644
--- a/libavcodec/screenpresso.c
+++ b/libavcodec/screenpresso.c
@@ -137,6 +137,9 @@ static int screenpresso_decode_frame(AVCodecContext *avctx, AVFrame *frame,
         return AVERROR_INVALIDDATA;
     }
 
+    /* Codec has aligned strides */
+    src_linesize = FFALIGN(avctx->width * component_size, 4);
+
     /* Inflate the frame after the 2 byte header */
     ret = uncompress(ctx->inflated_buf, &length,
                      avpkt->data + 2, avpkt->size - 2);
@@ -144,14 +147,16 @@ static int screenpresso_decode_frame(AVCodecContext *avctx, AVFrame *frame,
         av_log(avctx, AV_LOG_ERROR, "Deflate error %d.\n", ret);
         return AVERROR_UNKNOWN;
     }
+    if (length < src_linesize * avctx->height) {
+        av_log(avctx, AV_LOG_ERROR, "Deflated %lu bytes, but %d are needed\n",
+               length, src_linesize * avctx->height);
+        return AVERROR_INVALIDDATA;
+    }
 
     ret = ff_reget_buffer(avctx, ctx->current, 0);
     if (ret < 0)
         return ret;
 
-    /* Codec has aligned strides */
-    src_linesize = FFALIGN(avctx->width * component_size, 4);
-
     /* When a keyframe is found, copy it (flipped) */
     if (keyframe)
         av_image_copy_plane(ctx->current->data[0] +
-- 
2.52.0


From 8553e6ef5785ecc15733da84b49dea85d7b59dc1 Mon Sep 17 00:00:00 2001
From: Kenan Alghythee <[email protected]>
Date: Wed, 22 Jul 2026 19:20:46 +0200
Subject: [PATCH 125/180] avcodec/cbs_av1: pad the ITU-T T.35 payload buffer

Fixes: out of array access
Fixes: SUcVEyk7r3Gc
Found-by: Kenan Alghythee <[email protected]>
(cherry picked from commit e2bc6d88cd68184288c16570e04881c3a426a05a)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/cbs_av1_syntax_template.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/libavcodec/cbs_av1_syntax_template.c b/libavcodec/cbs_av1_syntax_template.c
index f3d84f3c05..e6fd4ff678 100644
--- a/libavcodec/cbs_av1_syntax_template.c
+++ b/libavcodec/cbs_av1_syntax_template.c
@@ -2011,10 +2011,12 @@ static int FUNC(metadata_itut_t35)(CodedBitstreamContext *ctx, RWContext *rw,
     // be arbitrarily many trailing zeroes so we need to read through twice.
     current->payload_size = cbs_av1_get_payload_bytes_left(rw);
 
-    current->payload_ref = av_buffer_alloc(current->payload_size);
+    current->payload_ref = av_buffer_alloc(current->payload_size +
+                                           AV_INPUT_BUFFER_PADDING_SIZE);
     if (!current->payload_ref)
         return AVERROR(ENOMEM);
     current->payload = current->payload_ref->data;
+    memset(current->payload + current->payload_size, 0, AV_INPUT_BUFFER_PADDING_SIZE);
 #endif
 
     for (i = 0; i < current->payload_size; i++)
-- 
2.52.0


From 3257728c6179b41689642b841ce45c42fa5054ce Mon Sep 17 00:00:00 2001
From: Kenan Alghythee <[email protected]>
Date: Sat, 11 Jul 2026 16:53:41 +0200
Subject: [PATCH 126/180] avfilter/vf_xpsnr: avoid a zero block size on small
 frames

Fixes: division by zero
Fixes: SUcVEyk7r3Gc
Found-by: Kenan Alghythee <[email protected]>
(cherry picked from commit 06fd4fd0e787e819fd697dc687efe7f7eae798c8)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavfilter/vf_xpsnr.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/libavfilter/vf_xpsnr.c b/libavfilter/vf_xpsnr.c
index 34c875456e..89f4f279ba 100644
--- a/libavfilter/vf_xpsnr.c
+++ b/libavfilter/vf_xpsnr.c
@@ -273,7 +273,7 @@ static int get_wsse(AVFilterContext *ctx, int16_t **org, int16_t *org_m1,
     const uint32_t       w = s->plane_width [0]; /* luma image width in pixels */
     const uint32_t       h = s->plane_height[0];/* luma image height in pixels */
     const double         r = (double)(w * h) / (3840.0 * 2160.0); /* UHD ratio */
-    const uint32_t       b = FFMAX(0, 4 * (int32_t) (32.0 * sqrt(r) +
+    const uint32_t       b = FFMAX(1, 4 * (int32_t) (32.0 * sqrt(r) +
                                                      0.5)); /* block size, integer multiple of 4 for SIMD */
     const uint32_t   w_blk = (w + b - 1) / b; /* luma width in units of blocks */
     const double   avg_act = sqrt(16.0 * (double) (1 << (2 * s->depth - 9)) / sqrt(FFMAX(0.00001,
@@ -399,7 +399,7 @@ static int do_xpsnr(FFFrameSync *fs)
     XPSNRContext *const s = ctx->priv;
     const uint32_t      w = s->plane_width [0];  /* luma image width in pixels */
     const uint32_t      h = s->plane_height[0]; /* luma image height in pixels */
-    const uint32_t      b = FFMAX(0, 4 * (int32_t) (32.0 * sqrt((double) (w * h) / (3840.0 * 2160.0)) + 0.5)); /* block size */
+    const uint32_t      b = FFMAX(1, 4 * (int32_t) (32.0 * sqrt((double) (w * h) / (3840.0 * 2160.0)) + 0.5)); /* block size */
     const uint32_t  w_blk = (w + b - 1) / b;  /* luma width in units of blocks */
     const uint32_t  h_blk = (h + b - 1) / b; /* luma height in units of blocks */
     AVFrame *master, *ref = NULL;
-- 
2.52.0


From 4af563686fa283e2e6138e38c3d72e91c9bdf851 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Sat, 11 Jul 2026 21:21:57 +0200
Subject: [PATCH 127/180] avformat/codec2: avoid integer overflow in packet
 size and duration

Fixes: signed integer overflow
Fixes: 2jy_poc_codec2.zip / poc_codec2.raw
Fixes: jOQASNnOm6O7
Found-by: Jiale Yao <[email protected]>
(cherry picked from commit 2b7e5012424a52998cd6a1fe3556272313cb7527)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavformat/codec2.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/libavformat/codec2.c b/libavformat/codec2.c
index dcc3ed9e59..0791b61b35 100644
--- a/libavformat/codec2.c
+++ b/libavformat/codec2.c
@@ -198,6 +198,8 @@ static int codec2_read_packet(AVFormatContext *s, AVPacket *pkt)
     }
 
     //try to read desired number of frames, compute n from to actual number of bytes read
+    if (c2->frames_per_packet > INT_MAX / block_align)
+        return AVERROR(EINVAL);
     size = c2->frames_per_packet * block_align;
     ret = av_get_packet(s->pb, pkt, size);
     if (ret < 0) {
@@ -207,7 +209,7 @@ static int codec2_read_packet(AVFormatContext *s, AVPacket *pkt)
     //only set duration - compute_pkt_fields() and ff_pcm_read_seek() takes care of everything else
     //tested by spamming the seek functionality in ffplay
     n = ret / block_align;
-    pkt->duration = n * frame_size;
+    pkt->duration = (int64_t)n * frame_size;
 
     return ret;
 }
-- 
2.52.0


From 9718986bf02a6936e0a8672254b847bec7323979 Mon Sep 17 00:00:00 2001
From: Omkhar Arasaratnam <[email protected]>
Date: Fri, 5 Jun 2026 07:21:29 -0700
Subject: [PATCH 128/180] avformat/tls_openssl: bind peer identity for
 numeric-IP verify

tls_open() installed a peer-identity target only when the URL host was
non-numeric. For a numeric-IP target the whole block was skipped, so
with verify=1 OpenSSL validated the chain to a trusted CA but bound no
identity, and any publicly-trusted certificate for any name was accepted
(e.g. tls://203.0.113.10?verify=1).

Run the identity block for every non-listening connection. Classify
s->host with the same getaddrinfo(AI_NUMERICHOST) rule tls.c uses. A
numeric host is pinned to the certificate's iPAddress SAN by handing
OpenSSL the parsed binary address via X509_VERIFY_PARAM_set1_ip();
everything else, including a verifyhost=<name> override, binds by name
via SSL_set1_host(). Passing the binary address rather than re-probing
the ASCII form pins legacy numeric spellings such as 2130706433 as IPs
instead of letting them fall back to hostname matching. SNI is still
suppressed for numeric transport hosts (RFC 6066 sec. 3) via
s->numerichost.

The identity target is installed unconditionally for non-listening
connections; whether the certificate is actually verified stays
controlled separately by SSL_CTX_set_verify() under s->verify, so
disabling verification keeps the prior behaviour. Sets AVERROR_EXTERNAL
with an explicit log line on failure.

dtls_open() sets s->is_dtls and then calls tls_open(), so DTLS client
connections run through the same identity block and are covered here.

Found-by: Claude (Anthropic). Human-verified and reported by
Omkhar Arasaratnam <[email protected]>.
Signed-off-by: Omkhar Arasaratnam <[email protected]>
(cherry picked from commit 83c692282631e6d035fa52d2e2c5118b1d2a27d7)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavformat/tls_openssl.c | 32 +++++++++++++++++++++++++++-----
 1 file changed, 27 insertions(+), 5 deletions(-)

diff --git a/libavformat/tls_openssl.c b/libavformat/tls_openssl.c
index 5d3be96fbb..48b4a2226a 100644
--- a/libavformat/tls_openssl.c
+++ b/libavformat/tls_openssl.c
@@ -851,16 +851,38 @@ static int tls_open(URLContext *h, const char *uri, int flags, AVDictionary **op
     }
 
     init_bio_method(h);
-    if (!s->listen && !s->numerichost) {
+    if (!s->listen) {
+        // Pin a numeric host to the certificate's iPAddress SAN and everything else
+        // to the hostname. Classify s->host with the same AI_NUMERICHOST rule tls.c
+        // uses and hand OpenSSL the binary address, so legacy numeric forms (e.g.
+        // 2130706433) are pinned as IPs instead of falling back to hostname matching.
+        // A verifyhost=<name> override leaves s->host non-numeric and binds by name.
+        struct addrinfo hints = { .ai_flags = AI_NUMERICHOST }, *ai = NULL;
+        int is_numeric_host = !getaddrinfo(s->host, NULL, &hints, &ai);
+        int ok;
+
         // By default OpenSSL does too lax wildcard matching
         SSL_set_hostflags(c->ssl, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
-        if (!SSL_set1_host(c->ssl, s->host)) {
-            av_log(h, AV_LOG_ERROR, "Failed to set hostname for TLS/SSL verification: %s\n",
-                openssl_get_error(c));
+        if (is_numeric_host) {
+            void *addr = ai->ai_family == AF_INET6 ?
+                (void *)&((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr :
+                (void *)&((struct sockaddr_in  *)ai->ai_addr)->sin_addr;
+            ok = X509_VERIFY_PARAM_set1_ip(SSL_get0_param(c->ssl), addr,
+                                           ai->ai_family == AF_INET6 ? 16 : 4);
+        } else {
+            ok = SSL_set1_host(c->ssl, s->host);
+        }
+        if (ai)
+            freeaddrinfo(ai);
+        if (!ok) {
+            av_log(h, AV_LOG_ERROR, "Failed to set %s for TLS/SSL verification: %s\n",
+                is_numeric_host ? "IP" : "hostname", openssl_get_error(c));
             ret = AVERROR_EXTERNAL;
             goto fail;
         }
-        if (!SSL_set_tlsext_host_name(c->ssl, s->host)) {
+        // SNI MUST NOT carry a literal IP address (RFC 6066 sec. 3); suppress it for
+        // numeric transport hosts, matching the GnuTLS backend.
+        if (!s->numerichost && !SSL_set_tlsext_host_name(c->ssl, s->host)) {
             av_log(h, AV_LOG_ERROR, "Failed to set hostname for SNI: %s\n", openssl_get_error(c));
             ret = AVERROR_EXTERNAL;
             goto fail;
-- 
2.52.0


From 02fc47e13f903768b75f7985a2706a6223ab4506 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Wed, 22 Jul 2026 05:49:11 +0200
Subject: [PATCH 129/180] avcodec/dvbsub_parser: avoid signed overflow in the
 capacity check

Fixes: signed integer overflow
Fixes: out of array access
Fixes: poc.wtv
Fixes: fJeEU9JwKwsR
Found-by: Adrian Junge (vurlo)
(cherry picked from commit 93f2a525ec6c7b467bae68322720d10188fc6e30)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/dvbsub_parser.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libavcodec/dvbsub_parser.c b/libavcodec/dvbsub_parser.c
index 4527e4dd75..a93f39bfe0 100644
--- a/libavcodec/dvbsub_parser.c
+++ b/libavcodec/dvbsub_parser.c
@@ -104,7 +104,7 @@ static int dvbsub_parse(AVCodecParserContext *s,
         }
     }
 
-    if (buf_size - buf_pos + pc->packet_index > PARSE_BUF_SIZE)
+    if (buf_size - buf_pos > PARSE_BUF_SIZE - pc->packet_index)
         return buf_size;
 
 /* if not currently in a packet, pass data */
-- 
2.52.0


From a5fe21a1a410a680fe93c33b0dd696b7e1c3aea4 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Wed, 22 Jul 2026 05:44:03 +0200
Subject: [PATCH 130/180] avcodec/rscc: do not leave uninitilized data when the
 input is too short

Fixes: use of uninitialized memory
Fixes: rscc_short_deflate_heap_disclosure.avi
Fixes: plB80py3i3Bu
Found-by: Adrian Junge (vurlo)
(cherry picked from commit cd1f545cf27ba08f6f5b31b1e92665d7874d4fd7)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/rscc.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/libavcodec/rscc.c b/libavcodec/rscc.c
index 3715e1c6d4..5fde30ec35 100644
--- a/libavcodec/rscc.c
+++ b/libavcodec/rscc.c
@@ -311,6 +311,12 @@ static int rscc_decode_frame(AVCodecContext *avctx, AVFrame *frame,
             ret = AVERROR_UNKNOWN;
             goto end;
         }
+        if (len < pixel_size) {
+            av_log(avctx, AV_LOG_WARNING, "Deflated %lu bytes, but %d are needed\n",
+                   len, pixel_size);
+            memset(ctx->inflated_buf + len, 0, pixel_size - len);
+            pixel_size = len;
+        }
         pixels = ctx->inflated_buf;
     }
 
-- 
2.52.0


From dd22741d7129b49ae3870ac4167188699a371b02 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Wed, 22 Jul 2026 06:17:01 +0200
Subject: [PATCH 131/180] avformat/mov: bound sgpd sync entry_count by the atom
 size

Fixes: unbounded allocation
Fixes: ffmpeg-sgpd-sync-entry-count-alloc.mp4
Fixes: 4f7OaHqbUKh2
Found-by: Brian Lee, Youngjoon Kim (Georgia Tech SSLab)
(cherry picked from commit a100d346dafd012bfc0c20098a764c6d01a05714)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavformat/mov.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/libavformat/mov.c b/libavformat/mov.c
index 98e5e4de9a..508ea97637 100644
--- a/libavformat/mov.c
+++ b/libavformat/mov.c
@@ -3949,6 +3949,9 @@ static int mov_read_sgpd(MOVContext *c, AVIOContext *pb, MOVAtom atom)
     default_group_description_index = version >= 2 ? avio_rb32(pb) : 0;
     entry_count = avio_rb32(pb);
 
+    if (entry_count > atom.size)
+        return AVERROR_INVALIDDATA;
+
     av_freep(&sc->sgpd_sync);
     sc->sgpd_sync_count = entry_count;
     sc->sgpd_sync = av_calloc(entry_count, sizeof(*sc->sgpd_sync));
-- 
2.52.0


From 031213bb6c55a65f5af51653f1116ba4f6eca134 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Tue, 28 Jul 2026 07:02:18 +0200
Subject: [PATCH 132/180] doc/nut.texi: point at the latest spec in the git
 repository

subversion in 2026 is a bit odd

(cherry picked from commit 05be98e3a2ea5c8dfc102860bd96a12a84b93346)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 doc/nut.texi | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/doc/nut.texi b/doc/nut.texi
index d3a5b8de39..ff5e3456ed 100644
--- a/doc/nut.texi
+++ b/doc/nut.texi
@@ -18,7 +18,7 @@ subtitle and user-defined streams in a simple, yet efficient, way.
 It was created by a group of FFmpeg and MPlayer developers in 2003
 and was finalized in 2008.
 
-The official nut specification is at svn://svn.mplayerhq.hu/nut
+The official nut specification is at https://git.ffmpeg.org/gitweb/nut.git/blob/refs/heads/master:/docs/nut.txt
 In case of any differences between this text and the official specification,
 the official specification shall prevail.
 
-- 
2.52.0


From 3b962c40dd511414f04b26fae64fcc8ff349bbd0 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Sat, 11 Jul 2026 16:59:59 +0200
Subject: [PATCH 133/180] avfilter/af_arnndn: pad the DCT input buffers to the
 read length

Fixes: out of array access
Fixes: SUcVEyk7r3Gc
Found-by: Kenan Alghythee <[email protected]>
(cherry picked from commit e38b5d15bd93585f96c75e31e2dd1e1fe9294d05)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavfilter/af_arnndn.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/libavfilter/af_arnndn.c b/libavfilter/af_arnndn.c
index 5d516f0b15..6209f1b0e0 100644
--- a/libavfilter/af_arnndn.c
+++ b/libavfilter/af_arnndn.c
@@ -1023,7 +1023,7 @@ static int compute_frame_features(AudioRNNContext *s, DenoiseState *st, AVComple
     float E = 0;
     float *ceps_0, *ceps_1, *ceps_2;
     float spec_variability = 0;
-    LOCAL_ALIGNED_32(float, Ly, [NB_BANDS]);
+    LOCAL_ALIGNED_32(float, Ly, [FFALIGN(NB_BANDS, 4)]);
     LOCAL_ALIGNED_32(float, p, [WINDOW_SIZE]);
     float pitch_buf[PITCH_BUF_SIZE>>1];
     int pitch_index;
@@ -1057,6 +1057,7 @@ static int compute_frame_features(AudioRNNContext *s, DenoiseState *st, AVComple
     for (int i = 0; i < NB_BANDS; i++)
         Exp[i] = Exp[i] / sqrtf(.001f+Ex[i]*Ep[i]);
 
+    memset(Exp + NB_BANDS, 0, (FFALIGN(NB_BANDS, 4) - NB_BANDS) * sizeof(*Exp));
     dct(s, tmp, Exp);
 
     for (int i = 0; i < NB_DELTA_CEPS; i++)
@@ -1082,6 +1083,7 @@ static int compute_frame_features(AudioRNNContext *s, DenoiseState *st, AVComple
         return 1;
     }
 
+    memset(Ly + NB_BANDS, 0, (FFALIGN(NB_BANDS, 4) - NB_BANDS) * sizeof(*Ly));
     dct(s, features, Ly);
     features[0] -= 12;
     features[1] -= 4;
@@ -1364,7 +1366,7 @@ static float rnnoise_channel(AudioRNNContext *s, DenoiseState *st, float *out, c
     AVComplexFloat P[WINDOW_SIZE];
     float x[FRAME_SIZE];
     float Ex[NB_BANDS], Ep[NB_BANDS];
-    LOCAL_ALIGNED_32(float, Exp, [NB_BANDS]);
+    LOCAL_ALIGNED_32(float, Exp, [FFALIGN(NB_BANDS, 4)]);
     float features[NB_FEATURES];
     float g[NB_BANDS];
     float gf[FREQ_SIZE];
-- 
2.52.0


From 5d82a3c3695fe9d7dc051f2a0502be8b646094ab Mon Sep 17 00:00:00 2001
From: Kenan Alghythee <[email protected]>
Date: Wed, 22 Jul 2026 23:48:29 +0200
Subject: [PATCH 134/180] avcodec/dovi_rpuenc: validate the data mapping before
 generation

Fixes: out of array access
Fixes: SUcVEyk7r3Gc
Found-by: Kenan Alghythee <[email protected]>
(cherry picked from commit f1ed338541d7596f5cd7721288049745ec5265f5)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/dovi_rpuenc.c | 86 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 86 insertions(+)

diff --git a/libavcodec/dovi_rpuenc.c b/libavcodec/dovi_rpuenc.c
index dc63c68405..fe95bd4262 100644
--- a/libavcodec/dovi_rpuenc.c
+++ b/libavcodec/dovi_rpuenc.c
@@ -367,6 +367,87 @@ static inline void put_se_coef(PutBitContext *pb, const AVDOVIRpuDataHeader *hdr
     }
 }
 
+static int validate_ue_golomb_value(uint64_t value)
+{
+    return value <= 0xFFFE;
+}
+
+static int validate_se_golomb_value(int64_t value)
+{
+    return value >= -0x7FFF && value <= 0x7FFF;
+}
+
+static int validate_ue_coef(const AVDOVIRpuDataHeader *hdr, uint64_t coef)
+{
+    if (hdr->coef_log2_denom >= 63)
+        return 0;
+    return validate_ue_golomb_value(coef >> hdr->coef_log2_denom);
+}
+
+static int validate_se_coef(const AVDOVIRpuDataHeader *hdr, int64_t coef)
+{
+    if (hdr->coef_log2_denom >= 63)
+        return 0;
+    return validate_se_golomb_value(coef >> hdr->coef_log2_denom);
+}
+
+static int validate_mapping_for_generation(const AVDOVIRpuDataHeader *hdr,
+                                           const AVDOVIDataMapping *mapping)
+{
+    if (!mapping->num_x_partitions || mapping->num_x_partitions > 0xFFFF ||
+        !mapping->num_y_partitions || mapping->num_y_partitions > 0xFFFF)
+        return 0;
+
+    for (int c = 0; c < 3; c++) {
+        const AVDOVIReshapingCurve *curve = &mapping->curves[c];
+
+        if (curve->num_pivots < 2 || curve->num_pivots > AV_DOVI_MAX_PIECES + 1)
+            return 0;
+
+        for (int i = 1; i < curve->num_pivots; i++)
+            if (curve->pivots[i] < curve->pivots[i - 1])
+                return 0;
+
+        for (int i = 0; i < curve->num_pivots - 1; i++) {
+            switch (curve->mapping_idc[i]) {
+            case AV_DOVI_MAPPING_POLYNOMIAL:
+                if (curve->poly_order[i] < 1 || curve->poly_order[i] > 2)
+                    return 0;
+                for (int k = 0; k <= curve->poly_order[i]; k++)
+                    if (!validate_se_coef(hdr, curve->poly_coef[i][k]))
+                        return 0;
+                break;
+            case AV_DOVI_MAPPING_MMR:
+                if (curve->mmr_order[i] < 1 || curve->mmr_order[i] > 3)
+                    return 0;
+                if (!validate_se_coef(hdr, curve->mmr_constant[i]))
+                    return 0;
+                for (int j = 0; j < curve->mmr_order[i]; j++)
+                    for (int k = 0; k < 7; k++)
+                        if (!validate_se_coef(hdr, curve->mmr_coef[i][j][k]))
+                            return 0;
+                break;
+            default:
+                return 0;
+            }
+        }
+    }
+
+    if (mapping->nlq_method_idc != AV_DOVI_NLQ_NONE) {
+        if (mapping->nlq_method_idc != AV_DOVI_NLQ_LINEAR_DZ)
+            return 0;
+        for (int c = 0; c < 3; c++) {
+            const AVDOVINLQParams *nlq = &mapping->nlq[c];
+            if (!validate_ue_coef(hdr, nlq->vdr_in_max) ||
+                !validate_ue_coef(hdr, nlq->linear_deadzone_slope) ||
+                !validate_ue_coef(hdr, nlq->linear_deadzone_threshold))
+                return 0;
+        }
+    }
+
+    return 1;
+}
+
 static int av_q2den(AVRational q, int den)
 {
     if (!q.den || q.den == den)
@@ -595,6 +676,11 @@ int ff_dovi_rpu_generate(DOVIContext *s, const AVDOVIMetadata *metadata,
         return AVERROR_INVALIDDATA;
     }
 
+    if (!validate_mapping_for_generation(hdr, mapping)) {
+        av_log(s->logctx, AV_LOG_ERROR, "Coefficient out of range for RPU\n");
+        return AVERROR_INVALIDDATA;
+    }
+
     if (!(flags & FF_DOVI_COMPRESS_RPU))
         dv_md_compression = AV_DOVI_COMPRESSION_NONE;
 
-- 
2.52.0


From ad25ca73b56cf5b2933fb2d38902dfa5913104ec Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Sat, 11 Jul 2026 17:01:30 +0200
Subject: [PATCH 135/180] avcodec/dovi_rpudec: bound num_x/y_partitions

Fixes: out of array access
Fixes: SUcVEyk7r3Gc
Found-by: Kenan Alghythee <[email protected]>
(cherry picked from commit 79e10e51960f848b82c597c540087bcc66aa6e17)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/dovi_rpudec.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/libavcodec/dovi_rpudec.c b/libavcodec/dovi_rpudec.c
index d210ba52cf..ba4e7cd214 100644
--- a/libavcodec/dovi_rpudec.c
+++ b/libavcodec/dovi_rpudec.c
@@ -582,6 +582,8 @@ int ff_dovi_rpu_parse(DOVIContext *s, const uint8_t *rpu, size_t rpu_size,
 
         mapping->num_x_partitions = get_ue_golomb_long(gb) + 1;
         mapping->num_y_partitions = get_ue_golomb_long(gb) + 1;
+        VALIDATE(mapping->num_x_partitions, 1, 0xFFFF);
+        VALIDATE(mapping->num_y_partitions, 1, 0xFFFF);
         /* End of rpu_data_header(), start of vdr_rpu_data_payload() */
 
         for (int c = 0; c < 3; c++) {
-- 
2.52.0


From 9466148a32c663c4f51a783b7101937d1590e4e2 Mon Sep 17 00:00:00 2001
From: Kenan Alghythee <[email protected]>
Date: Sat, 11 Jul 2026 16:57:38 +0200
Subject: [PATCH 136/180] avcodec/bsf/dovi_rpu: handle update_rpu() returning
 no RPU

Fixes: out of array access
Fixes: SUcVEyk7r3Gc
Found-by: Kenan Alghythee <[email protected]>
(cherry picked from commit 534f16d866c732a85c34ac576d66d578669578f1)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/bsf/dovi_rpu.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/libavcodec/bsf/dovi_rpu.c b/libavcodec/bsf/dovi_rpu.c
index 88da4417b5..e8aa3eadcd 100644
--- a/libavcodec/bsf/dovi_rpu.c
+++ b/libavcodec/bsf/dovi_rpu.c
@@ -99,6 +99,8 @@ static int dovi_rpu_update_fragment_hevc(AVBSFContext *bsf, AVPacket *pkt,
     ret = update_rpu(bsf, pkt, 0, nal->data + 2, nal->data_size - 2, &rpu, &rpu_size);
     if (ret < 0)
         return ret;
+    if (!rpu || rpu_size <= 0)
+        return 0;
 
     /* NAL unit header + NAL prefix */
     if (rpu_size + 3 <= nal->data_size && av_buffer_is_writable(nal->data_ref)) {
@@ -158,6 +160,10 @@ static int dovi_rpu_update_fragment_av1(AVBSFContext *bsf, AVPacket *pkt,
                          &rpu, &rpu_size);
         if (ret < 0)
             return ret;
+        if (!rpu || rpu_size <= 1) {
+            av_free(rpu);
+            continue;
+        }
 
         ref = av_buffer_create(rpu, rpu_size, av_buffer_default_free, NULL, 0);
         if (!ref) {
-- 
2.52.0


From 820b0a21821b5c9eeaf15789f62b1e94dd12f7b5 Mon Sep 17 00:00:00 2001
From: Kenan Alghythee <[email protected]>
Date: Sat, 11 Jul 2026 16:57:38 +0200
Subject: [PATCH 137/180] avcodec/dovi_rpuenc: validate vdr_rpu_id from the
 input metadata

Fixes: out of array access
Fixes: SUcVEyk7r3Gc
Found-by: Kenan Alghythee <[email protected]>
(cherry picked from commit 372a611913e6e2ba3d0fe859a00934f974f7d003)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/dovi_rpuenc.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/libavcodec/dovi_rpuenc.c b/libavcodec/dovi_rpuenc.c
index fe95bd4262..8b7a74f313 100644
--- a/libavcodec/dovi_rpuenc.c
+++ b/libavcodec/dovi_rpuenc.c
@@ -685,6 +685,10 @@ int ff_dovi_rpu_generate(DOVIContext *s, const AVDOVIMetadata *metadata,
         dv_md_compression = AV_DOVI_COMPRESSION_NONE;
 
     vdr_rpu_id = mapping->vdr_rpu_id;
+    if (vdr_rpu_id < 0 || vdr_rpu_id > DOVI_MAX_DM_ID) {
+        av_log(s->logctx, AV_LOG_ERROR, "Invalid VDR RPU id %d\n", vdr_rpu_id);
+        return AVERROR_INVALIDDATA;
+    }
     use_prev_vdr_rpu = 0;
 
     if (!s->vdr[vdr_rpu_id]) {
-- 
2.52.0


From 9bece37c53b821f5e9a4a1fa894cf9816d2b3043 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Wed, 22 Jul 2026 22:46:55 +0200
Subject: [PATCH 138/180] avcodec/dovi_rpuenc: normalize
 vdr_dm_metadata_present to 0/1

(cherry picked from commit 2c18311d59ac8b1a4ca2255750836b892179d27a)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/dovi_rpuenc.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libavcodec/dovi_rpuenc.c b/libavcodec/dovi_rpuenc.c
index 8b7a74f313..d0abcc0d9e 100644
--- a/libavcodec/dovi_rpuenc.c
+++ b/libavcodec/dovi_rpuenc.c
@@ -726,7 +726,7 @@ int ff_dovi_rpu_generate(DOVIContext *s, const AVDOVIMetadata *metadata,
             return AVERROR(ENOMEM);
     }
 
-    vdr_dm_metadata_present = memcmp(color, &ff_dovi_color_default, sizeof(*color));
+    vdr_dm_metadata_present = !!memcmp(color, &ff_dovi_color_default, sizeof(*color));
     if (metadata->num_ext_blocks)
         vdr_dm_metadata_present = 1;
 
-- 
2.52.0


From 63cfa99169cf925c8d18365fa1fd0b53e38c6785 Mon Sep 17 00:00:00 2001
From: Abdessamie <[email protected]>
Date: Wed, 22 Jul 2026 06:21:26 +0200
Subject: [PATCH 139/180] avformat/rtsp: clear authentication on cross-origin
 redirects

RTSP redirects retain URL credentials and authentication state when a
Location URI changes to another origin. This can forward reusable
credentials to a server with a different scheme, host, or port.

Clear the stored credentials and authentication state when the redirect
crosses an origin boundary. Preserve them for same-origin redirects.

Fixes: cross-origin credential disclosure
Fixes: rtsp_redirect_auth_leak_poc.py
Fixes: VaKaPOnfN02z
(cherry picked from commit a441a2eb383960d76632eb5dc42639ec52d46bd8)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavformat/rtsp.c | 34 +++++++++++++++++++++++++++++++++-
 1 file changed, 33 insertions(+), 1 deletion(-)

diff --git a/libavformat/rtsp.c b/libavformat/rtsp.c
index 45b62c4188..f3c76e8e4d 100644
--- a/libavformat/rtsp.c
+++ b/libavformat/rtsp.c
@@ -1868,6 +1868,32 @@ void ff_rtsp_close_connections(AVFormatContext *s)
     ffurl_closep(&rt->rtsp_hd);
 }
 
+static int rtsp_url_same_origin(const char *url1, const char *url2)
+{
+    char proto1[128], proto2[128];
+    char host1[1024], host2[1024];
+    int port1, port2;
+
+    av_url_split(proto1, sizeof(proto1), NULL, 0, host1, sizeof(host1),
+                 &port1, NULL, 0, url1);
+    av_url_split(proto2, sizeof(proto2), NULL, 0, host2, sizeof(host2),
+                 &port2, NULL, 0, url2);
+
+    if (!proto1[0] || !proto2[0] || !host1[0] || !host2[0])
+        return 0;
+
+    if (port1 < 0)
+        port1 = !av_strcasecmp(proto1, "rtsps") ? RTSPS_DEFAULT_PORT
+                                                : RTSP_DEFAULT_PORT;
+    if (port2 < 0)
+        port2 = !av_strcasecmp(proto2, "rtsps") ? RTSPS_DEFAULT_PORT
+                                                : RTSP_DEFAULT_PORT;
+
+    return !av_strcasecmp(proto1, proto2) &&
+           !av_strcasecmp(host1, host2) &&
+           port1 == port2;
+}
+
 int ff_rtsp_connect(AVFormatContext *s)
 {
     RTSPState *rt = s->priv_data;
@@ -2181,7 +2207,13 @@ redirect:
     ff_rtsp_close_streams(s);
     ff_rtsp_close_connections(s);
     if (reply->status_code >=300 && reply->status_code < 400 && s->iformat) {
-        int ret = ff_format_check_set_url(s, reply->location);
+        int ret;
+
+        if (!rtsp_url_same_origin(s->url, reply->location)) {
+            memset(rt->auth, 0, sizeof(rt->auth));
+            memset(&rt->auth_state, 0, sizeof(rt->auth_state));
+        }
+        ret = ff_format_check_set_url(s, reply->location);
         if (ret < 0) {
             err = ret;
             goto fail2;
-- 
2.52.0


From 16b2049d4d5222db6cd7c031409058571c94f6a9 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Wed, 22 Jul 2026 05:53:16 +0200
Subject: [PATCH 140/180] avcodec/cfhd: reject transform-2 output wider than
 the plane

Fixes: out of array access
Fixes: cfhd_transform2_output_width_oob.avi
Fixes: MimvoaEVpKow
Found-by: Adrian Junge (vurlo)
(cherry picked from commit db05df9d135fb56a4babb836d5e9f5c1d984e087)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/cfhd.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/libavcodec/cfhd.c b/libavcodec/cfhd.c
index 4d430e32ef..128362ac62 100644
--- a/libavcodec/cfhd.c
+++ b/libavcodec/cfhd.c
@@ -1224,7 +1224,7 @@ finish:
 
             if (lowpass_height > s->plane[plane].band[4][1].a_height || lowpass_width > s->plane[plane].band[4][1].a_width ||
                 !highpass_stride || s->plane[plane].band[4][1].width > s->plane[plane].band[4][1].a_width ||
-                lowpass_width < 3 || lowpass_height < 3) {
+                lowpass_width < 3 || lowpass_height < 3 || lowpass_width * 2 > s->plane[plane].width) {
                 av_log(avctx, AV_LOG_ERROR, "Invalid plane dimensions\n");
                 ret = AVERROR(EINVAL);
                 goto end;
@@ -1345,7 +1345,7 @@ finish:
 
             if (lowpass_height > s->plane[plane].band[4][1].a_height || lowpass_width > s->plane[plane].band[4][1].a_width ||
                 s->plane[plane].band[4][1].width > s->plane[plane].band[4][1].a_width ||
-                lowpass_width < 3 || lowpass_height < 3) {
+                lowpass_width < 3 || lowpass_height < 3 || lowpass_width * 2 > s->plane[plane].width) {
                 av_log(avctx, AV_LOG_ERROR, "Invalid plane dimensions\n");
                 ret = AVERROR(EINVAL);
                 goto end;
-- 
2.52.0


From 90243e3985fe4a9735ab8a7fc6fda553dcb0ed58 Mon Sep 17 00:00:00 2001
From: Dan Dennedy <[email protected]>
Date: Fri, 12 Dec 2025 11:08:32 -0800
Subject: [PATCH 141/180] scale_d3d11: Fix hw_frame_ctx reference leak

This fixes #20995. The reference is taken twice and assigned to the same pointer (see above in the same function). Only the latter is needed.

(cherry picked from commit 66a8c3758216736cb0cc0b9123f0514eb2627d2f)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavfilter/vf_scale_d3d11.c | 8 +-------
 1 file changed, 1 insertion(+), 7 deletions(-)

diff --git a/libavfilter/vf_scale_d3d11.c b/libavfilter/vf_scale_d3d11.c
index 549aba547d..1203111c49 100644
--- a/libavfilter/vf_scale_d3d11.c
+++ b/libavfilter/vf_scale_d3d11.c
@@ -341,13 +341,6 @@ static int scale_d3d11_config_props(AVFilterLink *outlink)
         return AVERROR(EINVAL);
     }
 
-    ///< Propagate hw_frames_ctx to output
-    outl->hw_frames_ctx = av_buffer_ref(inl->hw_frames_ctx);
-    if (!outl->hw_frames_ctx) {
-        av_log(ctx, AV_LOG_ERROR, "Failed to propagate hw_frames_ctx to output\n");
-        return AVERROR(ENOMEM);
-    }
-
     ///< Initialize filter's hardware device context
     if (!s->hw_device_ctx) {
         AVHWFramesContext *in_frames_ctx = (AVHWFramesContext *)inl->hw_frames_ctx->data;
@@ -395,6 +388,7 @@ static int scale_d3d11_config_props(AVFilterLink *outlink)
         return ret;
     }
 
+    ///< Propagate hw_frames_ctx to output
     outl->hw_frames_ctx = av_buffer_ref(s->hw_frames_ctx_out);
     if (!outl->hw_frames_ctx)
         return AVERROR(ENOMEM);
-- 
2.52.0


From 0710198038383d136a8ee5dee65f7f487cc133ba Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Wed, 22 Jul 2026 06:03:39 +0200
Subject: [PATCH 142/180] avcodec/pgssubdec: always give an output rect a
 palette
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Fixes: NULL pointer dereference
Fixes: poc_null_deref.sup
Fixes: Fobbab5Vtlr3
Found-by: VRI with 图龙锋
(cherry picked from commit 5d4d3bdc61412641883a45e060e810f80ea7f4b5)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/pgssubdec.c | 18 ++++++++++--------
 1 file changed, 10 insertions(+), 8 deletions(-)

diff --git a/libavcodec/pgssubdec.c b/libavcodec/pgssubdec.c
index 20583c9afa..3a4b60f419 100644
--- a/libavcodec/pgssubdec.c
+++ b/libavcodec/pgssubdec.c
@@ -537,6 +537,16 @@ static int display_end_segment(AVCodecContext *avctx, AVSubtitle *sub,
         sub->rects[sub->num_rects++] = rect;
         rect->type = SUBTITLE_BITMAP;
 
+        /* Allocate the palette now so that the error paths below, which
+         * leave the rect empty, still hand consumers a complete bitmap
+         * rect rather than one with a NULL palette. */
+        rect->nb_colors = 256;
+        rect->data[1]   = av_mallocz(AVPALETTE_SIZE);
+        if (!rect->data[1])
+            return AVERROR(ENOMEM);
+        if (!ctx->forced_subs_only || ctx->presentation.objects[i].composition_flag & 0x40)
+            memcpy(rect->data[1], palette->clut, rect->nb_colors * sizeof(uint32_t));
+
         /* Process bitmap */
         object = find_object(ctx->presentation.objects[i].id, &ctx->objects);
         if (!object) {
@@ -577,14 +587,6 @@ static int display_end_segment(AVCodecContext *avctx, AVSubtitle *sub,
                 continue;
             }
         }
-        /* Allocate memory for colors */
-        rect->nb_colors = 256;
-        rect->data[1]   = av_mallocz(AVPALETTE_SIZE);
-        if (!rect->data[1])
-            return AVERROR(ENOMEM);
-
-        if (!ctx->forced_subs_only || ctx->presentation.objects[i].composition_flag & 0x40)
-            memcpy(rect->data[1], palette->clut, rect->nb_colors * sizeof(uint32_t));
     }
     return 1;
 }
-- 
2.52.0


From 49200661268de86dce6e8527793e45bac85b7bea Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Thu, 30 Jul 2026 01:41:06 +0200
Subject: [PATCH 143/180] avcodec/vulkan/ffv1_dec_setup: reject a remap that
 produces zero entries

(cherry picked from commit cc82fdfbd4cacef2018f1dadb29330bf80c4fe87)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/vulkan/ffv1_dec_setup.comp.glsl | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/libavcodec/vulkan/ffv1_dec_setup.comp.glsl b/libavcodec/vulkan/ffv1_dec_setup.comp.glsl
index d000116012..646aae0b59 100644
--- a/libavcodec/vulkan/ffv1_dec_setup.comp.glsl
+++ b/libavcodec/vulkan/ffv1_dec_setup.comp.glsl
@@ -91,7 +91,7 @@ int decode_current_mul(uint ctx_off, int mul_count, int64_t i)
     return mul[ndx];
 }
 
-void decode_remap(uint slice_idx, inout SliceContext sc)
+bool decode_remap(uint slice_idx, inout SliceContext sc)
 {
     uint end = uint(rct_offset - 1);
     uint flip_mask = end ^ (end >> 1);
@@ -106,10 +106,8 @@ void decode_remap(uint slice_idx, inout SliceContext sc)
             rc_state[k] = uint8_t(128);
 
         int mul_count = int(get_usymbol(0));
-        if (mul_count > 4096) {
-            sc.remap_count[p] = j;
-            return;
-        }
+        if (mul_count > 4096)
+            return true;
         for (int mi = 0; mi < mul_count; mi++)
             mul[mi] = -1;
         mul[mul_count] = 1;
@@ -131,10 +129,8 @@ void decode_remap(uint slice_idx, inout SliceContext sc)
                 run1--;
                 if (current_mul > 1) {
                     int delta = get_isymbol(uint(lu*3 + 1)*CONTEXT_SIZE);
-                    if (delta <= -current_mul || delta > current_mul/2) {
-                        sc.remap_count[p] = j;
-                        return;
-                    }
+                    if (delta <= -current_mul || delta > current_mul/2)
+                        return true;
                     i += int64_t(current_mul - 1 + delta);
                 }
                 if (i - 1 >= int64_t(end))
@@ -148,8 +144,12 @@ void decode_remap(uint slice_idx, inout SliceContext sc)
                 i += int64_t(current_mul);
             lu ^= int(run == 0u);
         }
+        if (j == 0)
+            return true;
         sc.remap_count[p] = j;
     }
+
+    return false;
 }
 
 bool decode_slice_header(uint slice_idx, inout SliceContext sc)
@@ -200,8 +200,8 @@ bool decode_slice_header(uint slice_idx, inout SliceContext sc)
 
         if (micro_version >= 4) {
             sc.remap = get_usymbol(0);
-            if (sc.remap != 0)
-                decode_remap(slice_idx, sc);
+            if (sc.remap != 0 && decode_remap(slice_idx, sc))
+                return true;
         }
     }
 
-- 
2.52.0


From 788e3985f8a461a87cacc4f5c7f54f58d5f6fea5 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Thu, 30 Jul 2026 01:41:17 +0200
Subject: [PATCH 144/180] avcodec/vulkan/ffv1_dec_setup: test mul_count as
 unsigned

(cherry picked from commit c91b50659b9ae66a966b74246ead91418be53d21)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/vulkan/ffv1_dec_setup.comp.glsl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libavcodec/vulkan/ffv1_dec_setup.comp.glsl b/libavcodec/vulkan/ffv1_dec_setup.comp.glsl
index 646aae0b59..5a648f4605 100644
--- a/libavcodec/vulkan/ffv1_dec_setup.comp.glsl
+++ b/libavcodec/vulkan/ffv1_dec_setup.comp.glsl
@@ -106,7 +106,7 @@ bool decode_remap(uint slice_idx, inout SliceContext sc)
             rc_state[k] = uint8_t(128);
 
         int mul_count = int(get_usymbol(0));
-        if (mul_count > 4096)
+        if (uint(mul_count) > 4096u)
             return true;
         for (int mi = 0; mi < mul_count; mi++)
             mul[mi] = -1;
-- 
2.52.0


From caf5fdff494eaa0bd1f75de532d34db693c1531c Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Thu, 30 Jul 2026 01:41:17 +0200
Subject: [PATCH 145/180] avcodec/vulkan/ffv1_dec_setup: bound the fltmap write

(cherry picked from commit 946272b79a325e9bce613b260e50e4e4fe7f3159)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/vulkan/ffv1_dec_setup.comp.glsl | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/libavcodec/vulkan/ffv1_dec_setup.comp.glsl b/libavcodec/vulkan/ffv1_dec_setup.comp.glsl
index 5a648f4605..72df03874a 100644
--- a/libavcodec/vulkan/ffv1_dec_setup.comp.glsl
+++ b/libavcodec/vulkan/ffv1_dec_setup.comp.glsl
@@ -135,6 +135,8 @@ bool decode_remap(uint slice_idx, inout SliceContext sc)
                 }
                 if (i - 1 >= int64_t(end))
                     break;
+                if (j >= fltmap[slice_idx][p].length())
+                    return true;
                 uint iv = uint(i);
                 fltmap[slice_idx][p][j++] = iv ^ (((iv & flip_mask) != 0u) ? 0u : flip);
                 i++;
-- 
2.52.0


From b36c728ba890ea325d7fb646c444a26ca697957a Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Thu, 30 Jul 2026 01:40:49 +0200
Subject: [PATCH 146/180] avcodec/ffv1dec: mark the slice damaged when its
 remap fails

(cherry picked from commit 8ab8b4fabe8440d32a966f355dc2102d86336673)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/ffv1dec.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/libavcodec/ffv1dec.c b/libavcodec/ffv1dec.c
index ba973ab76f..ccafc71872 100644
--- a/libavcodec/ffv1dec.c
+++ b/libavcodec/ffv1dec.c
@@ -488,8 +488,10 @@ static int decode_slice(AVCodecContext *c, void *arg)
 
     if (sc->remap) {
         ret = decode_remap(f, sc);
-        if (ret < 0)
+        if (ret < 0) {
+            slice_set_damaged(f, sc);
             return ret;
+        }
     }
 
     if (ac == AC_GOLOMB_RICE) {
-- 
2.52.0


From 9f363dd22dd16a0be9f0643989402c25089f6288 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Thu, 30 Jul 2026 01:40:49 +0200
Subject: [PATCH 147/180] avcodec/ffv1dec: reject a remap that produces zero
 entries

(cherry picked from commit 56743898d214decccf219aea4233da7aca9dc71c)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/ffv1dec.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/libavcodec/ffv1dec.c b/libavcodec/ffv1dec.c
index ccafc71872..8de15d2476 100644
--- a/libavcodec/ffv1dec.c
+++ b/libavcodec/ffv1dec.c
@@ -369,6 +369,8 @@ static int decode_remap(FFV1Context *f, FFV1SliceContext *sc)
             }
             lu ^= !run;
         }
+        if (!j)
+            return AVERROR_INVALIDDATA;
         sc->remap_count[p] = j;
     }
     return 0;
-- 
2.52.0


From 8b36b1668ff0d1ef2457a9ff9cb627e6018f5d22 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Thu, 30 Jul 2026 01:41:06 +0200
Subject: [PATCH 148/180] avcodec/vulkan/ffv1_dec_setup: act on the slice
 header rejection

(cherry picked from commit 533a3c254d864a7cd4066cd055c2e2d038e95576)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/vulkan/ffv1_dec_setup.comp.glsl | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/libavcodec/vulkan/ffv1_dec_setup.comp.glsl b/libavcodec/vulkan/ffv1_dec_setup.comp.glsl
index 72df03874a..ea84da6d4e 100644
--- a/libavcodec/vulkan/ffv1_dec_setup.comp.glsl
+++ b/libavcodec/vulkan/ffv1_dec_setup.comp.glsl
@@ -219,7 +219,8 @@ void main(void)
     if (slice_idx == (gl_NumWorkGroups.x*gl_NumWorkGroups.y - 1))
         get_rac_equi();
 
-    decode_slice_header(slice_idx, slice_ctx[slice_idx]);
+    if (decode_slice_header(slice_idx, slice_ctx[slice_idx]))
+        slice_ctx[slice_idx].slice_dim = ivec2(0);
 
     slice_ctx[slice_idx].c = rc;
 
-- 
2.52.0


From 6af6fef641353a345535cef6fee11c0d3f488c79 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Wed, 22 Jul 2026 06:13:42 +0200
Subject: [PATCH 149/180] avcodec/cbs_h266: size vps_direct_ref_layer_flag for
 the full layer range

Fixes: out of array access
Fixes: poc_vvc_vps.266
Fixes: 1z6RugPwxp39
Found-by: TainAn Bill
(cherry picked from commit 4f8043e658e195cc4e44cb79e864c4a6466b72a7)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/cbs_h266.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libavcodec/cbs_h266.h b/libavcodec/cbs_h266.h
index 8d851a0bfb..0dc7d96828 100644
--- a/libavcodec/cbs_h266.h
+++ b/libavcodec/cbs_h266.h
@@ -270,7 +270,7 @@ typedef struct H266RawVPS {
     uint8_t  vps_layer_id[VVC_MAX_LAYERS];
     uint8_t  vps_independent_layer_flag[VVC_MAX_LAYERS];
     uint8_t  vps_max_tid_ref_present_flag[VVC_MAX_LAYERS];
-    uint8_t  vps_direct_ref_layer_flag[VVC_MAX_LAYERS][VVC_MAX_LAYERS - 1];
+    uint8_t  vps_direct_ref_layer_flag[VVC_MAX_LAYERS][VVC_MAX_LAYERS];
     uint8_t  vps_max_tid_il_ref_pics_plus1[VVC_MAX_LAYERS][VVC_MAX_LAYERS - 1];
     uint8_t  vps_each_layer_is_an_ols_flag;
     uint8_t  vps_ols_mode_idc;
-- 
2.52.0


From 5eac4c5e3e03b0585404c9a94dfe6f0590be9a18 Mon Sep 17 00:00:00 2001
From: James Almer <[email protected]>
Date: Fri, 31 Jul 2026 20:45:49 -0300
Subject: [PATCH 150/180] avformat/iamf_parse: bound the output mix gain
 duration by the audio elements

Based on a patch by Michael Niedermayer

Fixes: unbounded allocation
Fixes: poc_output_mix_config_dos.iamf
Fixes: 387qWpjUgeKH
Found-by: Duong Tran Thai (CyStack)
Signed-off-by: James Almer <[email protected]>
(cherry picked from commit d2476bfd2b0ab683f24ff92cb2b206549f0a1659)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavformat/iamf_parse.c | 34 +++++++++++++++++++++++-----------
 1 file changed, 23 insertions(+), 11 deletions(-)

diff --git a/libavformat/iamf_parse.c b/libavformat/iamf_parse.c
index 37d7e04f7b..62c825ffec 100644
--- a/libavformat/iamf_parse.c
+++ b/libavformat/iamf_parse.c
@@ -609,6 +609,7 @@ static int ambisonics_config(void *s, AVIOContext *pb,
 static int param_parse(void *s, IAMFContext *c, AVIOContext *pb,
                        unsigned int type,
                        const IAMFAudioElement *audio_element,
+                       const IAMFCodecConfig *codec_config,
                        AVIAMFParamDefinition **out_param_definition)
 {
     IAMFParamDefinition *param_definition = NULL;
@@ -631,14 +632,10 @@ static int param_parse(void *s, IAMFContext *c, AVIOContext *pb,
 
     if (mode == 0) {
         duration = ffio_read_leb(pb);
-        if (!duration)
+        if (!duration || duration > av_rescale(codec_config->nb_samples,
+                                               codec_config->sample_rate, parameter_rate)) {
+            av_log(s, AV_LOG_ERROR, "Invalid block duration in parameter_id %u\n", parameter_id);
             return AVERROR_INVALIDDATA;
-        if (audio_element) {
-            const IAMFCodecConfig *codec_config = ff_iamf_get_codec_config(c, audio_element->codec_config_id);
-            if (duration > av_rescale(codec_config->nb_samples, codec_config->sample_rate, parameter_rate)) {
-                av_log(s, AV_LOG_ERROR, "Invalid block duration in parameter_id %u\n", parameter_id);
-                return AVERROR_INVALIDDATA;
-            }
         }
         constant_subblock_duration = ffio_read_leb(pb);
         if (constant_subblock_duration == 0)
@@ -899,13 +896,17 @@ static int audio_element_obu(void *s, IAMFContext *c, AVIOContext *pb, int len)
                 ret = AVERROR_INVALIDDATA;
                 goto fail;
             }
-            ret = param_parse(s, c, pbc, type, audio_element, &element->demixing_info);
+            ret = param_parse(s, c, pbc, type,
+                              audio_element, codec_config,
+                              &element->demixing_info);
         } else if (type == AV_IAMF_PARAMETER_DEFINITION_RECON_GAIN) {
             if (element->recon_gain_info) {
                 ret = AVERROR_INVALIDDATA;
                 goto fail;
             }
-            ret = param_parse(s, c, pbc, type, audio_element, &element->recon_gain_info);
+            ret = param_parse(s, c, pbc, type,
+                              audio_element, codec_config,
+                              &element->recon_gain_info);
         } else {
             unsigned param_definition_size = ffio_read_leb(pbc);
             avio_skip(pbc, param_definition_size);
@@ -960,6 +961,7 @@ static int label_string(AVIOContext *pb, char **label)
 static int mix_presentation_obu(void *s, IAMFContext *c, AVIOContext *pb, int len)
 {
     AVIAMFMixPresentation *mix;
+    const IAMFCodecConfig *codec_config = NULL;
     IAMFMixPresentation **tmp, *mix_presentation = NULL;
     FFIOContext b;
     AVIOContext *pbc;
@@ -1054,6 +1056,7 @@ static int mix_presentation_obu(void *s, IAMFContext *c, AVIOContext *pb, int le
         for (int j = 0; j < nb_elements; j++) {
             AVIAMFSubmixElement *submix_element;
             IAMFAudioElement *audio_element = NULL;
+            const IAMFCodecConfig *config = NULL;
             unsigned int rendering_config_extension_size;
 
             submix_element = av_iamf_submix_add_element(sub_mix);
@@ -1076,6 +1079,7 @@ static int mix_presentation_obu(void *s, IAMFContext *c, AVIOContext *pb, int le
                 ret = AVERROR_INVALIDDATA;
                 goto fail;
             }
+            config = ff_iamf_get_codec_config(c, audio_element->codec_config_id);
 
             for (int k = 0; k < mix_presentation->count_label; k++) {
                 char *annotation = NULL;
@@ -1094,14 +1098,22 @@ static int mix_presentation_obu(void *s, IAMFContext *c, AVIOContext *pb, int le
             avio_skip(pbc, rendering_config_extension_size);
 
             ret = param_parse(s, c, pbc, AV_IAMF_PARAMETER_DEFINITION_MIX_GAIN,
-                              audio_element,
+                              audio_element, config,
                               &submix_element->element_mix_config);
             if (ret < 0)
                 goto fail;
             submix_element->default_mix_gain = av_make_q(sign_extend(avio_rb16(pbc), 16), 1 << 8);
+
+            if (!codec_config || (config->nb_samples >
+                                  av_rescale(codec_config->nb_samples,
+                                             codec_config->sample_rate,
+                                             config->sample_rate)))
+                codec_config = config;
         }
 
-        ret = param_parse(s, c, pbc, AV_IAMF_PARAMETER_DEFINITION_MIX_GAIN, NULL, &sub_mix->output_mix_config);
+        ret = param_parse(s, c, pbc, AV_IAMF_PARAMETER_DEFINITION_MIX_GAIN,
+                          NULL, codec_config,
+                          &sub_mix->output_mix_config);
         if (ret < 0)
             goto fail;
         sub_mix->default_mix_gain = av_make_q(sign_extend(avio_rb16(pbc), 16), 1 << 8);
-- 
2.52.0


From 95fe88589a7254a8b88e76ae4eae2fe0359dc11d Mon Sep 17 00:00:00 2001
From: James Almer <[email protected]>
Date: Sat, 25 Jul 2026 12:20:22 -0300
Subject: [PATCH 151/180] avformat/iamf_parse: check that num_sub_mixes and
 num_audio_elements in Mix Presentations are not zero

As required by the spec in Section 3.7

Signed-off-by: James Almer <[email protected]>
(cherry picked from commit 1b946a3abd81bd2b50ef6d38003ddb848be7acc7)
---
 libavformat/iamf_parse.c | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/libavformat/iamf_parse.c b/libavformat/iamf_parse.c
index 62c825ffec..892145cd87 100644
--- a/libavformat/iamf_parse.c
+++ b/libavformat/iamf_parse.c
@@ -1042,6 +1042,12 @@ static int mix_presentation_obu(void *s, IAMFContext *c, AVIOContext *pb, int le
     }
 
     nb_submixes = ffio_read_leb(pbc);
+    if (!nb_submixes) {
+        av_log(s, AV_LOG_ERROR, "Mix presentation %u has no submixes\n", mix_presentation_id);
+        ret = AVERROR_INVALIDDATA;
+        goto fail;
+    }
+
     for (int i = 0; i < nb_submixes; i++) {
         AVIAMFSubmix *sub_mix;
         unsigned nb_elements, nb_layouts;
@@ -1053,6 +1059,13 @@ static int mix_presentation_obu(void *s, IAMFContext *c, AVIOContext *pb, int le
         }
 
         nb_elements = ffio_read_leb(pbc);
+        if (!nb_elements) {
+            av_log(s, AV_LOG_ERROR, "Submix %d from Mix presentation %u has no audio elements\n",
+                   i, mix_presentation_id);
+            ret = AVERROR_INVALIDDATA;
+            goto fail;
+        }
+
         for (int j = 0; j < nb_elements; j++) {
             AVIAMFSubmixElement *submix_element;
             IAMFAudioElement *audio_element = NULL;
-- 
2.52.0


From a5ef0da32a0292d7fbe063f9d1065ac5a83b654f Mon Sep 17 00:00:00 2001
From: James Almer <[email protected]>
Date: Fri, 24 Jul 2026 11:30:57 -0300
Subject: [PATCH 152/180] avformat/dashdec: don't stop at the first input EOF

The current logic would stop demuxing the entire manifest once the first Representation
returned EOF, which could result in plenty of packets from other Representations being
lost.

Signed-off-by: James Almer <[email protected]>
(cherry picked from commit d9da090b1d05cd731610462fd91c5f167e698909)
---
 libavformat/dashdec.c | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/libavformat/dashdec.c b/libavformat/dashdec.c
index 0ffab1e889..1e6c684b32 100644
--- a/libavformat/dashdec.c
+++ b/libavformat/dashdec.c
@@ -2376,7 +2376,7 @@ static int dash_read_packet(AVFormatContext *s, AVPacket *pkt)
     }
 
     if (!cur) {
-        return AVERROR_INVALIDDATA;
+        return AVERROR_EOF;
     }
     while (!ff_check_interrupt(c->interrupt_callback) && !ret) {
         ret = av_read_frame(cur->ctx, pkt);
@@ -2392,9 +2392,17 @@ static int dash_read_packet(AVFormatContext *s, AVPacket *pkt)
             cur->is_restart_needed = 0;
             ff_format_io_close(cur->parent, &cur->input);
             ret = reopen_demux_for_component(s, cur);
+        } else if (ret == AVERROR_EOF) {
+            close_demux_for_component(cur);
+            ff_format_io_close(cur->parent, &cur->input);
+            av_log(s, AV_LOG_DEBUG, "EOF on stream_index %d\n", cur->stream_index);
+            // prevent recheck_discard_flags() from re-enabling the component
+            for (int i = 0; i < cur->nb_assoc_stream; i++)
+                cur->assoc_stream[i]->discard = AVDISCARD_ALL;
+            return FFERROR_REDO;
         }
     }
-    return AVERROR_EOF;
+    return ret;
 }
 
 static int dash_close(AVFormatContext *s)
-- 
2.52.0


From ae981e6f0e664e9a23e8cf9b838dad0eafea00a4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mirk=C3=B3=20Visontai?= <[email protected]>
Date: Mon, 13 Jul 2026 11:15:28 -0700
Subject: [PATCH 153/180] avdevice/android_camera: fix OOB read in metadata
 parsing

ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS metadata is an
int32[n*4] array (one 4-tuple per stream config: format, width,
height, input/output flag). ACameraMetadata_const_entry.count is the
total number of int32_t elements, not the number of tuples. The loop
bound must be count/4 to avoid iterating past the end of the array.

Similarly, ACAMERA_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES is an
int32[n*2] array (min/max pairs). The loop bound must be count/2.

Without this fix, both loops over-iterate and read heap memory
beyond the metadata array bounds.

Signed-off-by: Mirko Visontai <[email protected]>
(cherry picked from commit 1588bce21b6d5d9064fd9576c038fd8d6ec8beac)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavdevice/android_camera.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/libavdevice/android_camera.c b/libavdevice/android_camera.c
index 8433286296..7abdb6bd7b 100644
--- a/libavdevice/android_camera.c
+++ b/libavdevice/android_camera.c
@@ -249,7 +249,7 @@ static void match_video_size(AVFormatContext *avctx)
                                   ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS,
                                   &available_configs);
 
-    for (int i = 0; i < available_configs.count; i++) {
+    for (int i = 0; i < available_configs.count / 4; i++) {
         int32_t input = available_configs.data.i32[i * 4 + 3];
         int32_t format = available_configs.data.i32[i * 4 + 0];
 
@@ -296,7 +296,7 @@ static void match_framerate(AVFormatContext *avctx)
                                   ACAMERA_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES,
                                   &available_framerates);
 
-    for (int i = 0; i < available_framerates.count; i++) {
+    for (int i = 0; i < available_framerates.count / 2; i++) {
         int32_t min = available_framerates.data.i32[i * 2 + 0];
         int32_t max = available_framerates.data.i32[i * 2 + 1];
 
-- 
2.52.0


From 86a468b8b9e7476c4926fe00ac84e2dfe003a2af Mon Sep 17 00:00:00 2001
From: Zhao Zhili <[email protected]>
Date: Fri, 3 Jul 2026 19:04:37 +0800
Subject: [PATCH 154/180] swscale/x86: fix SIGILL in u8 SCALE on SSE4-only CPUs

Signed-off-by: Zhao Zhili <[email protected]>
(cherry picked from commit 22b08e8cc1cc0b9504f75cf46f6b6698247c3616)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libswscale/x86/ops_int.asm | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/libswscale/x86/ops_int.asm b/libswscale/x86/ops_int.asm
index ce9ab1fdc9..395feb9531 100644
--- a/libswscale/x86/ops_int.asm
+++ b/libswscale/x86/ops_int.asm
@@ -733,7 +733,12 @@ IF W,   packuswb %4, m11
 %macro SCALE 0
         LOAD_CONT tmp0q
 %if BITS == 8
+%if cpuflag(avx2)
         vpbroadcastw m12, [implq + SwsOpImpl.priv]
+%else
+        movd xm12, [implq + SwsOpImpl.priv]
+        SPLATW m12, xm12, 0
+%endif
         pxor m15, m15
         scale8 mx,  my,  mz,  mw,  m12
 IF1 V2, scale8 mx2, my2, mz2, mw2, m12
-- 
2.52.0


From 02edc0f612ba27b9a8286d7d8ce4280ebb93bd40 Mon Sep 17 00:00:00 2001
From: Steven Liu <[email protected]>
Date: Tue, 16 Jun 2026 22:05:31 +0800
Subject: [PATCH 155/180] avformat/dashdec: fix integer truncation in
 calc_max_seg_no()

When computing the segment count for a SegmentTimeline entry with
repeat="-1" (indefinite repeat, typical for live streams), the
expression:

  length_of_each_segment = pls->timelines[i]->duration /
                           pls->fragment_timescale;
  num = c->period_duration / length_of_each_segment;

performs two successive integer divisions.  The first division
truncates toward zero; if the segment duration in timescale ticks
is smaller than the timescale (duration < fragment_timescale),
length_of_each_segment becomes 0, and the second division triggers
a division by zero.

Fix this by using av_rescale(), which computes

  period_duration * fragment_timescale / duration

with 64-bit intermediate precision, avoiding both the truncation
and the zero-division risk.  If duration is 0 (invalid manifest),
explicitly fall back to first_seq_no instead of crashing.

Signed-off-by: Steven Liu <[email protected]>
(cherry picked from commit 0c0dd61b8f0cee6ed53f735d06b73998e13b6fea)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavformat/dashdec.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/libavformat/dashdec.c b/libavformat/dashdec.c
index 1e6c684b32..e8d9539937 100644
--- a/libavformat/dashdec.c
+++ b/libavformat/dashdec.c
@@ -1498,8 +1498,7 @@ static int64_t calc_max_seg_no(struct representation *pls, DASHContext *c)
         num = pls->first_seq_no + pls->n_timelines - 1;
         for (i = 0; i < pls->n_timelines; i++) {
             if (pls->timelines[i]->repeat == -1) {
-                int length_of_each_segment = pls->timelines[i]->duration / pls->fragment_timescale;
-                num =  c->period_duration / length_of_each_segment;
+                num = pls->timelines[i]->duration ? av_rescale(c->period_duration, pls->fragment_timescale, pls->timelines[i]->duration) : pls->first_seq_no;
             } else {
                 num += pls->timelines[i]->repeat;
             }
-- 
2.52.0


From 7bda4f9d1549816a9f01709affe723962899c2c7 Mon Sep 17 00:00:00 2001
From: Zhao Zhili <[email protected]>
Date: Mon, 6 Jul 2026 20:17:13 +0800
Subject: [PATCH 156/180] fftools/opt_common: fix format string bug in
 print_program_info

FFMPEG_CONFIGURATION and FFMPEG_VERSION were concatenated into av_log
format strings, so any % in them was interpreted as a conversion
specifier and consumed unrelated varargs, causing undefined behavior
ranging from garbled output to a segfault on `ffmpeg -version`. Pass
both as %s arguments instead.

Fixes issue #23662.

The FFMPEG_CONFIGURATION fix is based on a patch by FinnRG; the
FFMPEG_VERSION case is fixed here as well.

Signed-off-by: Zhao Zhili <[email protected]>
(cherry picked from commit 160737cf0da1915d8499881a8021d170f927411d)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 fftools/opt_common.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/fftools/opt_common.c b/fftools/opt_common.c
index 82b3cf2464..572cc563e3 100644
--- a/fftools/opt_common.c
+++ b/fftools/opt_common.c
@@ -201,14 +201,14 @@ static void print_program_info(int flags, int level)
 {
     const char *indent = flags & INDENT? "  " : "";
 
-    av_log(NULL, level, "%s version " FFMPEG_VERSION, program_name);
+    av_log(NULL, level, "%s version %s", program_name, FFMPEG_VERSION);
     if (flags & SHOW_COPYRIGHT)
         av_log(NULL, level, " Copyright (c) %d-%d the FFmpeg developers",
                program_birth_year, CONFIG_THIS_YEAR);
     av_log(NULL, level, "\n");
     av_log(NULL, level, "%sbuilt with %s\n", indent, CC_IDENT);
 
-    av_log(NULL, level, "%sconfiguration: " FFMPEG_CONFIGURATION "\n", indent);
+    av_log(NULL, level, "%sconfiguration: %s\n", indent, FFMPEG_CONFIGURATION);
 }
 
 static void print_buildconf(int flags, int level)
-- 
2.52.0


From 79e64c80bf174327573dbe28cb58f800bc1c967d Mon Sep 17 00:00:00 2001
From: Zhao Zhili <[email protected]>
Date: Wed, 8 Jul 2026 19:31:30 +0800
Subject: [PATCH 157/180] avfilter/vf_scale_cuda: fix non-scaling format
 conversion

A format-only conversion leaves the auto use_filters value unresolved.
Regression since 5d0748243f8.

Fix issue #23737

Signed-off-by: Zhao Zhili <[email protected]>
(cherry picked from commit c57660fb18f058e8ead224e840b242d9c68fd3c4)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavfilter/vf_scale_cuda.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/libavfilter/vf_scale_cuda.c b/libavfilter/vf_scale_cuda.c
index 409145c44f..240e40d43b 100644
--- a/libavfilter/vf_scale_cuda.c
+++ b/libavfilter/vf_scale_cuda.c
@@ -422,6 +422,8 @@ static av_cold int init_processing_chain(AVFilterContext *ctx, int in_width, int
             s->use_filters = 0;
         } else if (s->use_filters < 0 && (out_width < in_width || out_height < in_height))
             s->use_filters = 1; /* downscaling; needed for anti-aliasing */
+        else if (s->use_filters < 0)
+            s->use_filters = 0;
     }
 
     outl->hw_frames_ctx = av_buffer_ref(s->frames_ctx);
-- 
2.52.0


From e22de4c790ed1eaf5bdecf76ce14599ed94086ec Mon Sep 17 00:00:00 2001
From: Steven Liu <[email protected]>
Date: Tue, 16 Jun 2026 21:40:37 +0800
Subject: [PATCH 158/180] avformat/dashdec: check NULL pointer of av_strtok
 value before use it

fix issue: issues/23057 POC1
release seg memory and return NULL if av_strtok return NULL.

Signed-off-by: Steven Liu <[email protected]>
(cherry picked from commit 813c28d1ce216502ee21d4912980689ad60333d3)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavformat/dashdec.c | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/libavformat/dashdec.c b/libavformat/dashdec.c
index e8d9539937..348222dac4 100644
--- a/libavformat/dashdec.c
+++ b/libavformat/dashdec.c
@@ -589,7 +589,7 @@ static enum AVMediaType get_content_type(xmlNodePtr node)
     return type;
 }
 
-static struct fragment *get_fragment(char *range)
+static struct fragment *get_fragment(AVFormatContext *s, char *range)
 {
     struct fragment *seg = av_mallocz(sizeof(struct fragment));
 
@@ -600,6 +600,11 @@ static struct fragment *get_fragment(char *range)
     if (range) {
         char *str_end_offset;
         char *str_offset = av_strtok(range, "-", &str_end_offset);
+        if (!str_offset) {
+            av_log(s, AV_LOG_WARNING, "%s will get invalid range\n", range);
+            av_freep(&seg);
+            return NULL;
+        }
         seg->url_offset = strtoll(str_offset, NULL, 10);
         seg->size = strtoll(str_end_offset, NULL, 10) - seg->url_offset + 1;
     }
@@ -625,7 +630,7 @@ static int parse_manifest_segmenturlnode(AVFormatContext *s, struct representati
         range_val = xmlGetProp(fragmenturl_node, "range");
         if (initialization_val || range_val) {
             free_fragment(&rep->init_section);
-            rep->init_section = get_fragment(range_val);
+            rep->init_section = get_fragment(s, range_val);
             xmlFree(range_val);
             if (!rep->init_section) {
                 xmlFree(initialization_val);
@@ -646,7 +651,7 @@ static int parse_manifest_segmenturlnode(AVFormatContext *s, struct representati
         media_val = xmlGetProp(fragmenturl_node, "media");
         range_val = xmlGetProp(fragmenturl_node, "mediaRange");
         if (media_val || range_val) {
-            struct fragment *seg = get_fragment(range_val);
+            struct fragment *seg = get_fragment(s, range_val);
             xmlFree(range_val);
             if (!seg) {
                 xmlFree(media_val);
-- 
2.52.0


From 2bf9f7b6e3bf1f6960627b6cc52b6369108c0c45 Mon Sep 17 00:00:00 2001
From: Steven Liu <[email protected]>
Date: Tue, 16 Jun 2026 21:42:13 +0800
Subject: [PATCH 159/180] avformat/dashdec: check NULL pointer before use
 str_end_offset

fix issue: issues/23057 POC2

Signed-off-by: Steven Liu <[email protected]>
(cherry picked from commit 5e0ca682a43f3c3d2b443c0d86d6a3d4d6ad4dd7)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavformat/dashdec.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libavformat/dashdec.c b/libavformat/dashdec.c
index 348222dac4..3dad770d54 100644
--- a/libavformat/dashdec.c
+++ b/libavformat/dashdec.c
@@ -600,7 +600,7 @@ static struct fragment *get_fragment(AVFormatContext *s, char *range)
     if (range) {
         char *str_end_offset;
         char *str_offset = av_strtok(range, "-", &str_end_offset);
-        if (!str_offset) {
+        if (!str_offset || !str_end_offset) {
             av_log(s, AV_LOG_WARNING, "%s will get invalid range\n", range);
             av_freep(&seg);
             return NULL;
-- 
2.52.0


From 3c287af3affe1286350faa69c02bcc5d49de18bb Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Wed, 22 Jul 2026 05:44:41 +0200
Subject: [PATCH 160/180] avcodec/tiff: reject inflate output shorter than the
 strip

Fixes: use of uninitialized memory
Fixes: tiff_short_deflate_heap_disclosure.tiff
Fixes: 1cRIkpUVMQtn
Found-by: Adrian Junge (vurlo)
(cherry picked from commit 2f234ea34c81288e3840fca632dd16481d8de39f)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/tiff.c | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/libavcodec/tiff.c b/libavcodec/tiff.c
index 8a179f0fd0..b8ce7b0b55 100644
--- a/libavcodec/tiff.c
+++ b/libavcodec/tiff.c
@@ -526,6 +526,7 @@ static int tiff_unpack_zlib(TiffContext *s, AVFrame *p, uint8_t *dst, int stride
     uint8_t *zbuf;
     unsigned long outlen;
     int ret, line;
+    int rows = is_yuv ? (lines + s->subsampling[1] - 1) / s->subsampling[1] : lines;
     outlen = width * lines;
     zbuf   = av_malloc(outlen);
     if (!zbuf)
@@ -545,6 +546,12 @@ static int tiff_unpack_zlib(TiffContext *s, AVFrame *p, uint8_t *dst, int stride
         av_free(zbuf);
         return AVERROR_UNKNOWN;
     }
+    if (outlen < (unsigned long)width * rows) {
+        av_log(s->avctx, AV_LOG_ERROR, "Deflated %lu bytes, but %lu are needed\n",
+               outlen, (unsigned long)width * rows);
+        av_free(zbuf);
+        return AVERROR_INVALIDDATA;
+    }
     src = zbuf;
     for (line = 0; line < lines; line++) {
         if (s->bpp < 8 && s->avctx->pix_fmt == AV_PIX_FMT_PAL8) {
@@ -592,6 +599,7 @@ static int tiff_unpack_lzma(TiffContext *s, AVFrame *p, uint8_t *dst, int stride
 {
     uint64_t outlen = width * (uint64_t)lines;
     int ret, line;
+    int rows = is_yuv ? (lines + s->subsampling[1] - 1) / s->subsampling[1] : lines;
     uint8_t *buf = av_malloc(outlen);
     if (!buf)
         return AVERROR(ENOMEM);
@@ -610,6 +618,12 @@ static int tiff_unpack_lzma(TiffContext *s, AVFrame *p, uint8_t *dst, int stride
         av_free(buf);
         return AVERROR_UNKNOWN;
     }
+    if (outlen < (uint64_t)width * rows) {
+        av_log(s->avctx, AV_LOG_ERROR, "Uncompressed %"PRIu64" bytes, but %"PRIu64" are needed\n",
+               outlen, (uint64_t)width * rows);
+        av_free(buf);
+        return AVERROR_INVALIDDATA;
+    }
     src = buf;
     for (line = 0; line < lines; line++) {
         if (s->bpp < 8 && s->avctx->pix_fmt == AV_PIX_FMT_PAL8) {
-- 
2.52.0


From eebd95977f3c78b01dfaf0d863e2bdaa72f84e88 Mon Sep 17 00:00:00 2001
From: Raja-89 <[email protected]>
Date: Thu, 23 Jul 2026 23:22:32 +0530
Subject: [PATCH 161/180] avfilter/dnn: fix async teardown race condition in
 all backends

When the filter graph is torn down early (e.g. at EOF with -frames:v),
the main thread calls dnn_free_model and destroys the request queue
while async inference threads may still be running. When a detached
thread finishes and tries to push its result back to the destroyed
queue, it triggers a segmentation fault.

Add ff_dnn_wait_requests() to dnn_backend_common which blocks
until all allocated request items (ctx->nireq) have been safely
returned to the request_queue. Call it from dnn_free_model in the
torch, tensorflow, openvino, and onnx backends before destroying the queue.

(cherry picked from commit b52c71e43813952650a74c57b2f31bd0166600ba)
Co-Authored-by: Fable 5
---
 libavfilter/dnn/dnn_backend_common.c   |  9 +++++++++
 libavfilter/dnn/dnn_backend_common.h   | 11 +++++++++++
 libavfilter/dnn/dnn_backend_onnx.c     |  1 +
 libavfilter/dnn/dnn_backend_openvino.c |  1 +
 libavfilter/dnn/dnn_backend_tf.c       |  1 +
 libavfilter/dnn/dnn_backend_torch.cpp  |  1 +
 6 files changed, 24 insertions(+)

diff --git a/libavfilter/dnn/dnn_backend_common.c b/libavfilter/dnn/dnn_backend_common.c
index e45eefd14d..4d6752ae33 100644
--- a/libavfilter/dnn/dnn_backend_common.c
+++ b/libavfilter/dnn/dnn_backend_common.c
@@ -22,6 +22,7 @@
  */
 
 #include "libavutil/mem.h"
+#include "libavutil/time.h"
 #include "dnn_backend_common.h"
 
 #define DNN_ASYNC_SUCCESS (void *)0
@@ -102,6 +103,14 @@ int ff_dnn_async_module_cleanup(DNNAsyncExecModule *async_module)
     return 0;
 }
 
+void ff_dnn_wait_requests(SafeQueue *request_queue, int nireq)
+{
+    if (!request_queue)
+        return;
+    while (ff_safe_queue_size(request_queue) < nireq)
+        av_usleep(10000);
+}
+
 int ff_dnn_start_inference_async(void *ctx, DNNAsyncExecModule *async_module)
 {
     int ret;
diff --git a/libavfilter/dnn/dnn_backend_common.h b/libavfilter/dnn/dnn_backend_common.h
index 9f5d37b3e0..f0b8b47397 100644
--- a/libavfilter/dnn/dnn_backend_common.h
+++ b/libavfilter/dnn/dnn_backend_common.h
@@ -25,6 +25,7 @@
 #define AVFILTER_DNN_DNN_BACKEND_COMMON_H
 
 #include "queue.h"
+#include "safe_queue.h"
 #include "../dnn_interface.h"
 #include "libavutil/thread.h"
 
@@ -112,6 +113,16 @@ int ff_dnn_fill_task(TaskItem *task, DNNExecBaseParams *exec_params, void *backe
  */
 int ff_dnn_async_module_cleanup(DNNAsyncExecModule *async_module);
 
+/**
+ * Wait for all inference requests to complete before teardown.
+ * This blocks the calling thread until all request items have been
+ * returned to the request_queue by the async inference threads.
+ *
+ * @param request_queue pointer to the SafeQueue holding request items
+ * @param nireq total number of allocated request items
+ */
+void ff_dnn_wait_requests(SafeQueue *request_queue, int nireq);
+
 /**
  * Start asynchronous inference routine for the TensorFlow
  * model on a detached thread. It calls the completion callback
diff --git a/libavfilter/dnn/dnn_backend_onnx.c b/libavfilter/dnn/dnn_backend_onnx.c
index 0ff0ffb285..6c75d6eb24 100644
--- a/libavfilter/dnn/dnn_backend_onnx.c
+++ b/libavfilter/dnn/dnn_backend_onnx.c
@@ -152,6 +152,7 @@ static void dnn_free_model_onnx(DNNModel **model)
 
     onnx_model = (ONNXModel *)(*model);
 
+    ff_dnn_wait_requests(onnx_model->request_queue, onnx_model->ctx->nireq);
     while (ff_safe_queue_size(onnx_model->request_queue) != 0) {
         ONNXRequestItem *item = (ONNXRequestItem *)ff_safe_queue_pop_front(onnx_model->request_queue);
         destroy_request_item(&item);
diff --git a/libavfilter/dnn/dnn_backend_openvino.c b/libavfilter/dnn/dnn_backend_openvino.c
index ec46e29b8a..764e8dabb5 100644
--- a/libavfilter/dnn/dnn_backend_openvino.c
+++ b/libavfilter/dnn/dnn_backend_openvino.c
@@ -517,6 +517,7 @@ static void dnn_free_model_ov(DNNModel **model)
         return;
 
     ov_model = (OVModel *)(*model);
+    ff_dnn_wait_requests(ov_model->request_queue, ov_model->ctx->nireq);
     while (ff_safe_queue_size(ov_model->request_queue) != 0) {
         OVRequestItem *item = ff_safe_queue_pop_front(ov_model->request_queue);
         if (item && item->infer_request) {
diff --git a/libavfilter/dnn/dnn_backend_tf.c b/libavfilter/dnn/dnn_backend_tf.c
index 48372bea38..07129bcdb6 100644
--- a/libavfilter/dnn/dnn_backend_tf.c
+++ b/libavfilter/dnn/dnn_backend_tf.c
@@ -486,6 +486,7 @@ static void dnn_free_model_tf(DNNModel **model)
         return;
 
     tf_model = (TFModel *)(*model);
+    ff_dnn_wait_requests(tf_model->request_queue, tf_model->ctx->nireq);
     while (ff_safe_queue_size(tf_model->request_queue) != 0) {
         TFRequestItem *item = ff_safe_queue_pop_front(tf_model->request_queue);
         destroy_request_item(&item);
diff --git a/libavfilter/dnn/dnn_backend_torch.cpp b/libavfilter/dnn/dnn_backend_torch.cpp
index 24a202f493..5f16234b8e 100644
--- a/libavfilter/dnn/dnn_backend_torch.cpp
+++ b/libavfilter/dnn/dnn_backend_torch.cpp
@@ -121,6 +121,7 @@ static void dnn_free_model_th(DNNModel **model)
     th_model = (THModel *)(*model);
 
     if (th_model->request_queue) {
+        ff_dnn_wait_requests(th_model->request_queue, th_model->ctx->nireq);
         while (ff_safe_queue_size(th_model->request_queue) != 0) {
             THRequestItem *item = (THRequestItem *)ff_safe_queue_pop_front(th_model->request_queue);
             destroy_request_item(&item);
-- 
2.52.0


From 439ac0cb4b61816376e3f78cb6ef204567de708e Mon Sep 17 00:00:00 2001
From: Lynne <[email protected]>
Date: Tue, 14 Jul 2026 00:29:22 +0900
Subject: [PATCH 162/180] vulkan_encode: fix leak and swallowed errors in
 init_base_units()

The encoded session parameters buffer was never freed, and both
GetEncodedVideoSessionParametersKHR error paths returned err, which
is always 0 at that point, reporting success on failure.

(cherry picked from commit b672ae39053759557a42f3a54f31f8413a37d871)
Co-Authored-by: Fable 5
---
 libavcodec/vulkan_encode_av1.c  | 16 +++++++++-------
 libavcodec/vulkan_encode_h264.c | 16 +++++++++-------
 libavcodec/vulkan_encode_h265.c | 16 +++++++++-------
 3 files changed, 27 insertions(+), 21 deletions(-)

diff --git a/libavcodec/vulkan_encode_av1.c b/libavcodec/vulkan_encode_av1.c
index c280f37a91..fa2c192974 100644
--- a/libavcodec/vulkan_encode_av1.c
+++ b/libavcodec/vulkan_encode_av1.c
@@ -1023,7 +1023,7 @@ static int init_base_units(AVCodecContext *avctx)
     } else {
         av_log(avctx, AV_LOG_ERROR, "Unable to get feedback for AV1 sequence header = %zu\n",
                data_size);
-        return err;
+        return AVERROR_EXTERNAL;
     }
 
     ret = vk->GetEncodedVideoSessionParametersKHR(s->hwctx->act_dev, &params_info,
@@ -1031,7 +1031,8 @@ static int init_base_units(AVCodecContext *avctx)
                                                   &data_size, data);
     if (ret != VK_SUCCESS) {
         av_log(avctx, AV_LOG_ERROR, "Error writing feedback units\n");
-        return err;
+        err = AVERROR_EXTERNAL;
+        goto end;
     }
 
     av_log(avctx, AV_LOG_VERBOSE, "Feedback units written, overrides: %i\n",
@@ -1040,20 +1041,21 @@ static int init_base_units(AVCodecContext *avctx)
     params_feedback.hasOverrides = 1;
 
     /* No need to sync any overrides */
+    err = 0;
     if (!params_feedback.hasOverrides)
-        return 0;
+        goto end;
 
     /* Parse back tne units and override */
     err = parse_feedback_units(avctx, data, data_size);
     if (err < 0)
-        return err;
+        goto end;
 
     /* Create final session parameters */
     err = create_session_params(avctx);
-    if (err < 0)
-        return err;
 
-    return 0;
+end:
+    av_free(data);
+    return err;
 }
 
 static int vulkan_encode_av1_add_obu(AVCodecContext *avctx,
diff --git a/libavcodec/vulkan_encode_h264.c b/libavcodec/vulkan_encode_h264.c
index 0327ccba0b..33a9b0a7b8 100644
--- a/libavcodec/vulkan_encode_h264.c
+++ b/libavcodec/vulkan_encode_h264.c
@@ -1148,7 +1148,7 @@ static int init_base_units(AVCodecContext *avctx)
             return AVERROR(ENOMEM);
     } else {
         av_log(avctx, AV_LOG_ERROR, "Unable to get feedback for H.264 units = %zu\n", data_size);
-        return err;
+        return AVERROR_EXTERNAL;
     }
 
     ret = vk->GetEncodedVideoSessionParametersKHR(s->hwctx->act_dev, &params_info,
@@ -1156,7 +1156,8 @@ static int init_base_units(AVCodecContext *avctx)
                                                   &data_size, data);
     if (ret != VK_SUCCESS) {
         av_log(avctx, AV_LOG_ERROR, "Error writing feedback units\n");
-        return err;
+        err = AVERROR_EXTERNAL;
+        goto end;
     }
 
     av_log(avctx, AV_LOG_VERBOSE, "Feedback units written, overrides: %i (SPS: %i PPS: %i)\n",
@@ -1168,22 +1169,23 @@ static int init_base_units(AVCodecContext *avctx)
     h264_params_feedback.hasStdPPSOverrides = 1;
 
     /* No need to sync any overrides */
+    err = 0;
     if (!params_feedback.hasOverrides)
-        return 0;
+        goto end;
 
     /* Parse back tne units and override */
     err = parse_feedback_units(avctx, data, data_size,
                                h264_params_feedback.hasStdSPSOverrides,
                                h264_params_feedback.hasStdPPSOverrides);
     if (err < 0)
-        return err;
+        goto end;
 
     /* Create final session parameters */
     err = create_session_params(avctx);
-    if (err < 0)
-        return err;
 
-    return 0;
+end:
+    av_free(data);
+    return err;
 }
 
 static int vulkan_encode_h264_add_nal(AVCodecContext *avctx,
diff --git a/libavcodec/vulkan_encode_h265.c b/libavcodec/vulkan_encode_h265.c
index 8831e3220c..1218017555 100644
--- a/libavcodec/vulkan_encode_h265.c
+++ b/libavcodec/vulkan_encode_h265.c
@@ -1317,7 +1317,7 @@ static int init_base_units(AVCodecContext *avctx)
             return AVERROR(ENOMEM);
     } else {
         av_log(avctx, AV_LOG_ERROR, "Unable to get feedback for H.265 units = %zu\n", data_size);
-        return err;
+        return AVERROR_EXTERNAL;
     }
 
     ret = vk->GetEncodedVideoSessionParametersKHR(s->hwctx->act_dev, &params_info,
@@ -1325,7 +1325,8 @@ static int init_base_units(AVCodecContext *avctx)
                                                   &data_size, data);
     if (ret != VK_SUCCESS) {
         av_log(avctx, AV_LOG_ERROR, "Error writing feedback units\n");
-        return err;
+        err = AVERROR_EXTERNAL;
+        goto end;
     }
 
     av_log(avctx, AV_LOG_VERBOSE, "Feedback units written, overrides: %i (SPS: %i PPS: %i VPS: %i)\n",
@@ -1339,22 +1340,23 @@ static int init_base_units(AVCodecContext *avctx)
     h265_params_feedback.hasStdPPSOverrides = 1;
 
     /* No need to sync any overrides */
+    err = 0;
     if (!params_feedback.hasOverrides)
-        return 0;
+        goto end;
 
     /* Parse back tne units and override */
     err = parse_feedback_units(avctx, data, data_size,
                                h265_params_feedback.hasStdSPSOverrides,
                                h265_params_feedback.hasStdPPSOverrides);
     if (err < 0)
-        return err;
+        goto end;
 
     /* Create final session parameters */
     err = create_session_params(avctx);
-    if (err < 0)
-        return err;
 
-    return 0;
+end:
+    av_free(data);
+    return err;
 }
 
 static int vulkan_encode_h265_add_nal(AVCodecContext *avctx,
-- 
2.52.0


From b0692eb266b9f0132a0afc7d3a698e1e6ceb8c6f Mon Sep 17 00:00:00 2001
From: Christopher Decker <[email protected]>
Date: Thu, 23 Jul 2026 11:01:30 -0400
Subject: [PATCH 163/180] avformat/os_support: fix return value of win32_rename

The return value of MoveFileExW was not being correctly interpreted,
see https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-movefileexw.

On Windows a failed rename over the file: protocol now surfaces as failed to rename file %s to %s:
Operation not permitted (ff_rename, libavformat/avio.c:867) plus an AVERROR(EPERM) return, where previously the muxer
reported success and the user was left with a missing or stale output file and no diagnostic. This affects the
write-to-temp-then-rename paths in hlsenc, dashenc, hdsenc, smoothstreamingenc, segment, and img2enc (e.g. HLS/DASH
playlist updates, -write_temp_file, segment list finalization).

Also, added unit tests to exercise the rename function.

Signed-off-by: Christopher Decker <[email protected]>
(cherry picked from commit 94ff335d762ad08f2557a320def57f287d74e2a5)
Co-Authored-by: Fable 5
---
 libavformat/Makefile       |  1 +
 libavformat/os_support.h   |  4 +-
 libavformat/tests/rename.c | 97 ++++++++++++++++++++++++++++++++++++++
 tests/fate/libavformat.mak |  5 ++
 4 files changed, 105 insertions(+), 2 deletions(-)
 create mode 100644 libavformat/tests/rename.c

diff --git a/libavformat/Makefile b/libavformat/Makefile
index e76dc35c31..0d2e757b27 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -779,6 +779,7 @@ SKIPHEADERS-$(CONFIG_NETWORK)            += network.h rtsp.h
 
 TESTPROGS = id3v2                                                       \
             mkdir                                                       \
+            rename                                                      \
             seek                                                        \
             url                                                         \
             seek_utils
diff --git a/libavformat/os_support.h b/libavformat/os_support.h
index 8479163f70..4fa5ca3c52 100644
--- a/libavformat/os_support.h
+++ b/libavformat/os_support.h
@@ -279,7 +279,7 @@ static inline int win32_rename(const char *src_utf8, const char *dest_utf8)
         goto fallback;
     }
 
-    ret = MoveFileExW(src_w, dest_w, MOVEFILE_REPLACE_EXISTING);
+    ret = (MoveFileExW(src_w, dest_w, MOVEFILE_REPLACE_EXISTING) == 0) ? -1 : 0;
     av_free(src_w);
     av_free(dest_w);
     // Lacking proper mapping from GetLastError() error codes to errno codes
@@ -290,7 +290,7 @@ static inline int win32_rename(const char *src_utf8, const char *dest_utf8)
 fallback:
     /* filename may be be in CP_ACP */
 #if !HAVE_UWP
-    ret = MoveFileExA(src_utf8, dest_utf8, MOVEFILE_REPLACE_EXISTING);
+    ret = (MoveFileExA(src_utf8, dest_utf8, MOVEFILE_REPLACE_EXISTING) == 0) ? -1 : 0;
     if (ret)
         errno = EPERM;
 #else
diff --git a/libavformat/tests/rename.c b/libavformat/tests/rename.c
new file mode 100644
index 0000000000..8f3aa44ff8
--- /dev/null
+++ b/libavformat/tests/rename.c
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2026 Christopher Decker
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "libavutil/random_seed.h"
+
+#include "libavformat/os_support.h"
+
+static int create_file(const char *path)
+{
+    FILE *f = fopen(path, "wb");
+    if (!f)
+        return -1;
+    fputs("ffmpeg rename test\n", f);
+    fclose(f);
+    return 0;
+}
+
+static int file_exists(const char *path)
+{
+    FILE *f = fopen(path, "rb");
+    if (!f)
+        return 0;
+    fclose(f);
+    return 1;
+}
+
+int main(void)
+{
+    char src[64];
+    char dst[64];
+    unsigned seed = av_get_random_seed();
+    int ret = 0;
+
+    snprintf(src, sizeof(src), "ff-rename-test-%08x.src", seed);
+    snprintf(dst, sizeof(dst), "ff-rename-test-%08x.dst", seed);
+
+    if (create_file(src) < 0) {
+        perror("create src");
+        return 1;
+    }
+
+    /* rename() must follow POSIX semantics and return 0 on success. */
+    if (rename(src, dst) != 0) {
+        perror("rename");
+        ret = 1;
+        goto cleanup;
+    }
+
+    if (file_exists(src)) {
+        fprintf(stderr, "source still exists after rename\n");
+        ret = 1;
+        goto cleanup;
+    }
+
+    if (!file_exists(dst)) {
+        fprintf(stderr, "destination missing after rename\n");
+        ret = 1;
+        goto cleanup;
+    }
+
+    /* Renaming a nonexistent source must fail with a -1 return. */
+    if (rename(src, dst) != -1) {
+        fprintf(stderr, "rename of nonexistent source unexpectedly succeeded\n");
+        ret = 1;
+        goto cleanup;
+    }
+
+cleanup:
+    unlink(src);
+    unlink(dst);
+    return ret;
+}
diff --git a/tests/fate/libavformat.mak b/tests/fate/libavformat.mak
index 989515a646..7bd20cf9c8 100644
--- a/tests/fate/libavformat.mak
+++ b/tests/fate/libavformat.mak
@@ -7,6 +7,11 @@ fate-mkdir: libavformat/tests/mkdir$(EXESUF)
 fate-mkdir: CMD = run libavformat/tests/mkdir$(EXESUF)
 fate-mkdir: CMP = null
 
+FATE_LIBAVFORMAT += fate-rename
+fate-rename: libavformat/tests/rename$(EXESUF)
+fate-rename: CMD = run libavformat/tests/rename$(EXESUF)
+fate-rename: CMP = null
+
 FATE_LIBAVFORMAT-$(CONFIG_NETWORK) += fate-noproxy
 fate-noproxy: libavformat/tests/noproxy$(EXESUF)
 fate-noproxy: CMD = run libavformat/tests/noproxy$(EXESUF)
-- 
2.52.0


From 5da834256bdbe27e356e5752426bd15e53bf06ff Mon Sep 17 00:00:00 2001
From: Romain Beauxis <[email protected]>
Date: Sun, 2 Aug 2026 11:46:06 -0500
Subject: [PATCH 164/180] avformat/mp3enc: fix underflow of the LAME encoder
 delay

AV_RL32() is unsigned, so a skip_samples value below 528 + 1 wraps around
instead of clamping to zero and is written out as a delay of 4095 samples.

(cherry picked from commit 146e0f7b792033848e549c58ff928645fdeb5991)
Signed-off-by: Romain Beauxis <[email protected]>
---
 libavformat/mp3enc.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/libavformat/mp3enc.c b/libavformat/mp3enc.c
index 87c13f92bd..131fb231fc 100644
--- a/libavformat/mp3enc.c
+++ b/libavformat/mp3enc.c
@@ -367,9 +367,9 @@ static int mp3_write_audio_packet(AVFormatContext *s, AVPacket *pkt)
                                                 AV_PKT_DATA_SKIP_SAMPLES,
                                                 &side_data_size);
             if (side_data && side_data_size >= 10) {
-                mp3->padding = FFMAX(AV_RL32(side_data + 4) + 528 + 1, 0);
+                mp3->padding = FFMAX((int64_t)AV_RL32(side_data + 4) + 528 + 1, 0);
                 if (!mp3->delay)
-                    mp3->delay =  FFMAX(AV_RL32(side_data) - 528 - 1, 0);
+                    mp3->delay =  FFMAX((int64_t)AV_RL32(side_data) - 528 - 1, 0);
             } else {
                 mp3->padding = 0;
             }
-- 
2.52.0


From 2fb664dc33216d75751e4bc42c413107186a7720 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Fri, 2 Nov 2018 01:36:21 +0100
Subject: [PATCH 165/180] RELEASE_NOTES: Based on the version from 8.0

Name suggested by 2 people on ML, all other suggestions had only 1 supporter

Signed-off-by: Michael Niedermayer <[email protected]>
---
 RELEASE_NOTES | 15 +++++++++++++++
 1 file changed, 15 insertions(+)
 create mode 100644 RELEASE_NOTES

diff --git a/RELEASE_NOTES b/RELEASE_NOTES
new file mode 100644
index 0000000000..03354afbe4
--- /dev/null
+++ b/RELEASE_NOTES
@@ -0,0 +1,15 @@
+
+              ┌────────────────────────────────────┐
+              │ RELEASE NOTES for FFmpeg 9.0 "Lei" │
+              └────────────────────────────────────┘
+
+   The FFmpeg Project proudly presents FFmpeg 9.0 "Lei", about 4
+   months after the release of FFmpeg 8.1.
+
+   A complete Changelog is available at the root of the project, and the
+   complete Git history on https://git.ffmpeg.org/gitweb/ffmpeg.git
+
+   We hope you will like this release as much as we enjoyed working on it, and
+   as usual, if you have any questions about it, or any FFmpeg related topic,
+   feel free to join us on the #ffmpeg IRC channel (on irc.libera.chat) or ask
+   on the mailing-lists.
-- 
2.52.0


From 301508d12647c3dcc22ddd08fbbf714909d7f02a Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Thu, 30 Jul 2026 16:01:05 +0200
Subject: [PATCH 166/180] avformat/mov: reject a trun sample count the input
 cannot hold

Fixes: OOM
Fixes: 525088811/clusterfuzz-testcase-minimized-ffmpeg_DEMUXER_fuzzer-5229499332231168
Found-by: continuous fuzzing process https://github.com/google/oss-fuzz/tree/master/projects/ffmpeg
(cherry picked from commit ae0e0ba3c3df840191e7a4c9b76494372ec53202)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavformat/mov.c | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/libavformat/mov.c b/libavformat/mov.c
index 508ea97637..5c4c33c60d 100644
--- a/libavformat/mov.c
+++ b/libavformat/mov.c
@@ -6010,6 +6010,31 @@ static int mov_read_trun(MOVContext *c, AVIOContext *pb, MOVAtom atom)
     if (flags & MOV_TRUN_DATA_OFFSET)        data_offset        = avio_rb32(pb);
     if (flags & MOV_TRUN_FIRST_SAMPLE_FLAGS) first_sample_flags = avio_rb32(pb);
 
+    int entry_size =  !!(flags & MOV_TRUN_SAMPLE_DURATION) * 4
+                    + !!(flags & MOV_TRUN_SAMPLE_SIZE)     * 4
+                    + !!(flags & MOV_TRUN_SAMPLE_FLAGS)    * 4
+                    + !!(flags & MOV_TRUN_SAMPLE_CTS)      * 4;
+    int64_t sample_data_size = avio_size(sc->pb);
+    int64_t max_entries = INT64_MAX;
+
+    if (sample_data_size > 0)
+        max_entries = sample_data_size - sti->nb_index_entries;
+    if (entry_size) {
+        int64_t size = sc->pb == pb ? sample_data_size : avio_size(pb);
+        int64_t pos  = avio_tell(pb);
+        int64_t left = atom.size - 8 - !!(flags & MOV_TRUN_DATA_OFFSET)        * 4
+                                     - !!(flags & MOV_TRUN_FIRST_SAMPLE_FLAGS) * 4;
+
+        if (pos >= 0 && size >= pos)
+            left = FFMIN(left, size - pos);
+        max_entries = FFMIN(max_entries, left / entry_size);
+    }
+    if (entries > max_entries) {
+        av_log(c->fc, AV_LOG_ERROR, "trun sample count %u exceeds the %"PRId64" "
+               "samples the input can hold\n", entries, max_entries);
+        return AVERROR_INVALIDDATA;
+    }
+
     frag_stream_info = get_current_frag_stream_info(&c->frag_index);
     if (frag_stream_info) {
         if (frag_stream_info->next_trun_dts != AV_NOPTS_VALUE) {
-- 
2.52.0


From 2493e213ea86e6e21cf464e9365f6f8b609f3472 Mon Sep 17 00:00:00 2001
From: Lynne <[email protected]>
Date: Sun, 2 Aug 2026 06:30:59 +0900
Subject: [PATCH 167/180] vulkan_encode_av1: set primary_ref_frame to a
 reference name, not a slot

primary_ref_frame is an index into the seven reference names, but the
code assigned it the reference's DPB slot. The two coincide only while
the reference sits in slot 0; once it rotates to slot 1,
referenceNameSlotIndices[primary_ref_frame] is -1, which is invalid,
and NVIDIA drivers lose the device. Inherited from vaapi_encode_av1,
where the same confusion is harmless as the raw frame header maps
every reference name to the same slot.

Have set_name_slot() return the name it picked and use that.

Fixes #20540.

(cherry picked from commit 0da8f2f4eea877d234cbc88fa394ddfa028ffa94)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/vulkan_encode_av1.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/libavcodec/vulkan_encode_av1.c b/libavcodec/vulkan_encode_av1.c
index fa2c192974..d98b3a53b1 100644
--- a/libavcodec/vulkan_encode_av1.c
+++ b/libavcodec/vulkan_encode_av1.c
@@ -159,7 +159,7 @@ static int init_pic_rc(AVCodecContext *avctx, FFHWBaseEncodePicture *pic,
     return 0;
 }
 
-static void set_name_slot(int slot, int *slot_indices, uint32_t allowed_idx, int group)
+static int set_name_slot(int slot, int *slot_indices, uint32_t allowed_idx, int group)
 {
     int from = group ? AV1_REF_FRAME_GOLDEN : 0;
     int to = group ? AV1_REFS_PER_FRAME : AV1_REF_FRAME_GOLDEN;
@@ -167,7 +167,7 @@ static void set_name_slot(int slot, int *slot_indices, uint32_t allowed_idx, int
     for (int i = from; i < to; i++) {
         if ((slot_indices[i] == -1) && (allowed_idx & (1 << i))) {
             slot_indices[i] = slot;
-            return;
+            return i;
         }
     }
 
@@ -377,12 +377,12 @@ static int init_pic_params(AVCodecContext *avctx, FFHWBaseEncodePicture *pic,
         for (int i = 0; i < AV1_REFS_PER_FRAME; i++)
             ap->av1pic_info.ref_frame_idx[i] = ap_ref->slot;
 
-        ap->av1pic_info.primary_ref_frame = ap_ref->slot;
         ap->av1pic_info.ref_order_hint[ap_ref->slot] = ref->display_order - ap_ref->last_idr_frame;
         rc_group = VK_VIDEO_ENCODE_AV1_RATE_CONTROL_GROUP_PREDICTIVE_KHR;
         pred_mode = VK_VIDEO_ENCODE_AV1_PREDICTION_MODE_SINGLE_REFERENCE_KHR;
         ref_name_mask = enc->caps.singleReferenceNameMask;
-        set_name_slot(ap_ref->av1pic_info.current_frame_id, name_slots, ref_name_mask, 0);
+        ap->av1pic_info.primary_ref_frame =
+            set_name_slot(ap_ref->av1pic_info.current_frame_id, name_slots, ref_name_mask, 0);
 
 //        vpic->ref_frame_ctrl_l0.fields.search_idx0 = AV1_REF_FRAME_LAST;
 
@@ -422,11 +422,11 @@ static int init_pic_params(AVCodecContext *avctx, FFHWBaseEncodePicture *pic,
         ref = pic->refs[0][pic->nb_refs[0] - 1];
         ap_ref = ref->codec_priv;
         ap->last_idr_frame = ap_ref->last_idr_frame;
-        ap->av1pic_info.primary_ref_frame = ap_ref->slot;
         ap->av1pic_info.ref_order_hint[ap_ref->slot] = ref->display_order - ap_ref->last_idr_frame;
         for (int i = 0; i < AV1_REF_FRAME_GOLDEN; i++)
             ap->av1pic_info.ref_frame_idx[i] = ap_ref->slot;
-        set_name_slot(ap_ref->av1pic_info.current_frame_id, name_slots, ref_name_mask, 0);
+        ap->av1pic_info.primary_ref_frame =
+            set_name_slot(ap_ref->av1pic_info.current_frame_id, name_slots, ref_name_mask, 0);
 
         ref = pic->refs[1][pic->nb_refs[1] - 1];
         ap_ref = ref->codec_priv;
-- 
2.52.0


From 00cc404767e8541a1ba2d87607480da33458773a Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Sun, 26 Jul 2026 23:06:23 +0200
Subject: [PATCH 168/180] avcodec/utils: factor the timecode fields out of
 ff_alloc_timecode_sei

(cherry picked from commit a290dec0bfe45f68880dcbf7cddea3a4939d00b2)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/utils.c | 30 ++++++++++++++++++------------
 1 file changed, 18 insertions(+), 12 deletions(-)

diff --git a/libavcodec/utils.c b/libavcodec/utils.c
index 256fb4ed39..5ac20c6a5e 100644
--- a/libavcodec/utils.c
+++ b/libavcodec/utils.c
@@ -986,6 +986,22 @@ AVCPBProperties *av_cpb_properties_alloc(size_t *size)
     return props;
 }
 
+static void put_timecode_fields(PutBitContext *pb, AVRational rate, uint32_t tc)
+{
+    unsigned hours, minutes, seconds, frames, drop;
+
+    ff_timecode_set_smpte(&drop, &hours, &minutes, &seconds, &frames, rate, tc, 0, 0);
+
+    put_bits(pb, 5, 0);      // counting_type
+    put_bits(pb, 1, 1);      // full_timestamp_flag
+    put_bits(pb, 1, 0);      // discontinuity_flag
+    put_bits(pb, 1, drop);   // cnt_dropped_flag
+    put_bits(pb, 9, frames);
+    put_bits(pb, 6, seconds);
+    put_bits(pb, 6, minutes);
+    put_bits(pb, 5, hours);
+}
+
 int ff_alloc_timecode_sei(const AVFrame *frame, AVRational rate, size_t prefix_len,
                      void **data, size_t *sei_size)
 {
@@ -1015,20 +1031,10 @@ int ff_alloc_timecode_sei(const AVFrame *frame, AVRational rate, size_t prefix_l
     put_bits(&pb, 2, m); // num_clock_ts
 
     for (int j = 1; j <= m; j++) {
-        unsigned hh, mm, ss, ff, drop;
-        ff_timecode_set_smpte(&drop, &hh, &mm, &ss, &ff, rate, tc[j], 0, 0);
-
         put_bits(&pb, 1, 1); // clock_timestamp_flag
         put_bits(&pb, 1, 1); // units_field_based_flag
-        put_bits(&pb, 5, 0); // counting_type
-        put_bits(&pb, 1, 1); // full_timestamp_flag
-        put_bits(&pb, 1, 0); // discontinuity_flag
-        put_bits(&pb, 1, drop);
-        put_bits(&pb, 9, ff);
-        put_bits(&pb, 6, ss);
-        put_bits(&pb, 6, mm);
-        put_bits(&pb, 5, hh);
-        put_bits(&pb, 5, 0);
+        put_timecode_fields(&pb, rate, tc[j]);
+        put_bits(&pb, 5, 0); // time_offset_length
     }
     flush_put_bits(&pb);
 
-- 
2.52.0


From 330b026202985f37cb9657a7b82cf3b05144e1c1 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Sun, 26 Jul 2026 23:07:35 +0200
Subject: [PATCH 169/180] avcodec/utils: add ff_alloc_timecode_metadata_av1()

(cherry picked from commit 5589dfd9bc0153f9c29832f39bfe1989ebf5f5aa)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/internal.h |  3 +++
 libavcodec/utils.c    | 31 +++++++++++++++++++++++++++++++
 2 files changed, 34 insertions(+)

diff --git a/libavcodec/internal.h b/libavcodec/internal.h
index 137fd52745..e742b170cf 100644
--- a/libavcodec/internal.h
+++ b/libavcodec/internal.h
@@ -184,6 +184,9 @@ int avpriv_codec_get_cap_skip_frame_fill_param(const AVCodec *codec);
 int ff_alloc_timecode_sei(const AVFrame *frame, AVRational rate, size_t prefix_len,
                      void **data, size_t *sei_size);
 
+int ff_alloc_timecode_metadata_av1(const AVFrame *frame, AVRational rate,
+                                   void **data, size_t *size);
+
 /**
  * Get an estimated video bitrate based on frame size, frame rate and coded
  * bits per pixel.
diff --git a/libavcodec/utils.c b/libavcodec/utils.c
index 5ac20c6a5e..cb6cf2d6a1 100644
--- a/libavcodec/utils.c
+++ b/libavcodec/utils.c
@@ -1041,6 +1041,37 @@ int ff_alloc_timecode_sei(const AVFrame *frame, AVRational rate, size_t prefix_l
     return 0;
 }
 
+int ff_alloc_timecode_metadata_av1(const AVFrame *frame, AVRational rate,
+                                   void **data, size_t *size)
+{
+    AVFrameSideData *sd = NULL;
+    PutBitContext pb;
+    uint32_t *tc;
+
+    if (frame)
+        sd = av_frame_get_side_data(frame, AV_FRAME_DATA_S12M_TIMECODE);
+
+    *data = NULL;
+    if (!sd)
+        return 0;
+    tc = (uint32_t*)sd->data;
+    if (!(tc[0] & 3)) // num_clock_ts
+        return 0;
+
+    *size = 5;
+    *data = av_mallocz(*size);
+    if (!*data)
+        return AVERROR(ENOMEM);
+
+    init_put_bits(&pb, *data, *size);
+    put_timecode_fields(&pb, rate, tc[1]);
+    put_bits(&pb, 5, 1); // time_offset_length
+    put_bits(&pb, 1, 0); // time_offset_value
+    flush_put_bits(&pb);
+
+    return 0;
+}
+
 int64_t ff_guess_coded_bitrate(AVCodecContext *avctx)
 {
     AVRational framerate = avctx->framerate;
-- 
2.52.0


From 3538578ed0a4a43d057a575fb327444e815dbbb4 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Sun, 26 Jul 2026 23:08:14 +0200
Subject: [PATCH 170/180] avcodec/nvenc: write AV1 timecode metadata in AV1
 syntax

Fixes: https://code.ffmpeg.org/FFmpeg/FFmpeg/issues/20610

Tested on 5090 by claude

(cherry picked from commit afd059c340feb7379d33f8102b7dd7d01288fba1)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavcodec/nvenc.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/libavcodec/nvenc.c b/libavcodec/nvenc.c
index 1c931d43af..9203fb65c0 100644
--- a/libavcodec/nvenc.c
+++ b/libavcodec/nvenc.c
@@ -2718,8 +2718,10 @@ static int prepare_sei_data_array(AVCodecContext *avctx, const AVFrame *frame)
         void *tc_data = NULL;
         size_t tc_size = 0;
 
-        if (ff_alloc_timecode_sei(frame, avctx->framerate, 0, &tc_data, &tc_size) < 0) {
-            av_log(ctx, AV_LOG_ERROR, "Not enough memory for timecode sei, skipping\n");
+        if ((avctx->codec->id == AV_CODEC_ID_AV1 ?
+             ff_alloc_timecode_metadata_av1(frame, avctx->framerate, &tc_data, &tc_size) :
+             ff_alloc_timecode_sei(frame, avctx->framerate, 0, &tc_data, &tc_size)) < 0) {
+            av_log(ctx, AV_LOG_ERROR, "Not enough memory for timecode, skipping\n");
         }
 
         if (tc_data) {
-- 
2.52.0


From d32b387f2b0a484599d4587d651891f0c63c4238 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Wed, 22 Jul 2026 05:57:51 +0200
Subject: [PATCH 171/180] avformat/rawutils: reject raw RGB frames that do not
 fit an AVPacket

Fixes: integer overflow
Fixes: out of array access
Fixes: payload.film
Fixes: czK1F83k3zvT
Found-by: Clouditera Security, Z.ai Security, NSFOCUS
(cherry picked from commit d3ad8a7fee6a647c6362e4a105d949282d50a98f)
Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavformat/rawutils.c | 20 +++++++++++++-------
 1 file changed, 13 insertions(+), 7 deletions(-)

diff --git a/libavformat/rawutils.c b/libavformat/rawutils.c
index e44c946d47..bf5113555f 100644
--- a/libavformat/rawutils.c
+++ b/libavformat/rawutils.c
@@ -29,15 +29,21 @@ int ff_reshuffle_raw_rgb(AVFormatContext *s, AVPacket **ppkt, AVCodecParameters
     int ret;
     AVPacket *pkt = *ppkt;
     int64_t bpc = par->bits_per_coded_sample != 15 ? par->bits_per_coded_sample : 16;
-    int min_stride = (par->width * bpc + 7) >> 3;
-    int with_pal_size = min_stride * par->height + 1024;
-    int contains_pal = bpc == 8 && pkt->size == with_pal_size;
-    int size = contains_pal ? min_stride * par->height : pkt->size;
-    int stride = size / par->height;
-    int padding = expected_stride - FFMIN(expected_stride, stride);
-    int y;
+    int64_t min_stride = (par->width * bpc + 7) >> 3;
+    int with_pal_size, contains_pal, size, stride, padding, y;
     AVPacket *new_pkt;
 
+    if (par->height <= 0 || min_stride <= 0 || expected_stride <= 0 ||
+        min_stride      > (INT_MAX - 1024) / par->height ||
+        expected_stride > (INT_MAX - AV_INPUT_BUFFER_PADDING_SIZE) / par->height)
+        return AVERROR(EINVAL);
+
+    with_pal_size = min_stride * par->height + 1024;
+    contains_pal  = bpc == 8 && pkt->size == with_pal_size;
+    size          = contains_pal ? min_stride * par->height : pkt->size;
+    stride        = size / par->height;
+    padding       = expected_stride - FFMIN(expected_stride, stride);
+
     if (pkt->size == expected_stride * par->height)
         return 0;
     if (size != stride * par->height)
-- 
2.52.0


From b21c9530660a6d3e394f71a7244c01baa0bfc172 Mon Sep 17 00:00:00 2001
From: Oren Jack Maurice <[email protected]>
Date: Sun, 9 Aug 2026 11:59:12 +0300
Subject: [PATCH 172/180] avcodec/dolby_e: extract bit allocation and
 MDCT/IMDCT transform into shared helpers

Move the exponent/mantissa tables, bit-allocation model, window table and
MDCT/IMDCT transform out of dolby_e.c and into new dolby_e_bitalloc.{c,h}
and dolby_e_transform.{c,h}. The bit allocator and windowed transform are
symmetric between encode and decode (the allocator is run identically in
both directions, and Dolby E's own spec requires an identical analysis
and synthesis window per US 5,899,969), so a following change can reuse
them from a new encoder without duplicating this code.

No functional change to the decoder; this is a pure move plus adding a
forward MDCT and MDCT-with-fold path that the decoder itself never calls
(only ff_dolby_e_imdct_calc(), the exact code previously inlined in
dolby_e.c, is exercised by the existing decoder and its fate-dolby-e /
fate-s337m-demux regression tests).

Development-method: AI-assisted (Claude Code); reviewed by the submitter
before posting.

Signed-off-by: Oren Jack Maurice <[email protected]>
Signed-off-by: Oren Jack Maurice <[email protected]>
---
 libavcodec/Makefile            |   2 +-
 libavcodec/dolby_e.c           | 728 +--------------------------------
 libavcodec/dolby_e_bitalloc.c  | 492 ++++++++++++++++++++++
 libavcodec/dolby_e_bitalloc.h  |  76 ++++
 libavcodec/dolby_e_transform.c | 330 +++++++++++++++
 libavcodec/dolby_e_transform.h |  84 ++++
 6 files changed, 998 insertions(+), 714 deletions(-)
 create mode 100644 libavcodec/dolby_e_bitalloc.c
 create mode 100644 libavcodec/dolby_e_bitalloc.h
 create mode 100644 libavcodec/dolby_e_transform.c
 create mode 100644 libavcodec/dolby_e_transform.h

diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index 95ba308504..33cfceae8d 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -340,7 +340,7 @@ OBJS-$(CONFIG_DFPWM_DECODER)           += dfpwmdec.o
 OBJS-$(CONFIG_DFPWM_ENCODER)           += dfpwmenc.o
 OBJS-$(CONFIG_DNXHD_DECODER)           += dnxhddec.o dnxhddata.o
 OBJS-$(CONFIG_DNXHD_ENCODER)           += dnxhdenc.o dnxhddata.o
-OBJS-$(CONFIG_DOLBY_E_DECODER)         += dolby_e.o dolby_e_parse.o kbdwin.o
+OBJS-$(CONFIG_DOLBY_E_DECODER)         += dolby_e.o dolby_e_bitalloc.o dolby_e_transform.o dolby_e_parse.o kbdwin.o
 OBJS-$(CONFIG_DPX_DECODER)             += dpx.o
 OBJS-$(CONFIG_DPX_ENCODER)             += dpxenc.o
 OBJS-$(CONFIG_DSD_LSBF_DECODER)        += dsddec.o dsd.o
diff --git a/libavcodec/dolby_e.c b/libavcodec/dolby_e.c
index 2d32ac2832..1e26927469 100644
--- a/libavcodec/dolby_e.c
+++ b/libavcodec/dolby_e.c
@@ -24,13 +24,13 @@
 #include "libavutil/mem.h"
 #include "libavutil/mem_internal.h"
 #include "libavutil/opt.h"
-#include "libavutil/tx.h"
 
 #include "codec_internal.h"
 #include "decode.h"
 #include "get_bits.h"
 #include "dolby_e.h"
-#include "kbdwin.h"
+#include "dolby_e_bitalloc.h"
+#include "dolby_e_transform.h"
 
 #define MAX_SEGMENTS    2
 
@@ -46,20 +46,6 @@ enum DBEOutputChannelOrder {
     CHANNEL_ORDER_CODED,
 };
 
-typedef struct DBEGroup {
-    uint8_t         nb_exponent;
-    uint8_t         nb_bias_exp[MAX_MSTR_EXP];
-    uint16_t        exp_ofs;
-    uint16_t        mnt_ofs;
-    const uint8_t   *nb_mantissa;
-    uint8_t         imdct_idx;
-    uint8_t         imdct_phs;
-    uint16_t        win_len;
-    uint16_t        dst_ofs;
-    uint16_t        win_ofs;
-    uint16_t        src_ofs;
-} DBEGroup;
-
 typedef struct DBEChannel {
     int     gr_code;
     int     bw_code;
@@ -85,531 +71,21 @@ typedef struct DBEDecodeContext {
 
     DECLARE_ALIGNED(32, float, history)[MAX_CHANNELS][256];
 
-    AVTXContext         *imdct[2][3];
-    av_tx_fn             imdct_fn[2][3];
+    DBETransform         transform;
     AVFloatDSPContext   *fdsp;
 } DBEDecodeContext;
 
-static const int8_t lfe_channel_tab[MAX_PROG_CONF + 1] = {
-     5,  5, -1, -1, -1, -1, -1, -1, -1, -1, -1, 4,
-    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  5, 5
-};
-
 static const uint8_t ch_reorder_4[4] = { 0, 2, 1, 3 };
 static const uint8_t ch_reorder_6[6] = { 0, 2, 4, 1, 3, 5 };
 static const uint8_t ch_reorder_8[8] = { 0, 2, 6, 4, 1, 3, 7, 5 };
 static const uint8_t ch_reorder_n[8] = { 0, 2, 4, 6, 1, 3, 5, 7 };
 
-
-static const uint8_t nb_groups_tab[4] = { 1, 8, 7, 1 };
-
-static const uint8_t nb_mstr_exp_tab[4] = { 2, 2, 2, 1 };
-
-static const uint8_t nb_mantissa_38[38] = {
-     1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
-     2,  2,  2,  2,  2,  2,  3,  3,  3,  4,  4,  4,  5,  5,  6,  6,
-     7,  8,  9, 10, 11, 12,
-};
-
-static const uint8_t nb_mantissa_44[44] = {
-     1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  2,
-     2,  2,  2,  2,  2,  3,  3,  3,  3,  4,  4,  5,  5,  6,  7,  7,
-     8,  9, 10, 11, 12, 13, 15, 16, 18, 20, 22, 25,
-};
-
-static const uint8_t nb_mantissa_50[50] = {
-     1,  1,  1,  1,  1,  1,  1,  1,  2,  2,  2,  2,  2,  3,  3,  3,
-     3,  4,  4,  5,  5,  6,  6,  7,  8,  9,  9, 10, 12, 13, 14, 16,
-    18, 19, 22, 24, 27, 29, 32, 36, 40, 44, 49, 54, 60, 66, 74, 82,
-    90, 100,
-};
-
-static const uint8_t imdct_bits_tab[3] = { 8, 9, 11 };
-
-static const DBEGroup grp_tab_0[1] = {
-    { 50, { 27, 23 }, 0, 0, nb_mantissa_50, 2, 0, 1152, 0, 1408, 0 },
-};
-
-static const DBEGroup grp_tab_1[8] = {
-    { 38, { 12, 26 }, 0, 0, nb_mantissa_38, 0, 0, 192, 0, 256, 0 },
-    { 38, { 12, 26 }, 38, 128, nb_mantissa_38, 0, 1, 256, 64, 448, 0 },
-    { 38, { 12, 26 }, 76, 256, nb_mantissa_38, 0, 1, 256, 192, 704, 0 },
-    { 38, { 12, 26 }, 114, 384, nb_mantissa_38, 0, 1, 256, 320, 0, 0 },
-    { 38, { 12, 26 }, 152, 512, nb_mantissa_38, 0, 1, 256, 448, 0, 0 },
-    { 38, { 12, 26 }, 190, 640, nb_mantissa_38, 0, 1, 256, 576, 0, 0 },
-    { 38, { 12, 26 }, 228, 768, nb_mantissa_38, 0, 1, 256, 704, 0, 0 },
-    { 38, { 12, 26 }, 266, 896, nb_mantissa_38, 0, 1, 256, 832, 0, 0 },
-};
-
-static const DBEGroup grp_tab_2[7] = {
-    { 38, { 12, 26 }, 0, 0, nb_mantissa_38, 0, 0, 192, 0, 256, 0 },
-    { 38, { 12, 26 }, 38, 128, nb_mantissa_38, 0, 1, 256, 64, 448, 0 },
-    { 38, { 12, 26 }, 76, 256, nb_mantissa_38, 0, 1, 256, 192, 704, 0 },
-    { 38, { 12, 26 }, 114, 384, nb_mantissa_38, 0, 1, 256, 320, 0, 0 },
-    { 38, { 12, 26 }, 152, 512, nb_mantissa_38, 0, 1, 256, 448, 0, 0 },
-    { 38, { 12, 26 }, 190, 640, nb_mantissa_38, 0, 1, 256, 576, 0, 0 },
-    { 44, { 19, 25 }, 228, 768, nb_mantissa_44, 1, 1, 448, 704, 960, 64 },
-};
-
-static const DBEGroup grp_tab_3[1] = {
-    { 21, { 21 }, 0, 0, nb_mantissa_50, 2, 0, 1152, 0, 1408, 0 },
-};
-
-static const DBEGroup grp_tab_4[1] = {
-    { 50, { 27, 23 }, 0, 0, nb_mantissa_50, 2, 2, 1152, 0, 1408, 896 },
-};
-
-static const DBEGroup grp_tab_5[8] = {
-    { 38, { 12, 26 }, 0, 0, nb_mantissa_38, 0, 1, 256, 64, 0, 0 },
-    { 38, { 12, 26 }, 38, 128, nb_mantissa_38, 0, 1, 256, 192, 0, 0 },
-    { 38, { 12, 26 }, 76, 256, nb_mantissa_38, 0, 1, 256, 320, 0, 0 },
-    { 38, { 12, 26 }, 114, 384, nb_mantissa_38, 0, 1, 256, 448, 0, 0 },
-    { 38, { 12, 26 }, 152, 512, nb_mantissa_38, 0, 1, 256, 576, 0, 0 },
-    { 38, { 12, 26 }, 190, 640, nb_mantissa_38, 0, 1, 256, 704, 3008, 0 },
-    { 38, { 12, 26 }, 228, 768, nb_mantissa_38, 0, 1, 256, 832, 2752, 0 },
-    { 38, { 12, 26 }, 266, 896, nb_mantissa_38, 0, 2, 192, 960, 2560, 64 },
-};
-
-static const DBEGroup grp_tab_6[7] = {
-    { 44, { 19, 25 }, 0, 0, nb_mantissa_44, 1, 1, 448, 0, 3264, 0 },
-    { 38, { 12, 26 }, 44, 256, nb_mantissa_38, 0, 1, 256, 320, 0, 0 },
-    { 38, { 12, 26 }, 82, 384, nb_mantissa_38, 0, 1, 256, 448, 0, 0 },
-    { 38, { 12, 26 }, 120, 512, nb_mantissa_38, 0, 1, 256, 576, 0, 0 },
-    { 38, { 12, 26 }, 158, 640, nb_mantissa_38, 0, 1, 256, 704, 3008, 0 },
-    { 38, { 12, 26 }, 196, 768, nb_mantissa_38, 0, 1, 256, 832, 2752, 0 },
-    { 38, { 12, 26 }, 234, 896, nb_mantissa_38, 0, 2, 192, 960, 2560, 64 },
-};
-
-static const DBEGroup grp_tab_7[1] = {
-    { 21, { 21 }, 0, 0, nb_mantissa_50, 2, 2, 1152, 0, 1408, 896 },
-};
-
-static const DBEGroup *const frm_ofs_tab[2][4] = {
-    { grp_tab_0, grp_tab_1, grp_tab_2, grp_tab_3 },
-    { grp_tab_4, grp_tab_5, grp_tab_6, grp_tab_7 }
-};
-
-static const uint8_t mantissa_size1[16][4] = {
-    {  0,  0,  0,  0 }, {  2,  1,  1,  1 }, {  3,  2,  1,  1 }, {  4,  3,  2,  1 },
-    {  5,  4,  3,  2 }, {  6,  5,  4,  3 }, {  7,  6,  5,  4 }, {  8,  7,  6,  5 },
-    {  9,  8,  7,  6 }, { 10,  9,  8,  7 }, { 11, 10,  9,  8 }, { 12, 11, 10,  9 },
-    { 13, 12, 11, 10 }, { 14, 13, 12, 11 }, { 15, 14, 13, 12 }, { 16, 15, 14, 13 },
-};
-
-static const uint8_t mantissa_size2[16][4] = {
-    {  0,  0,  0,  0 }, {  2,  1,  2,  2 }, {  3,  2,  3,  3 }, {  4,  3,  4,  4 },
-    {  5,  4,  5,  5 }, {  6,  5,  6,  6 }, {  7,  6,  7,  7 }, {  8,  7,  8,  8 },
-    {  9,  8,  9,  9 }, { 10,  9, 10, 10 }, { 11, 10, 11, 11 }, { 12, 11, 12, 12 },
-    { 13, 12, 13, 13 }, { 14, 13, 14, 14 }, { 15, 14, 15, 15 }, { 16, 15, 16, 16 },
-};
-
-static const float start_window[192] = {
-    0.00161569379826, 0.00185748233347, 0.00198562758548, 0.00207834078104,
-    0.00215717748523, 0.00223067096393, 0.00230299213147, 0.00237651215396,
-    0.00245275561606, 0.00253281402069, 0.00261754673613, 0.00270768786168,
-    0.00280390761895, 0.00290684998656, 0.00301715751161, 0.00313548872798,
-    0.00326253122934, 0.00339901215995, 0.00354570716636, 0.00370344845023,
-    0.00387313232586, 0.00405572653911, 0.00425227750970, 0.00446391759265,
-    0.00469187240551, 0.00493746822816, 0.00520213944619, 0.00548743597507,
-    0.00579503056737, 0.00612672586953, 0.00648446105606, 0.00687031782873,
-    0.00728652552677, 0.00773546505205, 0.00821967127415, 0.00874183354619,
-    0.00930479393832, 0.00991154278653, 0.01056521116692, 0.01126905994567,
-    0.01202646513050, 0.01284089936559, 0.01371590957417, 0.01465509096066,
-    0.01566205783408, 0.01674041199523, 0.01789370972358, 0.01912542867865,
-    0.02043893626265, 0.02183746113793, 0.02332406961796, 0.02490164852364,
-    0.02657289580178, 0.02834031974193, 0.03020624702903, 0.03217283918354,
-    0.03424211623810, 0.03641598586180, 0.03869627565015, 0.04108476601498,
-    0.04358322107390, 0.04619341515939, 0.04891715301882, 0.05175628239149,
-
-    0.05471237327267, 0.05778734733755, 0.06098291402413, 0.06430101352084,
-    0.06774345212186, 0.07131188644726, 0.07500780649199, 0.07883251748595,
-    0.08278712056651, 0.08687249228061, 0.09108926295730, 0.09543779401074,
-    0.09991815425851, 0.10453009536427, 0.10927302653894, 0.11414598865987,
-    0.11914762799220, 0.12427616972097, 0.12952939152560, 0.13490459744934,
-    0.14039859233595, 0.14600765712201, 0.15172752528722, 0.15755336077528,
-    0.16347973770491, 0.16950062219342, 0.17560935661442, 0.18179864660619,
-    0.18806055113821, 0.19438647593012, 0.20076717050010, 0.20719272909882,
-    0.21365259576030, 0.22013557367283, 0.22662983904194, 0.23312295958328,
-    0.23960191774666, 0.24605313873388, 0.25246252333253, 0.25881548554631,
-    0.26509699495987, 0.27129162373316, 0.27738359807707, 0.28335685401987,
-    0.28919509723179, 0.29488186663467, 0.30040060148455, 0.30573471157819,
-    0.31086765019993, 0.31578298939317, 0.32046449711227, 0.32489621578468,
-    0.32906254179156, 0.33294830535654, 0.33653885031840, 0.33982011325336,
-    0.34277870140679, 0.34540196889300, 0.34767809062480, 0.34959613344194,
-    0.35114612391958, 0.35231911235422, 0.35310723244504, 0.35350375621308,
-
-    0.35350314372945, 0.35310108725579, 0.35229454943591, 0.35108179521634,
-    0.34946241721522, 0.34743735430290, 0.34500890320420, 0.34218072298001,
-    0.33895783229541, 0.33534659943168, 0.33135472505060, 0.32699121776996,
-    0.32226636266000, 0.31719168282019, 0.31177989424432, 0.30604485422875,
-    0.30000150362379, 0.29366580327088, 0.28705466500775, 0.28018587766131,
-    0.27307802848095, 0.26575042049535, 0.25822298630189, 0.25051619882000,
-    0.24265097955783, 0.23464860495522, 0.22653061137548, 0.21831869932335,
-    0.21003463746705, 0.20170016703857, 0.19333690717811, 0.18496626177620,
-    0.17660932835062, 0.16828680947474, 0.16001892724986, 0.15182534128597,
-    0.14372507062477, 0.13573642000364, 0.12787691082233, 0.12016321713317,
-    0.11261110693234, 0.10523538898282, 0.09804986534955, 0.09106728977263,
-    0.08429933194438, 0.07775654768810, 0.07144835495683, 0.06538301547324,
-    0.05956762170687, 0.05400808871425, 0.04870915012107, 0.04367435714993,
-    0.03890607899172, 0.03440550179663, 0.03017262174627, 0.02620622428513,
-    0.02250383492507, 0.01906161305732, 0.01587412848221, 0.01293388032354,
-    0.01023019677288, 0.00774641320626, 0.00545109736891, 0.00325868651263,
-};
-
-static const float short_window2[192] = {
-    0.00018861094606, 0.00033433010202, 0.00050309624485, 0.00070306161748,
-    0.00093995174533, 0.00121913067128, 0.00154606505568, 0.00192647806126,
-    0.00236641248692, 0.00287225985240, 0.00345077377440, 0.00410907465023,
-    0.00485464855241, 0.00569534163219, 0.00663935063508, 0.00769520981249,
-    0.00887177436246, 0.01017820046395, 0.01162392194150, 0.01321862359335,
-    0.01497221122468, 0.01689477844427, 0.01899657030441, 0.02128794388846,
-    0.02377932597692, 0.02648116795039, 0.02940389811590, 0.03255787167130,
-    0.03595331854986, 0.03960028941437, 0.04350860009563, 0.04768777479454,
-    0.05214698838949, 0.05689500821121, 0.06194013566525, 0.06729014809766,
-    0.07295224131210, 0.07893297315602, 0.08523820859989, 0.09187306673620,
-    0.09884187012422, 0.10614809690222, 0.11379433608064, 0.12178224641797,
-    0.13011251926531, 0.13878484574660, 0.14779788861830, 0.15714925912610,
-    0.16683549914631, 0.17685206886673, 0.18719334022589, 0.19785259629099,
-    0.20882203671372, 0.22009278936030, 0.23165492816694, 0.24349749722585,
-    0.25560854105961, 0.26797514099368, 0.28058345748882, 0.29341877824732,
-    0.30646557185942, 0.31970754671026, 0.33312771482295, 0.34670846027024,
-
-    0.36043161174692, 0.37427851885723, 0.38823013163645, 0.40226708279486,
-    0.41636977214436, 0.43051845264462, 0.44469331748632, 0.45887458761470,
-    0.47304259908636, 0.48717788964798, 0.50126128392546, 0.51527397661778,
-    0.52919761310050, 0.54301436685998, 0.55670701320069, 0.57025899869448,
-    0.58365450587230, 0.59687851269542, 0.60991684638414, 0.62275623122793,
-    0.63538433005035, 0.64778977905593, 0.65996221584264, 0.67189230042379,
-    0.68357172916486, 0.69499324160511, 0.70615062019861, 0.71703868307548,
-    0.72765326998919, 0.73799122168099, 0.74805035295521, 0.75782941981995,
-    0.76732808110520, 0.77654685502339, 0.78548707118622, 0.79415081863423,
-    0.80254089047207, 0.81066072573188, 0.81851434910893, 0.82610630922734,
-    0.83344161609862, 0.84052567843230, 0.84736424144524, 0.85396332579459,
-    0.86032916822973, 0.86646816451999, 0.87238681516918, 0.87809167437532,
-    0.88358930263537, 0.88888622333073, 0.89398888356256, 0.89890361943564,
-    0.90363662591861, 0.90819393133744, 0.91258137648979, 0.91680459830070,
-    0.92086901787718, 0.92477983276087, 0.92854201312583, 0.93216030163834,
-    0.93563921662343, 0.93898305819384, 0.94219591693690, 0.94528168477979,
-
-    0.94823843319821, 0.95106834367330, 0.95377776558539, 0.95636718335775,
-    0.95883679961479, 0.96118650212341, 0.96341583179195, 0.96552395212906,
-    0.96750962060547, 0.96937116231768, 0.97110644638309, 0.97271286544154,
-    0.97418731862798, 0.97552619834964, 0.97672538116257, 0.97778022299974,
-    0.97868555895586, 0.97943570778357, 0.98002448120255, 0.98044519806866,
-    0.98069070339493, 0.98075339216123, 0.98062523779637, 0.98029782516478,
-    0.97976238784222, 0.97900984942031, 0.97803086854002, 0.97681588731895,
-    0.97535518280755, 0.97363892108474, 0.97165721358452, 0.96940017523145,
-    0.96685798395452, 0.96402094114589, 0.96087953263194, 0.95742448973047,
-    0.95364684997699, 0.94953801711660, 0.94508981997396, 0.94029456983253,
-    0.93514511597504, 0.92963489905951, 0.92375800202883, 0.91750919827624,
-    0.91088399681406, 0.90387868421832, 0.89649036314692, 0.88871698725397,
-    0.88055739234735, 0.87201132366062, 0.86307945913336, 0.85376342861693,
-    0.84406582894455, 0.83399023482637, 0.82354120554757, 0.81272428745995,
-    0.80154601230457, 0.79001389138101, 0.77813640562199, 0.76592299164227,
-    0.75338402384395, 0.74053079267526, 0.72737547915460, 0.71393112578527,
-};
-
-static const float short_window3[64] = {
-    0.00326887936450, 0.00550242900936, 0.00786846643791, 0.01045683453520,
-    0.01330402120132, 0.01643221072863, 0.01985798040609, 0.02359509464766,
-    0.02765559221954, 0.03205025893128, 0.03678884369614, 0.04188015679495,
-    0.04733210987781, 0.05315172583924, 0.05934513287609, 0.06591755045290,
-    0.07287327156378, 0.08021564389822, 0.08794705152307, 0.09606889811179,
-    0.10458159240070, 0.11348453632940, 0.12277611617809, 0.13245369691511,
-    0.14251361989876, 0.15295120402567, 0.16376075037904, 0.17493555039885,
-    0.18646789757072, 0.19834910260891, 0.21056951208995, 0.22311853047787,
-    0.23598464546683, 0.24915545655419, 0.26261770674500, 0.27635731727778,
-    0.29035942525136, 0.30460842402318, 0.31908800624032, 0.33378120935681,
-    0.34867046348260, 0.36373764140285, 0.37896411059909, 0.39433078709788,
-    0.40981819096657, 0.42540650327031, 0.44107562429959, 0.45680523287270,
-    0.47257484651351, 0.48836388230077, 0.50415171818214, 0.51991775454258,
-    0.53564147581496, 0.55130251191887, 0.56688069931047, 0.58235614142007,
-    0.59770926827271, 0.61292089506118, 0.62797227945823, 0.64284517745255,
-    0.65752189749349, 0.67198535273209, 0.68621911114984, 0.70020744337099,
-};
-
-static const uint8_t dc_code_tab[5] = { 0, 0, 0, 1, 1 };
-
-static const uint8_t ht_code_tab[5] = { 0, 0, 1, 2, 2 };
-
-static const uint8_t band_ofs_tab[3][4] = {
-    { 12, 8, 4, 0 }, { 14, 10, 6, 0 }, { 12, 8, 4, 0 }
-};
-
-static const uint8_t band_low_tab[3] = { 9, 17, 24 };
-
-static const uint16_t fast_gain_tab[8] = {
-    128, 256, 384, 512, 640, 768, 896, 1024
-};
-
-static const uint16_t slow_decay_tab[2][2] = { { 27, -1 }, { 32, 21 } };
-
-static const uint16_t misc_decay_tab[3][2][2] = {
-    { { 354, -1 }, { 425, 425 } },
-    { { 266, -1 }, { 320,  -1 } },
-    { { 213, -1 }, { 256,  -1 } }
-};
-
-static const uint16_t fast_decay_tab[3][2][2][50] = {
-    {{{
-        142, 142, 142, 142, 142, 142, 142, 142, 142, 142,
-        142, 142, 142, 142, 142, 142, 142, 142, 142, 142,
-        142, 142, 142, 142, 142, 142, 142, 142, 142, 142,
-        142, 142, 142, 142, 142, 142, 142, 142,
-    }, {
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-    }}, {{
-        170, 170, 170, 170, 170, 170, 170, 170, 170, 170,
-        170, 170, 170, 170, 170, 170, 170, 170, 170, 170,
-        170, 170, 170, 170, 170, 170, 170, 170, 170, 170,
-        170, 170, 170, 170, 170, 170, 170, 170,
-    }, {
-         64,  64,  64,  64,  64,  64,  64,  64,  64,  64,
-         64,  64,  64,  64,  64,  64,  64,  64,  64,  64,
-         64,  64,  64,  64,  64,  64,  64,  64,  64,  64,
-         64,  64,  64,  64,  64,  64,  64,  64,
-    }}}, {{{
-        266, 266, 106, 106, 106, 106, 106, 106, 106, 106,
-        106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
-        106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
-        106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
-        106, 106, 106, 106,
-    }, {
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,
-    }}, {{
-        319, 319, 128, 128, 128, 128, 128, 128, 128, 128,
-        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
-        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
-        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
-        128, 128, 128, 128,
-    }, {
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,
-    }}}, {{{
-        106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
-        106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
-        106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
-        106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
-        106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
-    }, {
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-    }}, {{
-        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
-        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
-        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
-        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
-        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
-    }, {
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-    }}}
-};
-
-static const uint16_t fast_gain_adj_tab[3][2][62] = {
-    {{
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   1,   2,   4,   7,  11,  16,  29,  44,  59,
-         76,  94, 116, 142, 179, 221, 252, 285, 312, 334,
-    }, {
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          2,   5,   8,  10,  15,  28,  42,  57,  75,  93,
-        115, 140, 177, 219, 247, 280, 308, 330, 427, 533,
-    }}, {{
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   2,   5,   8,  12,  21,  35,  51,  69,  89,
-        111, 138, 176, 220, 251, 284, 312, 334,
-    }, {
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   2,
-          5,   8,  11,  18,  33,  49,  65,  84, 106, 132,
-        168, 214, 245, 279, 308, 329, 427, 533,
-    }}, {{
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   0,   0,   0,   0,   1,   4,   7,  10,  17,
-         31,  47,  65,  84, 107, 134, 171, 215, 250, 283,
-        312, 334,
-    }, {
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   0,   0,   0,   3,   6,   9,  13,  27,  43,
-         60,  79, 100, 126, 160, 207, 242, 276, 307, 329,
-        427, 533,
-    }}
-};
-
-static const uint16_t slow_gain_tab[3][2][50] = {
-    {{
-        3072, 3072, 3072, 3072, 3072, 3072, 1063, 1063, 1063, 1063,
-        1063, 1063, 1063, 1063, 1063, 1063, 1063, 1063, 1063, 1063,
-        1063, 1063, 1063, 1063, 1063, 1063, 1063, 1063, 1063, 1063,
-        1063, 1063, 1063, 1063, 1063, 1063, 1063, 1063,
-    }, {
-        3072, 3072, 3072, 3072, 3072, 3072,  850,  850,  850,  850,
-         850,  850,  850,  850,  850,  850,  850,  850,  850,  850,
-         850,  850,  850,  850,  850,  850,  850,  850,  850,  850,
-         850,  850,  850,  850,  850,  850,  850,  850,
-    }}, {{
-        3072, 1212, 1212, 1212,  999,  999,  999,  999,  999,  999,
-         999,  999,  999,  999,  999,  999,  999,  999,  999,  999,
-         999,  999,  999,  999,  999,  999,  999,  999,  999,  999,
-         999,  999,  999,  999,  999,  999,  999,  999,  999,  999,
-         999,  999,  999,  999,
-    }, {
-          -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
-          -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
-          -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
-          -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
-          -1,   -1,   -1,   -1,
-    }}, {{
-        3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072,
-         999,  999,  999,  999,  999,  999,  999,  999,  999,  999,
-         999,  999,  999,  999,  999,  999,  999,  999,  999,  999,
-         999,  999,  999,  999,  999,  999,  999,  999,  999,  999,
-         999,  999,  999,  999,  999,  999,  999,  999,  999,  999,
-    }, {
-          -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
-          -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
-          -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
-          -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
-          -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
-    }}
-};
-
-static const uint16_t hearing_thresh_tab[3][3][50] = {
-    {{
-        1403, 1141, 1000,  959,  948,  957,  946,  925,  899,  871,
-         843,  815,  789,  766,  745,  727,  705,  687,  681,  686,
-         701,  725,  768,  854,  940, 1018, 1075, 1103, 1111, 1106,
-        1098, 1105, 1142, 1237, 1419, 1721, 2169, 2805,
-    }, {
-        1401, 1130,  995,  957,  947,  955,  941,  918,  890,  861,
-         831,  803,  777,  754,  734,  717,  698,  684,  682,  692,
-         712,  743,  798,  894,  976, 1045, 1091, 1109, 1110, 1102,
-        1098, 1116, 1174, 1300, 1526, 1884, 2401, 3072,
-    }, {
-        1393, 1086,  974,  949,  957,  941,  913,  878,  843,  808,
-         777,  750,  727,  708,  695,  686,  681,  689,  714,  752,
-         811,  888,  971, 1044, 1087, 1108, 1110, 1102, 1098, 1115,
-        1172, 1290, 1489, 1812, 2293, 2964, 3072, 3072,
-    }}, {{
-        1412, 1343, 1141, 1047, 1000,  974,  959,  951,  948,  947,
-         957,  953,  946,  936,  925,  906,  878,  850,  822,  795,
-         771,  745,  719,  700,  687,  681,  685,  701,  733,  784,
-         885,  977, 1047, 1092, 1110, 1108, 1099, 1102, 1138, 1233,
-        1413, 1711, 2157, 2797,
-    }, {
-        1412, 1336, 1130, 1040,  995,  970,  957,  950,  947,  947,
-         955,  950,  941,  930,  918,  897,  868,  838,  810,  783,
-         759,  734,  710,  693,  684,  681,  690,  712,  752,  823,
-         924, 1009, 1069, 1102, 1111, 1104, 1098, 1111, 1168, 1295,
-        1518, 1873, 2388, 3072,
-    }, {
-        1411, 1293, 1086, 1009,  974,  957,  949,  947,  957,  951,
-         941,  928,  913,  896,  878,  852,  817,  785,  756,  732,
-         713,  695,  683,  682,  689,  710,  746,  811,  906,  992,
-        1061, 1099, 1111, 1106, 1098, 1107, 1155, 1266, 1471, 1799,
-        2277, 2945, 3072, 3072,
-    }}, {{
-        1431, 1412, 1403, 1379, 1343, 1293, 1229, 1180, 1125, 1075,
-        1040, 1014,  996,  979,  965,  957,  951,  948,  947,  957,
-         951,  940,  924,  903,  877,  846,  815,  785,  753,  725,
-         702,  686,  681,  689,  714,  760,  847,  947, 1028, 1083,
-        1108, 1109, 1101, 1100, 1132, 1222, 1402, 1705, 2160, 2803,
-    }, {
-        1431, 1412, 1401, 1375, 1336, 1278, 1215, 1168, 1115, 1066,
-        1032, 1008,  991,  975,  962,  954,  950,  947,  947,  955,
-         948,  935,  916,  894,  866,  835,  803,  772,  742,  715,
-         695,  683,  683,  697,  729,  784,  887,  982, 1054, 1096,
-        1111, 1106, 1098, 1107, 1159, 1281, 1505, 1865, 2391, 3072,
-    }, {
-        1427, 1411, 1393, 1353, 1293, 1215, 1160, 1118, 1072, 1031,
-        1003,  984,  971,  960,  952,  948,  947,  957,  952,  941,
-         924,  902,  876,  847,  815,  781,  750,  723,  700,  685,
-         681,  691,  719,  766,  858,  958, 1039, 1089, 1109, 1108,
-        1099, 1102, 1141, 1245, 1442, 1766, 2250, 2930, 3072, 3072,
-    }}
-};
-
-static const int16_t lwc_gain_tab[11][7] = {
-    {   -21,  -197,  -271,  -466, 32767, 32767, 32767 },
-    {  -197,   -29,  -244,  -271,  -540, 32767, 32767 },
-    {  -271,  -244,   -29,  -249,  -271,  -593, 32767 },
-    {  -466,  -271,  -249,   -29,  -251,  -271,  -632 },
-    {  -540,  -271,  -251,   -29,  -251,  -271,  -664 },
-    {  -593,  -271,  -251,   -29,  -252,  -271,  -690 },
-    {  -632,  -271,  -252,   -29,  -252,  -271,  -711 },
-    {  -664,  -271,  -252,   -29,  -252,  -271,  -730 },
-    {  -690,  -271,  -252,   -29,  -252,  -271,  -745 },
-    {  -711,  -271,  -252,   -29,  -253,  -271,  -759 },
-    {  -730,  -271,  -253,   -29,  -253,  -271,  -771 },
-};
-
-static const int16_t lwc_adj_tab[7] = {
-    -192, -320, -448, -512, -448, -320, -192,
-};
-
-static const uint8_t log_add_tab[212] = {
-    64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 52, 51, 50,
-    49, 48, 47, 47, 46, 45, 44, 44, 43, 42, 41, 41, 40, 39, 38, 38,
-    37, 36, 36, 35, 35, 34, 33, 33, 32, 32, 31, 30, 30, 29, 29, 28,
-    28, 27, 27, 26, 26, 25, 25, 24, 24, 23, 23, 22, 22, 21, 21, 21,
-    20, 20, 19, 19, 19, 18, 18, 18, 17, 17, 17, 16, 16, 16, 15, 15,
-    15, 14, 14, 14, 13, 13, 13, 13, 12, 12, 12, 12, 11, 11, 11, 11,
-    10, 10, 10, 10, 10,  9,  9,  9,  9,  9,  8,  8,  8,  8,  8,  8,
-     7,  7,  7,  7,  7,  7,  6,  6,  6,  6,  6,  6,  6,  6,  5,  5,
-     5,  5,  5,  5,  5,  5,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,
-     4,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  2,
-     2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
-     2,  2,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
-     1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
-     1,  1,  0,  0,
-};
-
-static const uint8_t bap_tab[64] = {
-     0,  1,  1,  1,  1,  1,  2,  2,  2,  2,  2,  3,  3,  3,  3,  4,
-     4,  4,  4,  5,  5,  5,  5,  6,  6,  6,  6,  7,  7,  7,  7,  8,
-     8,  8,  8,  9,  9,  9,  9, 10, 10, 10, 10, 11, 11, 11, 11, 12,
-    12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 15,
-};
-
 static float mantissa_tab1[17][4];
 static float mantissa_tab2[17][4];
 static float mantissa_tab3[17][4];
 static float exponent_tab[50];
 static float gain_tab[1024];
 
-DECLARE_ALIGNED(32, static float, window)[3712];
 
 static int skip_input(DBEContext *s, int nb_words)
 {
@@ -679,94 +155,6 @@ static int parse_exponents(DBEContext *s, DBEChannel *c)
     return 0;
 }
 
-static inline int log_add(int a, int b)
-{
-    int c = FFABS(a - b) >> 1;
-    return FFMAX(a, b) + log_add_tab[FFMIN(c, 211)];
-}
-
-static void calc_lowcomp(int *msk_val)
-{
-    int lwc_val[17] = { 0 };
-    int i, j, k;
-
-    for (i = 0; i < 11; i++) {
-        int max_j = 0;
-        int max_v = INT_MIN;
-        int thr   = 0;
-
-        for (j = FFMAX(i - 3, 0), k = 0; j <= i + 3; j++, k++) {
-            int v = msk_val[j] + lwc_gain_tab[i][k];
-            if (v > max_v) {
-                max_j = j;
-                max_v = v;
-            }
-            thr = log_add(thr, v);
-        }
-
-        if (msk_val[i] < thr) {
-            for (j = FFMAX(max_j - 3, 0),
-                 k = FFMAX(3 - max_j, 0);
-                 j <= max_j + 3; j++, k++)
-                lwc_val[j] += lwc_adj_tab[k];
-        }
-    }
-
-    for (i = 0; i < 16; i++) {
-        int v = FFMAX(lwc_val[i], -512);
-        msk_val[i] = FFMAX(msk_val[i] + v, 0);
-    }
-}
-
-static void bit_allocate(int nb_exponent, int nb_code, int fr_code,
-                         int *exp, int *bap,
-                         int fg_spc, int fg_ofs, int msk_mod, int snr_ofs)
-{
-    int msk_val[MAX_BIAS_EXP];
-    int psd_val[MAX_BIAS_EXP];
-    int fast_leak  = 0;
-    int slow_leak  = 0;
-    int dc_code    = dc_code_tab[fr_code - 1];
-    int ht_code    = ht_code_tab[fr_code - 1];
-    int fast_gain  = fast_gain_tab[fg_ofs];
-    int slow_decay = slow_decay_tab[dc_code][msk_mod];
-    int misc_decay = misc_decay_tab[nb_code][dc_code][msk_mod];
-    const uint16_t *slow_gain      = slow_gain_tab[nb_code][msk_mod];
-    const uint16_t *fast_decay     = fast_decay_tab[nb_code][dc_code][msk_mod];
-    const uint16_t *fast_gain_adj  = fast_gain_adj_tab[nb_code][dc_code];
-    const uint16_t *hearing_thresh = hearing_thresh_tab[nb_code][ht_code];
-    int i;
-
-    for (i = 0; i < nb_exponent; i++)
-        psd_val[i] = (48 - exp[i]) * 64;
-
-    fast_gain_adj += band_ofs_tab[nb_code][fg_spc];
-    for (i = 0; i < nb_exponent; i++) {
-        fast_leak = log_add(fast_leak  - fast_decay[i],
-                            psd_val[i] - fast_gain + fast_gain_adj[i]);
-        slow_leak = log_add(slow_leak  - slow_decay,
-                            psd_val[i] - slow_gain[i]);
-        msk_val[i] = FFMAX(fast_leak, slow_leak);
-    }
-
-    fast_leak = 0;
-    for (i = nb_exponent - 1; i > band_low_tab[nb_code]; i--) {
-        fast_leak = log_add(fast_leak - misc_decay, psd_val[i] - fast_gain);
-        msk_val[i] = FFMAX(msk_val[i], fast_leak);
-    }
-
-    for (i = 0; i < nb_exponent; i++)
-        msk_val[i] = FFMAX(msk_val[i], hearing_thresh[i]);
-
-    if (!nb_code)
-        calc_lowcomp(msk_val);
-
-    for (i = 0; i < nb_exponent; i++) {
-        int v = 16 * (snr_ofs - 64) + psd_val[i] - msk_val[i] >> 5;
-        bap[i] = bap_tab[av_clip_uintp2(v, 6)];
-    }
-}
-
 static int parse_bit_alloc(DBEDecodeContext *s1, DBEChannel *c)
 {
     DBEContext *s = &s1->dectx;
@@ -801,7 +189,7 @@ static int parse_bit_alloc(DBEDecodeContext *s1, DBEChannel *c)
 
     for (i = 0, p = NULL, g = c->groups; i < c->nb_groups; i++, p = g, g++) {
         if (c->exp_strategy[i] || bap_strategy[i]) {
-            bit_allocate(g->nb_exponent, g->imdct_idx, s->metadata.fr_code,
+            ff_dolby_e_bit_allocate(g->nb_exponent, g->imdct_idx, s->metadata.fr_code,
                          c->exponents + g->exp_ofs, c->bap + g->exp_ofs,
                          fg_spc[i], fg_ofs[i], msk_mod[i], snr_ofs);
         } else {
@@ -856,7 +244,7 @@ static int parse_mantissas(DBEContext *s, DBEChannel *c)
         for (j = 0; j < g->nb_exponent; j++) {
             int bap     = c->bap[g->exp_ofs + j];
             int idx     = c->idx[g->exp_ofs + j];
-            int size1   = mantissa_size1[bap][idx];
+            int size1   = ff_dolby_e_mantissa_size1[bap][idx];
             int count   = g->nb_mantissa[j];
             float exp   = exponent_tab[c->exponents[g->exp_ofs + j]];
             float scale = mantissa_tab1[size1][idx] * exp;
@@ -874,7 +262,7 @@ static int parse_mantissas(DBEContext *s, DBEChannel *c)
                     if (values[k] != escape) {
                         mnt[k] = values[k] * scale;
                     } else {
-                        int size2 = mantissa_size2[bap][idx];
+                        int size2 = ff_dolby_e_mantissa_size2[bap][idx];
                         int value = get_sbits(&s->gb, size2);
                         float a = mantissa_tab2[size2][idx];
                         float b = mantissa_tab3[size2][idx];
@@ -912,7 +300,7 @@ static int parse_channel(DBEDecodeContext *s1, int ch, int seg_id)
         return AVERROR_PATCHWELCOME;
     }
 
-    if (ch == lfe_channel_tab[s->metadata.prog_conf]) {
+    if (ch == ff_dolby_e_lfe_channel_tab[s->metadata.prog_conf]) {
         c->gr_code = 3;
         c->bw_code = 29;
     } else {
@@ -924,11 +312,11 @@ static int parse_channel(DBEDecodeContext *s1, int ch, int seg_id)
         }
     }
 
-    c->nb_groups   = nb_groups_tab[c->gr_code];
-    c->nb_mstr_exp = nb_mstr_exp_tab[c->gr_code];
+    c->nb_groups   = ff_dolby_e_nb_groups_tab[c->gr_code];
+    c->nb_mstr_exp = ff_dolby_e_nb_mstr_exp_tab[c->gr_code];
 
     for (i = 0; i < c->nb_groups; i++) {
-        c->groups[i] = frm_ofs_tab[seg_id][c->gr_code][i];
+        c->groups[i] = ff_dolby_e_frm_ofs_tab[seg_id][c->gr_code][i];
         if (c->nb_mstr_exp == 2) {
             c->groups[i].nb_exponent    -= c->bw_code;
             c->groups[i].nb_bias_exp[1] -= c->bw_code;
@@ -988,32 +376,6 @@ static int parse_meter(DBEDecodeContext *s1)
     return 0;
 }
 
-static void imdct_calc(DBEDecodeContext *s1, DBEGroup *g, float *result, float *values)
-{
-    AVTXContext *imdct = s1->imdct[g->imdct_phs == 1][g->imdct_idx];
-    av_tx_fn  imdct_fn = s1->imdct_fn[g->imdct_phs == 1][g->imdct_idx];
-    int n   = 1 << imdct_bits_tab[g->imdct_idx];
-    int n2  = n >> 1;
-
-    switch (g->imdct_phs) {
-    case 0:
-        imdct_fn(imdct, result, values, sizeof(float));
-        for (int i = 0; i < n2; i++)
-            result[n2 + i] = result[n2 - i - 1];
-        break;
-    case 1:
-        imdct_fn(imdct, result, values, sizeof(float));
-        break;
-    case 2:
-        imdct_fn(imdct, result + n2, values, sizeof(float));
-        for (int i = 0; i < n2; i++)
-            result[i] = -result[n - i - 1];
-        break;
-    default:
-        av_assert0(0);
-    }
-}
-
 static void transform(DBEDecodeContext *s1, DBEChannel *c, float *history, float *output)
 {
     LOCAL_ALIGNED_32(float, buffer, [2048]);
@@ -1025,9 +387,9 @@ static void transform(DBEDecodeContext *s1, DBEChannel *c, float *history, float
     for (i = 0, g = c->groups; i < c->nb_groups; i++, g++) {
         float *src = buffer + g->src_ofs;
         float *dst = result + g->dst_ofs;
-        float *win = window + g->win_ofs;
+        float *win = ff_dolby_e_window + g->win_ofs;
 
-        imdct_calc(s1, g, buffer, c->mantissas + g->mnt_ofs);
+        ff_dolby_e_imdct_calc(&s1->transform, g, buffer, c->mantissas + g->mnt_ofs);
         s1->fdsp->vector_fmul_add(dst, src, win, dst, g->win_len);
     }
 
@@ -1154,10 +516,7 @@ static av_cold int dolby_e_close(AVCodecContext *avctx)
 {
     DBEDecodeContext *s = avctx->priv_data;
 
-    for (int i = 0; i < 3; i++) {
-        av_tx_uninit(&s->imdct[0][i]);
-        av_tx_uninit(&s->imdct[1][i]);
-    }
+    ff_dolby_e_transform_uninit(&s->transform);
 
     av_freep(&s->fdsp);
     return 0;
@@ -1198,76 +557,19 @@ static av_cold void init_tables(void)
 
     for (i = 1; i < 1024; i++)
         gain_tab[i] = exp2f((i - 960) / 64.0f);
-
-    // short 1
-    ff_kbd_window_init(window, 3.0f, 128);
-    for (i = 0; i < 128; i++)
-        window[128 + i] = window[127 - i];
-
-    // start
-    for (i = 0; i < 192; i++)
-        window[256 + i] = start_window[i];
-
-    // short 2
-    for (i = 0; i < 192; i++)
-        window[448 + i] = short_window2[i];
-    for (i = 0; i < 64; i++)
-        window[640 + i] = window[63 - i];
-
-    // short 3
-    for (i = 0; i < 64; i++)
-        window[704 + i] = short_window3[i];
-    for (i = 0; i < 192; i++)
-        window[768 + i] = window[64 + i];
-
-    // bridge
-    for (i = 0; i < 128; i++)
-        window[960 + i] = window[i];
-    for (i = 0; i < 64; i++)
-        window[1088 + i] = 1.0f;
-
-    // long
-    ff_kbd_window_init(window + 1408, 3.0f, 256);
-    for (i = 0; i < 640; i++)
-        window[1664 + i] = 1.0f;
-    for (i = 0; i < 256; i++)
-        window[2304 + i] = window[1152 + i] = window[1663 - i];
-
-    // reverse start
-    for (i = 0; i < 192; i++)
-        window[2560 + i] = window[447 - i];
-
-    // reverse short 2
-    for (i = 0; i < 256; i++)
-        window[2752 + i] = window[703 - i];
-
-    // reverse short 3
-    for (i = 0; i < 256; i++)
-        window[3008 + i] = window[959 - i];
-
-    // reverse bridge
-    for (i = 0; i < 448; i++)
-        window[3264 + i] = window[1407 - i];
 }
 
 static av_cold int dolby_e_init(AVCodecContext *avctx)
 {
     static AVOnce init_once = AV_ONCE_INIT;
     DBEDecodeContext *s = avctx->priv_data;
-    float scale = 2.0f;
     int ret;
 
     if (ff_thread_once(&init_once, init_tables))
         return AVERROR_UNKNOWN;
 
-    for (int i = 0; i < 3; i++) {
-        if ((ret = av_tx_init(&s->imdct[0][i], &s->imdct_fn[0][i], AV_TX_FLOAT_MDCT,
-                              1, 1 << imdct_bits_tab[i] - 1, &scale, 0)) < 0)
-            return ret;
-        if ((ret = av_tx_init(&s->imdct[1][i], &s->imdct_fn[1][i], AV_TX_FLOAT_MDCT,
-                              1, 1 << imdct_bits_tab[i] - 1, &scale, AV_TX_FULL_IMDCT)) < 0)
-            return ret;
-    }
+    if ((ret = ff_dolby_e_transform_init(&s->transform)) < 0)
+        return ret;
 
     if (!(s->fdsp = avpriv_float_dsp_alloc(0)))
         return AVERROR(ENOMEM);
diff --git a/libavcodec/dolby_e_bitalloc.c b/libavcodec/dolby_e_bitalloc.c
new file mode 100644
index 0000000000..94939828db
--- /dev/null
+++ b/libavcodec/dolby_e_bitalloc.c
@@ -0,0 +1,492 @@
+/*
+ * Copyright (C) 2017 foo86
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "libavutil/common.h"
+#include "dolby_e_bitalloc.h"
+
+#define MAX_BIAS_EXP_BA 50
+
+const int8_t ff_dolby_e_lfe_channel_tab[MAX_PROG_CONF + 1] = {
+     5,  5, -1, -1, -1, -1, -1, -1, -1, -1, -1, 4,
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  5, 5
+};
+
+const uint8_t ff_dolby_e_nb_groups_tab[4] = { 1, 8, 7, 1 };
+
+const uint8_t ff_dolby_e_nb_mstr_exp_tab[4] = { 2, 2, 2, 1 };
+
+static const uint8_t nb_mantissa_38[38] = {
+     1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+     2,  2,  2,  2,  2,  2,  3,  3,  3,  4,  4,  4,  5,  5,  6,  6,
+     7,  8,  9, 10, 11, 12,
+};
+
+static const uint8_t nb_mantissa_44[44] = {
+     1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  2,
+     2,  2,  2,  2,  2,  3,  3,  3,  3,  4,  4,  5,  5,  6,  7,  7,
+     8,  9, 10, 11, 12, 13, 15, 16, 18, 20, 22, 25,
+};
+
+static const uint8_t nb_mantissa_50[50] = {
+     1,  1,  1,  1,  1,  1,  1,  1,  2,  2,  2,  2,  2,  3,  3,  3,
+     3,  4,  4,  5,  5,  6,  6,  7,  8,  9,  9, 10, 12, 13, 14, 16,
+    18, 19, 22, 24, 27, 29, 32, 36, 40, 44, 49, 54, 60, 66, 74, 82,
+    90, 100,
+};
+
+static const DBEGroup grp_tab_0[1] = {
+    { 50, { 27, 23 }, 0, 0, nb_mantissa_50, 2, 0, 1152, 0, 1408, 0 },
+};
+
+static const DBEGroup grp_tab_1[8] = {
+    { 38, { 12, 26 }, 0, 0, nb_mantissa_38, 0, 0, 192, 0, 256, 0 },
+    { 38, { 12, 26 }, 38, 128, nb_mantissa_38, 0, 1, 256, 64, 448, 0 },
+    { 38, { 12, 26 }, 76, 256, nb_mantissa_38, 0, 1, 256, 192, 704, 0 },
+    { 38, { 12, 26 }, 114, 384, nb_mantissa_38, 0, 1, 256, 320, 0, 0 },
+    { 38, { 12, 26 }, 152, 512, nb_mantissa_38, 0, 1, 256, 448, 0, 0 },
+    { 38, { 12, 26 }, 190, 640, nb_mantissa_38, 0, 1, 256, 576, 0, 0 },
+    { 38, { 12, 26 }, 228, 768, nb_mantissa_38, 0, 1, 256, 704, 0, 0 },
+    { 38, { 12, 26 }, 266, 896, nb_mantissa_38, 0, 1, 256, 832, 0, 0 },
+};
+
+static const DBEGroup grp_tab_2[7] = {
+    { 38, { 12, 26 }, 0, 0, nb_mantissa_38, 0, 0, 192, 0, 256, 0 },
+    { 38, { 12, 26 }, 38, 128, nb_mantissa_38, 0, 1, 256, 64, 448, 0 },
+    { 38, { 12, 26 }, 76, 256, nb_mantissa_38, 0, 1, 256, 192, 704, 0 },
+    { 38, { 12, 26 }, 114, 384, nb_mantissa_38, 0, 1, 256, 320, 0, 0 },
+    { 38, { 12, 26 }, 152, 512, nb_mantissa_38, 0, 1, 256, 448, 0, 0 },
+    { 38, { 12, 26 }, 190, 640, nb_mantissa_38, 0, 1, 256, 576, 0, 0 },
+    { 44, { 19, 25 }, 228, 768, nb_mantissa_44, 1, 1, 448, 704, 960, 64 },
+};
+
+static const DBEGroup grp_tab_3[1] = {
+    { 21, { 21 }, 0, 0, nb_mantissa_50, 2, 0, 1152, 0, 1408, 0 },
+};
+
+static const DBEGroup grp_tab_4[1] = {
+    { 50, { 27, 23 }, 0, 0, nb_mantissa_50, 2, 2, 1152, 0, 1408, 896 },
+};
+
+static const DBEGroup grp_tab_5[8] = {
+    { 38, { 12, 26 }, 0, 0, nb_mantissa_38, 0, 1, 256, 64, 0, 0 },
+    { 38, { 12, 26 }, 38, 128, nb_mantissa_38, 0, 1, 256, 192, 0, 0 },
+    { 38, { 12, 26 }, 76, 256, nb_mantissa_38, 0, 1, 256, 320, 0, 0 },
+    { 38, { 12, 26 }, 114, 384, nb_mantissa_38, 0, 1, 256, 448, 0, 0 },
+    { 38, { 12, 26 }, 152, 512, nb_mantissa_38, 0, 1, 256, 576, 0, 0 },
+    { 38, { 12, 26 }, 190, 640, nb_mantissa_38, 0, 1, 256, 704, 3008, 0 },
+    { 38, { 12, 26 }, 228, 768, nb_mantissa_38, 0, 1, 256, 832, 2752, 0 },
+    { 38, { 12, 26 }, 266, 896, nb_mantissa_38, 0, 2, 192, 960, 2560, 64 },
+};
+
+static const DBEGroup grp_tab_6[7] = {
+    { 44, { 19, 25 }, 0, 0, nb_mantissa_44, 1, 1, 448, 0, 3264, 0 },
+    { 38, { 12, 26 }, 44, 256, nb_mantissa_38, 0, 1, 256, 320, 0, 0 },
+    { 38, { 12, 26 }, 82, 384, nb_mantissa_38, 0, 1, 256, 448, 0, 0 },
+    { 38, { 12, 26 }, 120, 512, nb_mantissa_38, 0, 1, 256, 576, 0, 0 },
+    { 38, { 12, 26 }, 158, 640, nb_mantissa_38, 0, 1, 256, 704, 3008, 0 },
+    { 38, { 12, 26 }, 196, 768, nb_mantissa_38, 0, 1, 256, 832, 2752, 0 },
+    { 38, { 12, 26 }, 234, 896, nb_mantissa_38, 0, 2, 192, 960, 2560, 64 },
+};
+
+static const DBEGroup grp_tab_7[1] = {
+    { 21, { 21 }, 0, 0, nb_mantissa_50, 2, 2, 1152, 0, 1408, 896 },
+};
+
+const DBEGroup *const ff_dolby_e_frm_ofs_tab[2][4] = {
+    { grp_tab_0, grp_tab_1, grp_tab_2, grp_tab_3 },
+    { grp_tab_4, grp_tab_5, grp_tab_6, grp_tab_7 }
+};
+
+const uint8_t ff_dolby_e_mantissa_size1[16][4] = {
+    {  0,  0,  0,  0 }, {  2,  1,  1,  1 }, {  3,  2,  1,  1 }, {  4,  3,  2,  1 },
+    {  5,  4,  3,  2 }, {  6,  5,  4,  3 }, {  7,  6,  5,  4 }, {  8,  7,  6,  5 },
+    {  9,  8,  7,  6 }, { 10,  9,  8,  7 }, { 11, 10,  9,  8 }, { 12, 11, 10,  9 },
+    { 13, 12, 11, 10 }, { 14, 13, 12, 11 }, { 15, 14, 13, 12 }, { 16, 15, 14, 13 },
+};
+
+const uint8_t ff_dolby_e_mantissa_size2[16][4] = {
+    {  0,  0,  0,  0 }, {  2,  1,  2,  2 }, {  3,  2,  3,  3 }, {  4,  3,  4,  4 },
+    {  5,  4,  5,  5 }, {  6,  5,  6,  6 }, {  7,  6,  7,  7 }, {  8,  7,  8,  8 },
+    {  9,  8,  9,  9 }, { 10,  9, 10, 10 }, { 11, 10, 11, 11 }, { 12, 11, 12, 12 },
+    { 13, 12, 13, 13 }, { 14, 13, 14, 14 }, { 15, 14, 15, 15 }, { 16, 15, 16, 16 },
+};
+
+static const uint8_t dc_code_tab[5] = { 0, 0, 0, 1, 1 };
+
+static const uint8_t ht_code_tab[5] = { 0, 0, 1, 2, 2 };
+
+static const uint8_t band_ofs_tab[3][4] = {
+    { 12, 8, 4, 0 }, { 14, 10, 6, 0 }, { 12, 8, 4, 0 }
+};
+
+static const uint8_t band_low_tab[3] = { 9, 17, 24 };
+
+static const uint16_t fast_gain_tab[8] = {
+    128, 256, 384, 512, 640, 768, 896, 1024
+};
+
+static const uint16_t slow_decay_tab[2][2] = { { 27, -1 }, { 32, 21 } };
+
+static const uint16_t misc_decay_tab[3][2][2] = {
+    { { 354, -1 }, { 425, 425 } },
+    { { 266, -1 }, { 320,  -1 } },
+    { { 213, -1 }, { 256,  -1 } }
+};
+
+static const uint16_t fast_decay_tab[3][2][2][50] = {
+    {{{
+        142, 142, 142, 142, 142, 142, 142, 142, 142, 142,
+        142, 142, 142, 142, 142, 142, 142, 142, 142, 142,
+        142, 142, 142, 142, 142, 142, 142, 142, 142, 142,
+        142, 142, 142, 142, 142, 142, 142, 142,
+    }, {
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+    }}, {{
+        170, 170, 170, 170, 170, 170, 170, 170, 170, 170,
+        170, 170, 170, 170, 170, 170, 170, 170, 170, 170,
+        170, 170, 170, 170, 170, 170, 170, 170, 170, 170,
+        170, 170, 170, 170, 170, 170, 170, 170,
+    }, {
+         64,  64,  64,  64,  64,  64,  64,  64,  64,  64,
+         64,  64,  64,  64,  64,  64,  64,  64,  64,  64,
+         64,  64,  64,  64,  64,  64,  64,  64,  64,  64,
+         64,  64,  64,  64,  64,  64,  64,  64,
+    }}}, {{{
+        266, 266, 106, 106, 106, 106, 106, 106, 106, 106,
+        106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+        106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+        106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+        106, 106, 106, 106,
+    }, {
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,
+    }}, {{
+        319, 319, 128, 128, 128, 128, 128, 128, 128, 128,
+        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
+        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
+        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
+        128, 128, 128, 128,
+    }, {
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,
+    }}}, {{{
+        106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+        106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+        106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+        106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+        106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+    }, {
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+    }}, {{
+        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
+        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
+        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
+        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
+        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
+    }, {
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+    }}}
+};
+
+static const uint16_t fast_gain_adj_tab[3][2][62] = {
+    {{
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   1,   2,   4,   7,  11,  16,  29,  44,  59,
+         76,  94, 116, 142, 179, 221, 252, 285, 312, 334,
+    }, {
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          2,   5,   8,  10,  15,  28,  42,  57,  75,  93,
+        115, 140, 177, 219, 247, 280, 308, 330, 427, 533,
+    }}, {{
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   2,   5,   8,  12,  21,  35,  51,  69,  89,
+        111, 138, 176, 220, 251, 284, 312, 334,
+    }, {
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   2,
+          5,   8,  11,  18,  33,  49,  65,  84, 106, 132,
+        168, 214, 245, 279, 308, 329, 427, 533,
+    }}, {{
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   0,   0,   0,   0,   1,   4,   7,  10,  17,
+         31,  47,  65,  84, 107, 134, 171, 215, 250, 283,
+        312, 334,
+    }, {
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   0,   0,   0,   3,   6,   9,  13,  27,  43,
+         60,  79, 100, 126, 160, 207, 242, 276, 307, 329,
+        427, 533,
+    }}
+};
+
+static const uint16_t slow_gain_tab[3][2][50] = {
+    {{
+        3072, 3072, 3072, 3072, 3072, 3072, 1063, 1063, 1063, 1063,
+        1063, 1063, 1063, 1063, 1063, 1063, 1063, 1063, 1063, 1063,
+        1063, 1063, 1063, 1063, 1063, 1063, 1063, 1063, 1063, 1063,
+        1063, 1063, 1063, 1063, 1063, 1063, 1063, 1063,
+    }, {
+        3072, 3072, 3072, 3072, 3072, 3072,  850,  850,  850,  850,
+         850,  850,  850,  850,  850,  850,  850,  850,  850,  850,
+         850,  850,  850,  850,  850,  850,  850,  850,  850,  850,
+         850,  850,  850,  850,  850,  850,  850,  850,
+    }}, {{
+        3072, 1212, 1212, 1212,  999,  999,  999,  999,  999,  999,
+         999,  999,  999,  999,  999,  999,  999,  999,  999,  999,
+         999,  999,  999,  999,  999,  999,  999,  999,  999,  999,
+         999,  999,  999,  999,  999,  999,  999,  999,  999,  999,
+         999,  999,  999,  999,
+    }, {
+          -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+          -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+          -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+          -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+          -1,   -1,   -1,   -1,
+    }}, {{
+        3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072,
+         999,  999,  999,  999,  999,  999,  999,  999,  999,  999,
+         999,  999,  999,  999,  999,  999,  999,  999,  999,  999,
+         999,  999,  999,  999,  999,  999,  999,  999,  999,  999,
+         999,  999,  999,  999,  999,  999,  999,  999,  999,  999,
+    }, {
+          -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+          -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+          -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+          -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+          -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+    }}
+};
+
+static const uint16_t hearing_thresh_tab[3][3][50] = {
+    {{
+        1403, 1141, 1000,  959,  948,  957,  946,  925,  899,  871,
+         843,  815,  789,  766,  745,  727,  705,  687,  681,  686,
+         701,  725,  768,  854,  940, 1018, 1075, 1103, 1111, 1106,
+        1098, 1105, 1142, 1237, 1419, 1721, 2169, 2805,
+    }, {
+        1401, 1130,  995,  957,  947,  955,  941,  918,  890,  861,
+         831,  803,  777,  754,  734,  717,  698,  684,  682,  692,
+         712,  743,  798,  894,  976, 1045, 1091, 1109, 1110, 1102,
+        1098, 1116, 1174, 1300, 1526, 1884, 2401, 3072,
+    }, {
+        1393, 1086,  974,  949,  957,  941,  913,  878,  843,  808,
+         777,  750,  727,  708,  695,  686,  681,  689,  714,  752,
+         811,  888,  971, 1044, 1087, 1108, 1110, 1102, 1098, 1115,
+        1172, 1290, 1489, 1812, 2293, 2964, 3072, 3072,
+    }}, {{
+        1412, 1343, 1141, 1047, 1000,  974,  959,  951,  948,  947,
+         957,  953,  946,  936,  925,  906,  878,  850,  822,  795,
+         771,  745,  719,  700,  687,  681,  685,  701,  733,  784,
+         885,  977, 1047, 1092, 1110, 1108, 1099, 1102, 1138, 1233,
+        1413, 1711, 2157, 2797,
+    }, {
+        1412, 1336, 1130, 1040,  995,  970,  957,  950,  947,  947,
+         955,  950,  941,  930,  918,  897,  868,  838,  810,  783,
+         759,  734,  710,  693,  684,  681,  690,  712,  752,  823,
+         924, 1009, 1069, 1102, 1111, 1104, 1098, 1111, 1168, 1295,
+        1518, 1873, 2388, 3072,
+    }, {
+        1411, 1293, 1086, 1009,  974,  957,  949,  947,  957,  951,
+         941,  928,  913,  896,  878,  852,  817,  785,  756,  732,
+         713,  695,  683,  682,  689,  710,  746,  811,  906,  992,
+        1061, 1099, 1111, 1106, 1098, 1107, 1155, 1266, 1471, 1799,
+        2277, 2945, 3072, 3072,
+    }}, {{
+        1431, 1412, 1403, 1379, 1343, 1293, 1229, 1180, 1125, 1075,
+        1040, 1014,  996,  979,  965,  957,  951,  948,  947,  957,
+         951,  940,  924,  903,  877,  846,  815,  785,  753,  725,
+         702,  686,  681,  689,  714,  760,  847,  947, 1028, 1083,
+        1108, 1109, 1101, 1100, 1132, 1222, 1402, 1705, 2160, 2803,
+    }, {
+        1431, 1412, 1401, 1375, 1336, 1278, 1215, 1168, 1115, 1066,
+        1032, 1008,  991,  975,  962,  954,  950,  947,  947,  955,
+         948,  935,  916,  894,  866,  835,  803,  772,  742,  715,
+         695,  683,  683,  697,  729,  784,  887,  982, 1054, 1096,
+        1111, 1106, 1098, 1107, 1159, 1281, 1505, 1865, 2391, 3072,
+    }, {
+        1427, 1411, 1393, 1353, 1293, 1215, 1160, 1118, 1072, 1031,
+        1003,  984,  971,  960,  952,  948,  947,  957,  952,  941,
+         924,  902,  876,  847,  815,  781,  750,  723,  700,  685,
+         681,  691,  719,  766,  858,  958, 1039, 1089, 1109, 1108,
+        1099, 1102, 1141, 1245, 1442, 1766, 2250, 2930, 3072, 3072,
+    }}
+};
+
+static const int16_t lwc_gain_tab[11][7] = {
+    {   -21,  -197,  -271,  -466, 32767, 32767, 32767 },
+    {  -197,   -29,  -244,  -271,  -540, 32767, 32767 },
+    {  -271,  -244,   -29,  -249,  -271,  -593, 32767 },
+    {  -466,  -271,  -249,   -29,  -251,  -271,  -632 },
+    {  -540,  -271,  -251,   -29,  -251,  -271,  -664 },
+    {  -593,  -271,  -251,   -29,  -252,  -271,  -690 },
+    {  -632,  -271,  -252,   -29,  -252,  -271,  -711 },
+    {  -664,  -271,  -252,   -29,  -252,  -271,  -730 },
+    {  -690,  -271,  -252,   -29,  -252,  -271,  -745 },
+    {  -711,  -271,  -252,   -29,  -253,  -271,  -759 },
+    {  -730,  -271,  -253,   -29,  -253,  -271,  -771 },
+};
+
+static const int16_t lwc_adj_tab[7] = {
+    -192, -320, -448, -512, -448, -320, -192,
+};
+
+static const uint8_t log_add_tab[212] = {
+    64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 52, 51, 50,
+    49, 48, 47, 47, 46, 45, 44, 44, 43, 42, 41, 41, 40, 39, 38, 38,
+    37, 36, 36, 35, 35, 34, 33, 33, 32, 32, 31, 30, 30, 29, 29, 28,
+    28, 27, 27, 26, 26, 25, 25, 24, 24, 23, 23, 22, 22, 21, 21, 21,
+    20, 20, 19, 19, 19, 18, 18, 18, 17, 17, 17, 16, 16, 16, 15, 15,
+    15, 14, 14, 14, 13, 13, 13, 13, 12, 12, 12, 12, 11, 11, 11, 11,
+    10, 10, 10, 10, 10,  9,  9,  9,  9,  9,  8,  8,  8,  8,  8,  8,
+     7,  7,  7,  7,  7,  7,  6,  6,  6,  6,  6,  6,  6,  6,  5,  5,
+     5,  5,  5,  5,  5,  5,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,
+     4,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  2,
+     2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
+     2,  2,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+     1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+     1,  1,  0,  0,
+};
+
+static const uint8_t bap_tab[64] = {
+     0,  1,  1,  1,  1,  1,  2,  2,  2,  2,  2,  3,  3,  3,  3,  4,
+     4,  4,  4,  5,  5,  5,  5,  6,  6,  6,  6,  7,  7,  7,  7,  8,
+     8,  8,  8,  9,  9,  9,  9, 10, 10, 10, 10, 11, 11, 11, 11, 12,
+    12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 15,
+};
+
+static inline int log_add(int a, int b)
+{
+    int c = FFABS(a - b) >> 1;
+    return FFMAX(a, b) + log_add_tab[FFMIN(c, 211)];
+}
+
+static void calc_lowcomp(int *msk_val)
+{
+    int lwc_val[17] = { 0 };
+    int i, j, k;
+
+    for (i = 0; i < 11; i++) {
+        int max_j = 0;
+        int max_v = INT_MIN;
+        int thr   = 0;
+
+        for (j = FFMAX(i - 3, 0), k = 0; j <= i + 3; j++, k++) {
+            int v = msk_val[j] + lwc_gain_tab[i][k];
+            if (v > max_v) {
+                max_j = j;
+                max_v = v;
+            }
+            thr = log_add(thr, v);
+        }
+
+        if (msk_val[i] < thr) {
+            for (j = FFMAX(max_j - 3, 0),
+                 k = FFMAX(3 - max_j, 0);
+                 j <= max_j + 3; j++, k++)
+                lwc_val[j] += lwc_adj_tab[k];
+        }
+    }
+
+    for (i = 0; i < 16; i++) {
+        int v = FFMAX(lwc_val[i], -512);
+        msk_val[i] = FFMAX(msk_val[i] + v, 0);
+    }
+}
+
+void ff_dolby_e_bit_allocate(int nb_exponent, int nb_code, int fr_code,
+                              int *exp, int *bap,
+                              int fg_spc, int fg_ofs, int msk_mod, int snr_ofs)
+{
+    int msk_val[MAX_BIAS_EXP_BA];
+    int psd_val[MAX_BIAS_EXP_BA];
+    int fast_leak  = 0;
+    int slow_leak  = 0;
+    int dc_code    = dc_code_tab[fr_code - 1];
+    int ht_code    = ht_code_tab[fr_code - 1];
+    int fast_gain  = fast_gain_tab[fg_ofs];
+    int slow_decay = slow_decay_tab[dc_code][msk_mod];
+    int misc_decay = misc_decay_tab[nb_code][dc_code][msk_mod];
+    const uint16_t *slow_gain      = slow_gain_tab[nb_code][msk_mod];
+    const uint16_t *fast_decay     = fast_decay_tab[nb_code][dc_code][msk_mod];
+    const uint16_t *fast_gain_adj  = fast_gain_adj_tab[nb_code][dc_code];
+    const uint16_t *hearing_thresh = hearing_thresh_tab[nb_code][ht_code];
+    int i;
+
+    for (i = 0; i < nb_exponent; i++)
+        psd_val[i] = (48 - exp[i]) * 64;
+
+    fast_gain_adj += band_ofs_tab[nb_code][fg_spc];
+    for (i = 0; i < nb_exponent; i++) {
+        fast_leak = log_add(fast_leak  - fast_decay[i],
+                            psd_val[i] - fast_gain + fast_gain_adj[i]);
+        slow_leak = log_add(slow_leak  - slow_decay,
+                            psd_val[i] - slow_gain[i]);
+        msk_val[i] = FFMAX(fast_leak, slow_leak);
+    }
+
+    fast_leak = 0;
+    for (i = nb_exponent - 1; i > band_low_tab[nb_code]; i--) {
+        fast_leak = log_add(fast_leak - misc_decay, psd_val[i] - fast_gain);
+        msk_val[i] = FFMAX(msk_val[i], fast_leak);
+    }
+
+    for (i = 0; i < nb_exponent; i++)
+        msk_val[i] = FFMAX(msk_val[i], hearing_thresh[i]);
+
+    if (!nb_code)
+        calc_lowcomp(msk_val);
+
+    for (i = 0; i < nb_exponent; i++) {
+        int v = 16 * (snr_ofs - 64) + psd_val[i] - msk_val[i] >> 5;
+        bap[i] = bap_tab[av_clip_uintp2(v, 6)];
+    }
+}
diff --git a/libavcodec/dolby_e_bitalloc.h b/libavcodec/dolby_e_bitalloc.h
new file mode 100644
index 0000000000..5f8bc6577b
--- /dev/null
+++ b/libavcodec/dolby_e_bitalloc.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 foo86
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVCODEC_DOLBY_E_BITALLOC_H
+#define AVCODEC_DOLBY_E_BITALLOC_H
+
+#include <stdint.h>
+
+#include "dolby_e.h"
+
+#define DBE_MAX_MSTR_EXP 2
+
+/**
+ * @struct DBEGroup
+ * Fixed per-(segment,gr_code) group geometry: exponent/mantissa counts and
+ * offsets, and the IMDCT/window parameters needed to reconstruct one group.
+ * Entries are static, table-driven (ff_dolby_e_frm_ofs_tab) -- none of this
+ * is coded in the bitstream itself.
+ */
+typedef struct DBEGroup {
+    uint8_t         nb_exponent;
+    uint8_t         nb_bias_exp[DBE_MAX_MSTR_EXP];
+    uint16_t        exp_ofs;
+    uint16_t        mnt_ofs;
+    const uint8_t   *nb_mantissa;
+    uint8_t         imdct_idx;
+    uint8_t         imdct_phs;
+    uint16_t        win_len;
+    uint16_t        dst_ofs;
+    uint16_t        win_ofs;
+    uint16_t        src_ofs;
+} DBEGroup;
+
+extern const DBEGroup   *const ff_dolby_e_frm_ofs_tab[2][4];
+extern const uint8_t     ff_dolby_e_nb_groups_tab[4];
+extern const uint8_t     ff_dolby_e_nb_mstr_exp_tab[4];
+extern const uint8_t     ff_dolby_e_mantissa_size1[16][4];
+extern const uint8_t     ff_dolby_e_mantissa_size2[16][4];
+extern const int8_t      ff_dolby_e_lfe_channel_tab[MAX_PROG_CONF + 1];
+
+/**
+ * Compute the bit allocation pointer (bap) for one group from its
+ * transmitted parameters. This is the complete Dolby E/AC-3-style
+ * psychoacoustic model; it is run identically by the decoder (from
+ * transmitted parameters) and by the encoder (searched over the free
+ * parameters to hit a bit budget). See dolby-e-encoder-spec.md 2.2.
+ *
+ * @param nb_exponent number of exponents/bap values in this group
+ * @param nb_code      group->imdct_idx (selects the table set: 0/1/2)
+ * @param fr_code      metadata.fr_code (1-based index into dc/ht code tabs)
+ * @param exp          nb_exponent exponent values for this group
+ * @param bap          [out] nb_exponent bap values for this group
+ * @param fg_spc, fg_ofs, msk_mod, snr_ofs  transmitted bit-alloc parameters
+ */
+void ff_dolby_e_bit_allocate(int nb_exponent, int nb_code, int fr_code,
+                              int *exp, int *bap,
+                              int fg_spc, int fg_ofs, int msk_mod, int snr_ofs);
+
+#endif
diff --git a/libavcodec/dolby_e_transform.c b/libavcodec/dolby_e_transform.c
new file mode 100644
index 0000000000..822c121d4d
--- /dev/null
+++ b/libavcodec/dolby_e_transform.c
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2017 foo86
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "libavutil/mem.h"
+#include "libavutil/thread.h"
+#include "libavutil/mem_internal.h"
+#include "kbdwin.h"
+#include "dolby_e_transform.h"
+
+const uint8_t ff_dolby_e_imdct_bits_tab[3] = { 8, 9, 11 };
+
+static const float start_window[192] = {
+    0.00161569379826, 0.00185748233347, 0.00198562758548, 0.00207834078104,
+    0.00215717748523, 0.00223067096393, 0.00230299213147, 0.00237651215396,
+    0.00245275561606, 0.00253281402069, 0.00261754673613, 0.00270768786168,
+    0.00280390761895, 0.00290684998656, 0.00301715751161, 0.00313548872798,
+    0.00326253122934, 0.00339901215995, 0.00354570716636, 0.00370344845023,
+    0.00387313232586, 0.00405572653911, 0.00425227750970, 0.00446391759265,
+    0.00469187240551, 0.00493746822816, 0.00520213944619, 0.00548743597507,
+    0.00579503056737, 0.00612672586953, 0.00648446105606, 0.00687031782873,
+    0.00728652552677, 0.00773546505205, 0.00821967127415, 0.00874183354619,
+    0.00930479393832, 0.00991154278653, 0.01056521116692, 0.01126905994567,
+    0.01202646513050, 0.01284089936559, 0.01371590957417, 0.01465509096066,
+    0.01566205783408, 0.01674041199523, 0.01789370972358, 0.01912542867865,
+    0.02043893626265, 0.02183746113793, 0.02332406961796, 0.02490164852364,
+    0.02657289580178, 0.02834031974193, 0.03020624702903, 0.03217283918354,
+    0.03424211623810, 0.03641598586180, 0.03869627565015, 0.04108476601498,
+    0.04358322107390, 0.04619341515939, 0.04891715301882, 0.05175628239149,
+
+    0.05471237327267, 0.05778734733755, 0.06098291402413, 0.06430101352084,
+    0.06774345212186, 0.07131188644726, 0.07500780649199, 0.07883251748595,
+    0.08278712056651, 0.08687249228061, 0.09108926295730, 0.09543779401074,
+    0.09991815425851, 0.10453009536427, 0.10927302653894, 0.11414598865987,
+    0.11914762799220, 0.12427616972097, 0.12952939152560, 0.13490459744934,
+    0.14039859233595, 0.14600765712201, 0.15172752528722, 0.15755336077528,
+    0.16347973770491, 0.16950062219342, 0.17560935661442, 0.18179864660619,
+    0.18806055113821, 0.19438647593012, 0.20076717050010, 0.20719272909882,
+    0.21365259576030, 0.22013557367283, 0.22662983904194, 0.23312295958328,
+    0.23960191774666, 0.24605313873388, 0.25246252333253, 0.25881548554631,
+    0.26509699495987, 0.27129162373316, 0.27738359807707, 0.28335685401987,
+    0.28919509723179, 0.29488186663467, 0.30040060148455, 0.30573471157819,
+    0.31086765019993, 0.31578298939317, 0.32046449711227, 0.32489621578468,
+    0.32906254179156, 0.33294830535654, 0.33653885031840, 0.33982011325336,
+    0.34277870140679, 0.34540196889300, 0.34767809062480, 0.34959613344194,
+    0.35114612391958, 0.35231911235422, 0.35310723244504, 0.35350375621308,
+
+    0.35350314372945, 0.35310108725579, 0.35229454943591, 0.35108179521634,
+    0.34946241721522, 0.34743735430290, 0.34500890320420, 0.34218072298001,
+    0.33895783229541, 0.33534659943168, 0.33135472505060, 0.32699121776996,
+    0.32226636266000, 0.31719168282019, 0.31177989424432, 0.30604485422875,
+    0.30000150362379, 0.29366580327088, 0.28705466500775, 0.28018587766131,
+    0.27307802848095, 0.26575042049535, 0.25822298630189, 0.25051619882000,
+    0.24265097955783, 0.23464860495522, 0.22653061137548, 0.21831869932335,
+    0.21003463746705, 0.20170016703857, 0.19333690717811, 0.18496626177620,
+    0.17660932835062, 0.16828680947474, 0.16001892724986, 0.15182534128597,
+    0.14372507062477, 0.13573642000364, 0.12787691082233, 0.12016321713317,
+    0.11261110693234, 0.10523538898282, 0.09804986534955, 0.09106728977263,
+    0.08429933194438, 0.07775654768810, 0.07144835495683, 0.06538301547324,
+    0.05956762170687, 0.05400808871425, 0.04870915012107, 0.04367435714993,
+    0.03890607899172, 0.03440550179663, 0.03017262174627, 0.02620622428513,
+    0.02250383492507, 0.01906161305732, 0.01587412848221, 0.01293388032354,
+    0.01023019677288, 0.00774641320626, 0.00545109736891, 0.00325868651263,
+};
+
+static const float short_window2[192] = {
+    0.00018861094606, 0.00033433010202, 0.00050309624485, 0.00070306161748,
+    0.00093995174533, 0.00121913067128, 0.00154606505568, 0.00192647806126,
+    0.00236641248692, 0.00287225985240, 0.00345077377440, 0.00410907465023,
+    0.00485464855241, 0.00569534163219, 0.00663935063508, 0.00769520981249,
+    0.00887177436246, 0.01017820046395, 0.01162392194150, 0.01321862359335,
+    0.01497221122468, 0.01689477844427, 0.01899657030441, 0.02128794388846,
+    0.02377932597692, 0.02648116795039, 0.02940389811590, 0.03255787167130,
+    0.03595331854986, 0.03960028941437, 0.04350860009563, 0.04768777479454,
+    0.05214698838949, 0.05689500821121, 0.06194013566525, 0.06729014809766,
+    0.07295224131210, 0.07893297315602, 0.08523820859989, 0.09187306673620,
+    0.09884187012422, 0.10614809690222, 0.11379433608064, 0.12178224641797,
+    0.13011251926531, 0.13878484574660, 0.14779788861830, 0.15714925912610,
+    0.16683549914631, 0.17685206886673, 0.18719334022589, 0.19785259629099,
+    0.20882203671372, 0.22009278936030, 0.23165492816694, 0.24349749722585,
+    0.25560854105961, 0.26797514099368, 0.28058345748882, 0.29341877824732,
+    0.30646557185942, 0.31970754671026, 0.33312771482295, 0.34670846027024,
+
+    0.36043161174692, 0.37427851885723, 0.38823013163645, 0.40226708279486,
+    0.41636977214436, 0.43051845264462, 0.44469331748632, 0.45887458761470,
+    0.47304259908636, 0.48717788964798, 0.50126128392546, 0.51527397661778,
+    0.52919761310050, 0.54301436685998, 0.55670701320069, 0.57025899869448,
+    0.58365450587230, 0.59687851269542, 0.60991684638414, 0.62275623122793,
+    0.63538433005035, 0.64778977905593, 0.65996221584264, 0.67189230042379,
+    0.68357172916486, 0.69499324160511, 0.70615062019861, 0.71703868307548,
+    0.72765326998919, 0.73799122168099, 0.74805035295521, 0.75782941981995,
+    0.76732808110520, 0.77654685502339, 0.78548707118622, 0.79415081863423,
+    0.80254089047207, 0.81066072573188, 0.81851434910893, 0.82610630922734,
+    0.83344161609862, 0.84052567843230, 0.84736424144524, 0.85396332579459,
+    0.86032916822973, 0.86646816451999, 0.87238681516918, 0.87809167437532,
+    0.88358930263537, 0.88888622333073, 0.89398888356256, 0.89890361943564,
+    0.90363662591861, 0.90819393133744, 0.91258137648979, 0.91680459830070,
+    0.92086901787718, 0.92477983276087, 0.92854201312583, 0.93216030163834,
+    0.93563921662343, 0.93898305819384, 0.94219591693690, 0.94528168477979,
+
+    0.94823843319821, 0.95106834367330, 0.95377776558539, 0.95636718335775,
+    0.95883679961479, 0.96118650212341, 0.96341583179195, 0.96552395212906,
+    0.96750962060547, 0.96937116231768, 0.97110644638309, 0.97271286544154,
+    0.97418731862798, 0.97552619834964, 0.97672538116257, 0.97778022299974,
+    0.97868555895586, 0.97943570778357, 0.98002448120255, 0.98044519806866,
+    0.98069070339493, 0.98075339216123, 0.98062523779637, 0.98029782516478,
+    0.97976238784222, 0.97900984942031, 0.97803086854002, 0.97681588731895,
+    0.97535518280755, 0.97363892108474, 0.97165721358452, 0.96940017523145,
+    0.96685798395452, 0.96402094114589, 0.96087953263194, 0.95742448973047,
+    0.95364684997699, 0.94953801711660, 0.94508981997396, 0.94029456983253,
+    0.93514511597504, 0.92963489905951, 0.92375800202883, 0.91750919827624,
+    0.91088399681406, 0.90387868421832, 0.89649036314692, 0.88871698725397,
+    0.88055739234735, 0.87201132366062, 0.86307945913336, 0.85376342861693,
+    0.84406582894455, 0.83399023482637, 0.82354120554757, 0.81272428745995,
+    0.80154601230457, 0.79001389138101, 0.77813640562199, 0.76592299164227,
+    0.75338402384395, 0.74053079267526, 0.72737547915460, 0.71393112578527,
+};
+
+static const float short_window3[64] = {
+    0.00326887936450, 0.00550242900936, 0.00786846643791, 0.01045683453520,
+    0.01330402120132, 0.01643221072863, 0.01985798040609, 0.02359509464766,
+    0.02765559221954, 0.03205025893128, 0.03678884369614, 0.04188015679495,
+    0.04733210987781, 0.05315172583924, 0.05934513287609, 0.06591755045290,
+    0.07287327156378, 0.08021564389822, 0.08794705152307, 0.09606889811179,
+    0.10458159240070, 0.11348453632940, 0.12277611617809, 0.13245369691511,
+    0.14251361989876, 0.15295120402567, 0.16376075037904, 0.17493555039885,
+    0.18646789757072, 0.19834910260891, 0.21056951208995, 0.22311853047787,
+    0.23598464546683, 0.24915545655419, 0.26261770674500, 0.27635731727778,
+    0.29035942525136, 0.30460842402318, 0.31908800624032, 0.33378120935681,
+    0.34867046348260, 0.36373764140285, 0.37896411059909, 0.39433078709788,
+    0.40981819096657, 0.42540650327031, 0.44107562429959, 0.45680523287270,
+    0.47257484651351, 0.48836388230077, 0.50415171818214, 0.51991775454258,
+    0.53564147581496, 0.55130251191887, 0.56688069931047, 0.58235614142007,
+    0.59770926827271, 0.61292089506118, 0.62797227945823, 0.64284517745255,
+    0.65752189749349, 0.67198535273209, 0.68621911114984, 0.70020744337099,
+};
+
+DECLARE_ALIGNED(32, float, ff_dolby_e_window)[DBE_WINDOW_SIZE];
+
+static av_cold void init_window_tables(void)
+{
+    float *window = ff_dolby_e_window;
+    int i;
+
+    // short 1
+    ff_kbd_window_init(window, 3.0f, 128);
+    for (i = 0; i < 128; i++)
+        window[128 + i] = window[127 - i];
+
+    // start
+    for (i = 0; i < 192; i++)
+        window[256 + i] = start_window[i];
+
+    // short 2
+    for (i = 0; i < 192; i++)
+        window[448 + i] = short_window2[i];
+    for (i = 0; i < 64; i++)
+        window[640 + i] = window[63 - i];
+
+    // short 3
+    for (i = 0; i < 64; i++)
+        window[704 + i] = short_window3[i];
+    for (i = 0; i < 192; i++)
+        window[768 + i] = window[64 + i];
+
+    // bridge
+    for (i = 0; i < 128; i++)
+        window[960 + i] = window[i];
+    for (i = 0; i < 64; i++)
+        window[1088 + i] = 1.0f;
+
+    // long
+    ff_kbd_window_init(window + 1408, 3.0f, 256);
+    for (i = 0; i < 640; i++)
+        window[1664 + i] = 1.0f;
+    for (i = 0; i < 256; i++)
+        window[2304 + i] = window[1152 + i] = window[1663 - i];
+
+    // reverse start
+    for (i = 0; i < 192; i++)
+        window[2560 + i] = window[447 - i];
+
+    // reverse short 2
+    for (i = 0; i < 256; i++)
+        window[2752 + i] = window[703 - i];
+
+    // reverse short 3
+    for (i = 0; i < 256; i++)
+        window[3008 + i] = window[959 - i];
+
+    // reverse bridge
+    for (i = 0; i < 448; i++)
+        window[3264 + i] = window[1407 - i];
+}
+
+void ff_dolby_e_init_window(void)
+{
+    static AVOnce init_once = AV_ONCE_INIT;
+    ff_thread_once(&init_once, init_window_tables);
+}
+
+int ff_dolby_e_transform_init(DBETransform *t)
+{
+    float scale = 2.0f;
+    /* Forward MDCT scale, calibrated empirically per transform size against
+     * the decoder's fixed inverse scale=2.0 (does not follow a clean 1/n2
+     * power law across sizes -- calibrated independently per index via
+     * Gate 3 testing, see tools/dolby_e_transform_test.c). Polarity of
+     * phs==1 groups is corrected separately in ff_dolby_e_mdct_calc(). */
+    static const float fwd_scale_tab[3] = {
+        0.007927333333813935f,   /* idx 0, n2=128  */
+        0.003963666666906967f,   /* idx 1, n2=256  */
+        0.0005298084368779297f,  /* idx 2, n2=1024 */
+    };
+    int ret;
+
+    ff_dolby_e_init_window();
+
+    for (int i = 0; i < 3; i++) {
+        float fwd_scale = fwd_scale_tab[i];
+
+        if ((ret = av_tx_init(&t->imdct[0][i], &t->imdct_fn[0][i], AV_TX_FLOAT_MDCT,
+                              1, 1 << ff_dolby_e_imdct_bits_tab[i] - 1, &scale, 0)) < 0)
+            return ret;
+        if ((ret = av_tx_init(&t->imdct[1][i], &t->imdct_fn[1][i], AV_TX_FLOAT_MDCT,
+                              1, 1 << ff_dolby_e_imdct_bits_tab[i] - 1, &scale, AV_TX_FULL_IMDCT)) < 0)
+            return ret;
+        if ((ret = av_tx_init(&t->mdct[i], &t->mdct_fn[i], AV_TX_FLOAT_MDCT,
+                              0, 1 << ff_dolby_e_imdct_bits_tab[i] - 1, &fwd_scale, 0)) < 0)
+            return ret;
+    }
+
+    return 0;
+}
+
+void ff_dolby_e_transform_uninit(DBETransform *t)
+{
+    for (int i = 0; i < 3; i++) {
+        av_tx_uninit(&t->imdct[0][i]);
+        av_tx_uninit(&t->imdct[1][i]);
+        av_tx_uninit(&t->mdct[i]);
+    }
+}
+
+void ff_dolby_e_imdct_calc(DBETransform *t, const DBEGroup *g,
+                            float *result, const float *values)
+{
+    AVTXContext *imdct = t->imdct[g->imdct_phs == 1][g->imdct_idx];
+    av_tx_fn  imdct_fn = t->imdct_fn[g->imdct_phs == 1][g->imdct_idx];
+    int n   = 1 << ff_dolby_e_imdct_bits_tab[g->imdct_idx];
+    int n2  = n >> 1;
+
+    switch (g->imdct_phs) {
+    case 0:
+        imdct_fn(imdct, result, (void *)values, sizeof(float));
+        for (int i = 0; i < n2; i++)
+            result[n2 + i] = result[n2 - i - 1];
+        break;
+    case 1:
+        imdct_fn(imdct, result, (void *)values, sizeof(float));
+        break;
+    case 2:
+        imdct_fn(imdct, result + n2, (void *)values, sizeof(float));
+        for (int i = 0; i < n2; i++)
+            result[i] = -result[n - i - 1];
+        break;
+    }
+}
+
+void ff_dolby_e_mdct_calc(DBETransform *t, const DBEGroup *g,
+                           float *values, const float *windowed_samples)
+{
+    AVTXContext *mdct = t->mdct[g->imdct_idx];
+    av_tx_fn  mdct_fn = t->mdct_fn[g->imdct_idx];
+    int n2 = 1 << (ff_dolby_e_imdct_bits_tab[g->imdct_idx] - 1);
+
+    mdct_fn(mdct, values, (void *)windowed_samples, sizeof(float));
+
+    /* The decoder's phs==1 groups use the FULL_IMDCT inverse path, which
+     * has an opposite intrinsic polarity from the half+mirror path (phs 0/2)
+     * -- found empirically (Gate 3 testing, dolby_e_transform_test.c). */
+    if (g->imdct_phs == 1)
+        for (int i = 0; i < n2; i++)
+            values[i] = -values[i];
+}
+
+void ff_dolby_e_mdct_calc_fold(DBETransform *t, const DBEGroup *g,
+                                float *values, const float *target)
+{
+    /* Round-trip impulse magnitude for phs 0/2, calibrated per transform
+     * size against the fwd_scale_tab in ff_dolby_e_transform_init() --
+     * see tools/dolby_e_transform_test.c (Gate 3 testing). Deliberately
+     * separate from phs==1's magnitude: the two decode paths (half+mirror
+     * vs FULL_IMDCT) have different intrinsic scale, confirmed empirically. */
+    static const float fold_a_tab[3] = {
+        1.014699f, /* idx 0 */
+        1.014699f, /* idx 1 */
+        0.542523839363f, /* idx 2 */
+    };
+    int n2 = 1 << (ff_dolby_e_imdct_bits_tab[g->imdct_idx] - 1);
+    int n  = n2 * 2;
+    float a = fold_a_tab[g->imdct_idx];
+    float *abuf = av_malloc(n * sizeof(float));
+
+    for (int j = 0; j < n2 / 2; j++)
+        abuf[j] = target[n2 / 2 - 1 - j] / (2 * a);
+    for (int j = n2 / 2; j < 3 * n2 / 2; j++)
+        abuf[j] = -target[j - n2 / 2] / (2 * a);
+    for (int j = 3 * n2 / 2; j < n; j++)
+        abuf[j] = -target[(5 * n2 / 2 - 1) - j] / (2 * a);
+
+    ff_dolby_e_mdct_calc(t, g, values, abuf);
+    av_free(abuf);
+}
diff --git a/libavcodec/dolby_e_transform.h b/libavcodec/dolby_e_transform.h
new file mode 100644
index 0000000000..483fe4ff60
--- /dev/null
+++ b/libavcodec/dolby_e_transform.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2017 foo86
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVCODEC_DOLBY_E_TRANSFORM_H
+#define AVCODEC_DOLBY_E_TRANSFORM_H
+
+#include "libavutil/tx.h"
+#include "dolby_e_bitalloc.h"
+
+#define DBE_WINDOW_SIZE 3712
+
+/*
+ * Shared analysis=synthesis window table (dolby-e-encoder-spec.md 9.4/GATE-C:
+ * US 5,899,969 confirms the analysis and synthesis windows are identical).
+ * Thread-safe, idempotent: safe to call from both decoder and encoder init.
+ */
+extern float ff_dolby_e_window[DBE_WINDOW_SIZE];
+void ff_dolby_e_init_window(void);
+
+extern const uint8_t ff_dolby_e_imdct_bits_tab[3];
+
+typedef struct DBETransform {
+    AVTXContext *imdct[2][3];   /* [phs==1][imdct_idx], synthesis (inverse) */
+    av_tx_fn     imdct_fn[2][3];
+    AVTXContext *mdct[3];       /* [imdct_idx], analysis (forward) */
+    av_tx_fn     mdct_fn[3];
+} DBETransform;
+
+int  ff_dolby_e_transform_init(DBETransform *t);
+void ff_dolby_e_transform_uninit(DBETransform *t);
+
+/*
+ * Synthesis (inverse), identical to imdct_calc() in dolby_e.c: produces the
+ * group's full n = 1 << ff_dolby_e_imdct_bits_tab[g->imdct_idx] samples into
+ * result[0..n), from n2 = n/2 spectral `values`.
+ */
+void ff_dolby_e_imdct_calc(DBETransform *t, const DBEGroup *g,
+                            float *result, const float *values);
+
+/*
+ * Analysis (forward), for imdct_phs==1 groups only: produces n2 spectral
+ * `values` from n windowed time-domain samples (already windowed by the
+ * caller). phs==1's inverse has a direct "self" term per output sample, so
+ * a standard windowed forward MDCT round-trips correctly via overlap-add.
+ */
+void ff_dolby_e_mdct_calc(DBETransform *t, const DBEGroup *g,
+                           float *values, const float *windowed_samples);
+
+/*
+ * Analysis (forward), for imdct_phs==0/2 groups only: decode's half+mirror
+ * inverse for these phases has NO self term (only cross-position aliasing
+ * pairs -- see dolby-e-encoder-spec.md Phase 3 notes), so a windowed
+ * forward MDCT of the input signal cannot reconstruct it; the amount of
+ * lookahead/lookbehind needed (up to n samples) isn't a windowing problem,
+ * it's solving the transform's fold relation directly.
+ *
+ * `target` is n2 = n/2 samples representing the DESIRED, already
+ * window-multiplied buffer content for the group's direct region:
+ * buffer[0:n2) for phs==0, buffer[n2:n) for phs==2. Pre-multiplying by the
+ * synthesis window (not dividing) is required so overlapping neighbors'
+ * window^2 terms sum to the true signal via the Princen-Bradley condition,
+ * instead of each side independently reconstructing the full value.
+ */
+void ff_dolby_e_mdct_calc_fold(DBETransform *t, const DBEGroup *g,
+                                float *values, const float *target);
+
+#endif
-- 
2.52.0


From 43aaab1f188e39ff7cc45fde1f352fd6ca6111f4 Mon Sep 17 00:00:00 2001
From: Oren Jack Maurice <[email protected]>
Date: Sun, 9 Aug 2026 12:01:38 +0300
Subject: [PATCH 173/180] avcodec/dolby_e: add encoder

Adds a Dolby E encoder built on the bit allocator and MDCT/IMDCT helpers
just factored out of the decoder. Scope, per the design notes retained
in the file header:

 - All 5 SMPTE frame rates (23.98/24/25/29.97/30 fps) and all 3 SMPTE
   337M word depths (16/20/24, "word_bits") are supported, sharing the
   decoder's own sample-rate/envelope tables. Only the 25 fps/20-bit
   combination has been cross-checked against a real Dolby-authored
   sample; the rest are round-trip self-verified (encode -> mux ->
   demux -> decode) against this codebase's own decoder, not against
   third-party hardware or software.
 - Each channel uses a single-group configuration (gr_code=0, or 3 for
   the LFE channel, which the format itself forces); snr_ofs is
   binary-searched per channel against its word budget as the encoder's
   rate-control knob.
 - No bitstream whitening key, no run-length/escape-coded mantissas, no
   OAMD/Atmos evolution_data. metadata_crc is emitted as 0, matching
   every real sample measured so far; the CRC's exact bit range has not
   been independently derived.

Reviewers should treat the untested frame-rate/word-depth combinations
and the metadata_crc field as the areas most likely to need real-sample
validation before this is relied on for interop with third-party Dolby E
equipment.

Development-method: AI-assisted (Claude Code); reviewed by the submitter
before posting.

Signed-off-by: Oren Jack Maurice <[email protected]>
---
 Changelog                 |   3 +
 doc/encoders.texi         |  37 ++
 doc/general_contents.texi |   2 +-
 libavcodec/Makefile       |   1 +
 libavcodec/allcodecs.c    |   1 +
 libavcodec/dolby_e_enc.c  | 960 ++++++++++++++++++++++++++++++++++++++
 6 files changed, 1003 insertions(+), 1 deletion(-)
 create mode 100644 libavcodec/dolby_e_enc.c

diff --git a/Changelog b/Changelog
index b1dca93516..a23d3aa25f 100644
--- a/Changelog
+++ b/Changelog
@@ -20,6 +20,9 @@ version 9.0:
 - Add AMF hardware memory mapping support.
 - ONNX Runtime DNN backend with GPU execution provider support
 - Remove deprecated NVENC options and support for pre-11.1 SDK versions
+- Dolby E encoder
+- SMPTE 337M (s337m) muxer
+- Dolby E carriage in WAV, MPEG-TS and IEC 61937 (spdif) muxers
 
 
 version 8.1:
diff --git a/doc/encoders.texi b/doc/encoders.texi
index 605638e040..172cbfeea4 100644
--- a/doc/encoders.texi
+++ b/doc/encoders.texi
@@ -518,6 +518,43 @@ Selected by Encoder (default)
 
 @end table
 
+@anchor{dolby_e}
+@section dolby_e
+
+Dolby E encoder.
+
+Dolby E packs up to 8 audio programs plus metadata into a burst that fits
+in one video frame period, for carriage over AES3/SDI professional audio
+links (typically demuxed/muxed as raw SMPTE 337M via the @code{s337m}
+muxer, or over IEC 61937-carrying containers such as WAV or MPEG-TS).
+The @var{sample_rate} passed to the encoder selects the SMPTE frame rate
+(23.98/24/25/29.97/30 fps) rather than an audio sample rate as such; see
+the @code{s337m} muxer and @code{dolby_e} decoder for the accepted rates.
+
+@subsection Options
+
+@table @option
+@item program_config
+Program configuration, selecting how the input channels are split into
+mono/stereo/5.1 programs (same numbering as the @code{dolby_e} decoder's
+@code{prog_conf} table).
+
+@item dialnorm
+Dialogue Level in dBFS, from -31 to -1. Default -31.
+
+@item word_bits
+SMPTE 337M word size in bits: 16, 20 or 24. Default 20.
+
+@item start_timecode
+Starting SMPTE timecode, as @code{hh:mm:ss:ff} (or @code{hh:mm:ss;ff} for
+drop-frame). Auto-increments once per encoded frame. If unset, the
+timecode field is explicitly marked invalid.
+
+@item fg_spc, fg_ofs, msk_mod
+Masking-curve shape tuning for the bit allocator. Expert options; the
+defaults match real-world Dolby E encoders for the cases tested so far.
+@end table
+
 @anchor{flac}
 @section flac
 
diff --git a/doc/general_contents.texi b/doc/general_contents.texi
index 7a1c4f3a21..517a7195aa 100644
--- a/doc/general_contents.texi
+++ b/doc/general_contents.texi
@@ -1340,7 +1340,7 @@ following image formats are supported:
     @tab All versions except 5.1 are supported.
 @item DCA (DTS Coherent Acoustics)  @tab  X  @tab  X
     @tab supported extensions: XCh, XXCH, X96, XBR, XLL, LBR (partially)
-@item Dolby E  @tab     @tab  X
+@item Dolby E  @tab  X  @tab  X
 @item DPCM Cuberoot-Delta-Exact @tab  @tab  X
     @tab Used in few games.
 @item DPCM Gremlin           @tab     @tab  X
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index 33cfceae8d..d64c5ba78a 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -341,6 +341,7 @@ OBJS-$(CONFIG_DFPWM_ENCODER)           += dfpwmenc.o
 OBJS-$(CONFIG_DNXHD_DECODER)           += dnxhddec.o dnxhddata.o
 OBJS-$(CONFIG_DNXHD_ENCODER)           += dnxhdenc.o dnxhddata.o
 OBJS-$(CONFIG_DOLBY_E_DECODER)         += dolby_e.o dolby_e_bitalloc.o dolby_e_transform.o dolby_e_parse.o kbdwin.o
+OBJS-$(CONFIG_DOLBY_E_ENCODER)         += dolby_e_enc.o dolby_e_bitalloc.o dolby_e_transform.o audio_frame_queue.o kbdwin.o
 OBJS-$(CONFIG_DPX_DECODER)             += dpx.o
 OBJS-$(CONFIG_DPX_ENCODER)             += dpxenc.o
 OBJS-$(CONFIG_DSD_LSBF_DECODER)        += dsddec.o dsd.o
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index 47eded261f..a185fc78e1 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -456,6 +456,7 @@ extern const FFCodec ff_dca_encoder;
 extern const FFCodec ff_dca_decoder;
 extern const FFCodec ff_dfpwm_encoder;
 extern const FFCodec ff_dfpwm_decoder;
+extern const FFCodec ff_dolby_e_encoder;
 extern const FFCodec ff_dolby_e_decoder;
 extern const FFCodec ff_dsd_lsbf_decoder;
 extern const FFCodec ff_dsd_msbf_decoder;
diff --git a/libavcodec/dolby_e_enc.c b/libavcodec/dolby_e_enc.c
new file mode 100644
index 0000000000..31a2b47812
--- /dev/null
+++ b/libavcodec/dolby_e_enc.c
@@ -0,0 +1,960 @@
+/*
+ * Dolby E encoder
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * Dolby-E-format encoder. Scope, per dolby-e-encoder-spec.md:
+ *  - fr_code/word_bits are generalized across all 5 SMPTE-defined frame
+ *    rates (23.98/24/25/29.97/30 fps, selected via avctx->sample_rate) and
+ *    all 3 SMPTE 337M word depths (16/20/24, "word_bits" AVOption). Only
+ *    the 25 fps/20-bit combination has been cross-checked against a real
+ *    Dolby-authored sample; the others share the same already-generic
+ *    decoder-side tables (bit allocation, sync word, burst envelope) but
+ *    have only been round-trip self-verified (encode -> mux -> demux ->
+ *    decode), not checked against real broadcast content.
+ *  - Every non-LFE channel uses gr_code=0 (single "long" group); the LFE
+ *    channel is forced to gr_code=3 by the format itself. Both are the
+ *    single-group configuration validated in Phase 3 (dolby_e_transform.c,
+ *    ff_dolby_e_mdct_calc_fold()). gr_code 1/2 (short multi-group) are also
+ *    validated but not used here -- one group per channel keeps the first
+ *    working encoder's bit-allocation logic simple, per the spec's
+ *    "start crude" guidance.
+ *  - idx[] is always 0 (no run-length savings, no escape-coded mantissas).
+ *  - fg_spc/fg_ofs/msk_mod are fixed; snr_ofs is binary-searched per
+ *    channel against its word budget -- the one real rate-control knob.
+ *  - key_present is always 0 (no bitstream whitening key).
+ *  - metadata_crc is emitted as 0 verbatim, matching every real sample
+ *    measured so far (dolby-e-encoder-spec.md 6.2.1) -- not independently
+ *    derived, since the CRC's exact bit range is still open (GATE-B-adjacent).
+ *  - evolution_data_exists is always 0 (OAMD/Atmos metadata is explicitly
+ *    out of scope, dolby-e-encoder-spec.md 8.2).
+ */
+
+#include <float.h>
+#include <math.h>
+
+#include "libavutil/channel_layout.h"
+#include "libavutil/intreadwrite.h"
+#include "libavutil/mem.h"
+#include "libavutil/opt.h"
+#include "libavutil/timecode.h"
+
+#include "audio_frame_queue.h"
+#include "codec_internal.h"
+#include "encode.h"
+#include "put_bits.h"
+#include "dolby_e.h"
+#include "dolby_e_bitalloc.h"
+#include "dolby_e_transform.h"
+
+/* Max samples of lookahead the fold construction needs beyond one frame,
+ * see Phase 3 notes. */
+#define LOOKAHEAD    256
+#define BUF_SAMPLES  (2 * FRAME_SAMPLES)
+/* >= 8 programs * AC3_PGM_BITS(126) + max word_bits(24)-1 headroom. */
+#define MAX_TAIL_BITS 1536
+
+/* fr_code (1-5) -> avctx->sample_rate, exactly mirroring the decoder's own
+ * sample_rate_tab (dolby_e_parse.c) -- FRAME_SAMPLES itself (dolby_e.h) is a
+ * fixed 1792 regardless of frame rate (confirmed shared with the decoder,
+ * which sets frame->nb_samples = FRAME_SAMPLES unconditionally); what
+ * varies by frame rate is only this reported rate and the coded burst
+ * envelope below. */
+static const int fr_code_sample_rate_tab[6] = {
+    0, 42965, 43008, 44800, 53706, 53760
+};
+
+/* fr_code -> total coded words per burst, exactly mirroring the s337m
+ * demuxer/muxer's own 4-bucket envelope table (libavformat/s337m.c
+ * s337m_get_offset_and_codec(), libavformat/s337menc.c burst_payload_words()):
+ * that table is keyed on word count alone, not word_bits or frame rate, and
+ * has only 4 distinct buckets for the 5 frame rates -- 29.97 and 30 fps
+ * share the same bucket there, so they do here too, deferring entirely to
+ * the demuxer's own already-implemented mapping rather than inventing a
+ * 5th value nothing downstream would recognize. */
+static const int fr_code_total_words_tab[6] = {
+    0, 3644, 3640, 3648, 3040, 3040
+};
+
+/* Coded (Dolby-native) channel index -> AVFrame channel index, exactly
+ * mirroring dolby_e.c's own ch_reorder_4/6/8/n tables (not exported, so
+ * duplicated verbatim rather than guessed) -- e.g. for 6 channels the coded
+ * buffer order is L,C,Ls,R,LFE,Rs, while AVFrame/ch_layout order is the
+ * standard L,R,C,LFE,Ls,Rs, per EBU Tech 3285 Suppl. 6's own documented
+ * "L for all programmes, then C for all programmes, ..." channel sequence.
+ * The encoder needs the INVERSE of the decoder's mapping: source sample
+ * data for coded channel ch comes from frame->extended_data[reorder[ch]]. */
+static const uint8_t ch_reorder_4[4] = { 0, 2, 1, 3 };
+static const uint8_t ch_reorder_6[6] = { 0, 2, 4, 1, 3, 5 };
+static const uint8_t ch_reorder_8[8] = { 0, 2, 6, 4, 1, 3, 7, 5 };
+static const uint8_t ch_reorder_n[8] = { 0, 2, 4, 6, 1, 3, 5, 7 };
+
+/* fr_code -> nominal frame rate, for AVTimecode; mirrors
+ * fr_code_sample_rate_tab (both ultimately encode the same 5 SMPTE frame
+ * rates). */
+static const AVRational fr_code_rate_tab[6] = {
+    { 0, 0 }, { 24000, 1001 }, { 24, 1 }, { 25, 1 }, { 30000, 1001 }, { 30, 1 }
+};
+
+/* Per-program AC-3-style metadata block written into the metadata segment's
+ * tail (GATE-B). Field layout, order and width cross-checked against two
+ * independent sources that both trace back to SMPTE RDD 6-2006 ("Description
+ * and Guide to the Use of the Dolby E Audio Metadata Serial Bitstream"):
+ * EBU Tech 3285 Supplement 6 (normative for program_id/program_info/
+ * datarate_info/surround_config/dialnorm_info/ac3_langcod/audio_prod_info/
+ * ext_bsi1/ext_bsi2, the first 68 bits) and the bbc/bmx open-source RDD6
+ * implementation (RDD6DolbyDigitalCompleteExtBSI, all 126 bits -- EBU
+ * documents an older/simpler compr/dynrng tail, bmx the fuller later
+ * revision, used here since it is a strict superset). */
+#define AC3_PGM_BITS 126
+
+/* Channel count per program, in program order, for each program_config
+ * (0-padded); mirrors EBU Tech 3285 Supplement 6 Table 5 / SMPTE RDD 6's
+ * Programme Configuration table exactly. Only ever 1, 2, 4, 6 or 8 channels
+ * appear; a 6 or 8 always denotes the "X.1" program, which per that table is
+ * always program 0 -- so LFE presence per program needs no extra table, see
+ * dolby_e_encode_frame(). */
+static const uint8_t prog_ch_counts_tab[MAX_PROG_CONF + 1][8] = {
+    { 6, 2, 0, 0, 0, 0, 0, 0 }, /*  0: 5.1+2 */
+    { 6, 1, 1, 0, 0, 0, 0, 0 }, /*  1: 5.1+1+1 */
+    { 4, 4, 0, 0, 0, 0, 0, 0 }, /*  2: 4+4 */
+    { 4, 2, 2, 0, 0, 0, 0, 0 }, /*  3: 4+2+2 */
+    { 4, 2, 1, 1, 0, 0, 0, 0 }, /*  4: 4+2+1+1 */
+    { 4, 1, 1, 1, 1, 0, 0, 0 }, /*  5: 4+1+1+1+1 */
+    { 2, 2, 2, 2, 0, 0, 0, 0 }, /*  6: 2+2+2+2 */
+    { 2, 2, 2, 1, 1, 0, 0, 0 }, /*  7: 2+2+2+1+1 */
+    { 2, 2, 1, 1, 1, 1, 0, 0 }, /*  8: 2+2+1+1+1+1 */
+    { 2, 1, 1, 1, 1, 1, 1, 0 }, /*  9: 2+1+1+1+1+1+1 */
+    { 1, 1, 1, 1, 1, 1, 1, 1 }, /* 10: 1+1+1+1+1+1+1+1 */
+    { 6, 0, 0, 0, 0, 0, 0, 0 }, /* 11: 5.1 */
+    { 4, 2, 0, 0, 0, 0, 0, 0 }, /* 12: 4+2 */
+    { 4, 1, 1, 0, 0, 0, 0, 0 }, /* 13: 4+1+1 */
+    { 2, 2, 2, 0, 0, 0, 0, 0 }, /* 14: 2+2+2 */
+    { 2, 2, 1, 1, 0, 0, 0, 0 }, /* 15: 2+2+1+1 */
+    { 2, 1, 1, 1, 1, 0, 0, 0 }, /* 16: 2+1+1+1+1 */
+    { 1, 1, 1, 1, 1, 1, 0, 0 }, /* 17: 1+1+1+1+1+1 */
+    { 4, 0, 0, 0, 0, 0, 0, 0 }, /* 18: 4 */
+    { 2, 2, 0, 0, 0, 0, 0, 0 }, /* 19: 2+2 */
+    { 2, 1, 1, 0, 0, 0, 0, 0 }, /* 20: 2+1+1 */
+    { 1, 1, 1, 1, 0, 0, 0, 0 }, /* 21: 1+1+1+1 */
+    { 8, 0, 0, 0, 0, 0, 0, 0 }, /* 22: 7.1 */
+    { 8, 0, 0, 0, 0, 0, 0, 0 }, /* 23: 7.1 Screen */
+};
+
+/* ac3_acmod/ac3_lfeon for a program with pch channels (see prog_ch_counts_tab)
+ * of which has_lfe indicates one is the LFE. AC-3's acmod has no channel
+ * count above 3/2 (5 full-bandwidth channels), so an 8-channel ("7.1")
+ * program is clamped to 3/2 -- the two extra surround channels have no
+ * representation in this field, which is otherwise purely descriptive
+ * downmix metadata and does not affect this encoder's own round-trip. */
+static void ac3_acmod_for_program(int pch, int has_lfe, int *acmod, int *lfeon)
+{
+    *lfeon = has_lfe;
+    switch (pch) {
+    case 1:  *acmod = 1; break; /* 1/0: C */
+    case 2:  *acmod = 2; break; /* 2/0: L, R */
+    case 4:  *acmod = 6; break; /* 2/2: L, R, Ls, Rs (best-effort default) */
+    default: *acmod = 7; break; /* 3/2: L, C, R, Ls, Rs (6 or 8 ch, has_lfe) */
+    }
+}
+
+/* Writes one AC3_PGM_BITS-bit per-program metadata block (dialnorm and
+ * associated AC-3-style fields); see the AC3_PGM_BITS comment above for
+ * sourcing. All fields are unconditionally present regardless of acmod. */
+static void put_ac3_program_metadata(PutBitContext *pb, int program_id,
+                                      int acmod, int lfeon, int dialnorm)
+{
+    put_bits(pb, 5, program_id);
+    put_bits(pb, 5, 31);        /* ac3_datarate: not specified */
+    put_bits(pb, 3, 0);         /* ac3_bsmod: complete main */
+    put_bits(pb, 3, acmod);
+    put_bits(pb, 2, 0);         /* ac3_cmixlev */
+    put_bits(pb, 2, 0);         /* ac3_surmixlev */
+    put_bits(pb, 2, 0);         /* ac3_dsurmod */
+    put_bits(pb, 1, lfeon);
+    put_bits(pb, 5, dialnorm);
+    put_bits(pb, 1, 0);         /* ac3_langcode */
+    put_bits(pb, 8, 0);         /* ac3_langcod */
+    put_bits(pb, 1, 0);         /* ac3_audprodie */
+    put_bits(pb, 5, 0);         /* ac3_mixlevel */
+    put_bits(pb, 2, 0);         /* ac3_roomtyp */
+    put_bits(pb, 1, 0);         /* ac3_copyrightb */
+    put_bits(pb, 1, 1);         /* ac3_origbs: original bitstream */
+    put_bits(pb, 1, 0);         /* ac3_xbsi1e */
+    put_bits(pb, 2, 0);         /* ac3_dmixmod */
+    put_bits(pb, 3, 0);         /* ac3_ltrtcmixlev */
+    put_bits(pb, 3, 0);         /* ac3_ltrtsurmixlev */
+    put_bits(pb, 3, 0);         /* ac3_lorocmixlev */
+    put_bits(pb, 3, 0);         /* ac3_lorosurmixlev */
+    put_bits(pb, 1, 0);         /* ac3_xbsi2e */
+    put_bits(pb, 2, 0);         /* ac3_dsurexmod */
+    put_bits(pb, 2, 0);         /* ac3_dheadphonmod */
+    put_bits(pb, 1, 0);         /* ac3_adconvtyp */
+    put_bits(pb, 8, 0);         /* reserved */
+    put_bits(pb, 1, 0);         /* reserved */
+    put_bits(pb, 1, 0);         /* ac3_hpfon */
+    put_bits(pb, 1, 0);         /* ac3_bwlpfon */
+    put_bits(pb, 1, 0);         /* ac3_lfelpfon */
+    put_bits(pb, 1, 0);         /* ac3_sur90on */
+    put_bits(pb, 1, 0);         /* ac3_suratton */
+    put_bits(pb, 1, 0);         /* ac3_rfpremphon */
+    put_bits(pb, 1, 0);         /* ac3_compre */
+    put_bits(pb, 8, 0);         /* ac3_compr1 */
+    put_bits(pb, 1, 0);         /* ac3_dynrnge */
+    put_bits(pb, 8, 0);         /* ac3_dynrng1 */
+    put_bits(pb, 8, 0);         /* ac3_dynrng2 */
+    put_bits(pb, 8, 0);         /* ac3_dynrng3 */
+    put_bits(pb, 8, 0);         /* ac3_dynrng4 */
+    put_bits(pb, 1, 0);         /* reserved */
+}
+
+typedef struct DBEEncChannel {
+    int present;
+    int is_lfe;
+    int gr_code, bw_code;
+    DBEGroup group;
+
+    int nb_mstr_exp;
+    int mstr_exp[DBE_MAX_MSTR_EXP];
+    int bias_exp[50];
+    int exponents[50];
+
+    int fg_spc, fg_ofs, msk_mod;
+    int snr_ofs;
+    int bap[50];
+
+    int nb_mantissas;
+    int mantissa[1024];
+
+    int tail_bits;
+} DBEEncChannel;
+
+typedef struct DBEEncContext {
+    const AVClass *class;
+
+    int prog_conf;
+    int nb_channels, nb_programs;
+    int lfe_channel;
+    /* coded channel -> AVFrame channel, see ch_reorder_* above */
+    const uint8_t *ch_reorder;
+    int dialogue_level;
+    /* AVOptions: masking-curve shape tuning, see encode_channel() */
+    int fg_spc, fg_ofs, msk_mod;
+
+    int word_bits;   /* AVOption: 16, 20 or 24 */
+    int word_bytes;  /* (word_bits + 7) / 8 */
+    int fr_code;     /* derived from avctx->sample_rate, see
+                       * fr_code_sample_rate_tab */
+    int total_words; /* derived from fr_code, see fr_code_total_words_tab */
+
+    char *start_timecode; /* AVOption: HH:MM:SS[:;]FF, or NULL */
+    AVTimecode tc;
+    int have_tc;
+
+    DBETransform transform;
+    AudioFrameQueue afq;
+
+    float **samples;         /* [nb_channels][BUF_SAMPLES] */
+    int buffered_frames;
+    int64_t frame_count;
+
+    DBEEncChannel enc_ch[2][MAX_CHANNELS]; /* [seg_id][ch] */
+    int ch_size[MAX_CHANNELS];
+
+    uint8_t *pktbuf;
+    int pktbuf_size;
+} DBEEncContext;
+
+/* Writes the 64-bit e_SMPTE_time_code field. Layout per EBU Tech 3285
+ * Supplement 6 Table 7: each of the 4 BCD groups (H/M/S/F) is bit-for-bit
+ * identical to av_timecode_get_smpte()'s own weighted-BCD sub-encoding
+ * (2 tens bits + 4 units bits, verified directly against libavutil/timecode.c);
+ * only the byte-level ARRANGEMENT differs, interleaving each group with a
+ * byte of reserved/user bits that this encoder has no data for and leaves
+ * 0. With no start_timecode configured, the field is left explicitly
+ * invalid via EBU's own documented convention (hours BCD = 0x3F) rather
+ * than a plausible-looking all-zero (which would read as a spurious valid
+ * 00:00:00:00). */
+static void put_smpte_timecode(PutBitContext *pb, DBEEncContext *s)
+{
+    if (s->have_tc) {
+        uint32_t tcsmpte =
+            av_timecode_get_smpte_from_framenum(&s->tc, (int)s->frame_count);
+        unsigned hours_bcd6   = tcsmpte & 0x3F;
+        unsigned minutes_bcd7 = (tcsmpte >> 8)  & 0x7F;
+        unsigned seconds_bcd7 = (tcsmpte >> 16) & 0x7F;
+        unsigned frames_bcd6  = (tcsmpte >> 24) & 0x3F;
+        unsigned drop         = (tcsmpte >> 30) & 1;
+
+        put_bits(pb, 8, 0);                              /* byte0: reserved */
+        put_bits(pb, 2, 0); put_bits(pb, 6, hours_bcd6);  /* byte1 */
+        put_bits(pb, 8, 0);                               /* byte2: reserved */
+        put_bits(pb, 1, 0); put_bits(pb, 7, minutes_bcd7); /* byte3 */
+        put_bits(pb, 8, 0);                               /* byte4: reserved */
+        put_bits(pb, 1, 0); put_bits(pb, 7, seconds_bcd7); /* byte5 */
+        put_bits(pb, 8, 0);                               /* byte6: reserved */
+        put_bits(pb, 1, 0); put_bits(pb, 1, drop);
+        put_bits(pb, 6, frames_bcd6);                      /* byte7 */
+    } else {
+        put_bits(pb, 8, 0);
+        /* hours = 0x3F: explicitly invalid, EBU Tech 3285 Suppl. 6 4.1.3 */
+        put_bits(pb, 8, 0x3F);
+        for (int k = 0; k < 6; k++)
+            put_bits(pb, 8, 0);
+    }
+}
+
+static float dbe_exp_val(int e)
+{
+    int i = e >> 1;
+    float base = 1.0f / (float)(1 << i);
+    return (e & 1) ? base * 0.70710678f : base;
+}
+
+/* Largest e in [0,max_e] with dbe_exp_val(e) >= mag (best-fit block
+ * exponent). */
+static int choose_exponent(float mag, int max_e)
+{
+    int e;
+    if (mag <= 0.0f)
+        return max_e;
+    if (mag >= 1.0f)
+        return 0;
+    for (e = 0; e < max_e; e++)
+        if (dbe_exp_val(e + 1) < mag)
+            break;
+    return e;
+}
+
+static int quantize_mantissa(float x, int size1, float exp_val)
+{
+    int half, v;
+    if (size1 <= 0 || exp_val <= 0.0f)
+        return 0;
+    half = 1 << (size1 - 1);
+    v = (int)lrintf(x * half / exp_val);
+    if (v > half - 1) v = half - 1;
+    if (v < -half)    v = -half;
+    return v;
+}
+
+/* Encodes one channel's one segment (single group: gr_code 0 or 3). */
+static void encode_channel(DBEEncContext *s, DBEEncChannel *c, int seg_id,
+                            const float *signal, long anchor, int budget_bits)
+{
+    const DBEGroup *g = &c->group;
+    int n2 = 1 << (ff_dolby_e_imdct_bits_tab[g->imdct_idx] - 1);
+    float *values = av_malloc(n2 * sizeof(float));
+    float *target = av_malloc(n2 * sizeof(float));
+    int direct_lo = (g->imdct_phs == 0) ? 0 : n2;
+    long base = anchor + g->dst_ofs + (direct_lo - g->src_ofs);
+    int win_base = g->win_ofs + (direct_lo - g->src_ofs);
+    int fixed_bits, avail_mantissa_bits;
+    int lo_snr, hi_snr, best_snr;
+
+    for (int i = 0; i < n2; i++) {
+        long t = base + i;
+        target[i] = (t >= 0) ? signal[t] : 0.0f;
+        target[i] *= ff_dolby_e_window[win_base + i];
+    }
+
+    /* TDAC boundary correction. The fold construction above correctly
+     * reconstructs each group's own n2-sample direct region in isolation
+     * (verified by impulse probing: buffer[direct region] == target,
+     * exactly, for both phs=0 and phs=2). But win_len (1152) exceeds n2
+     * (1024): the outer overlap_half=128 samples on each side of target
+     * feed BOTH this group's own reconstruction AND, via the mirror
+     * extension past n2, the overlap-add with the adjacent, opposite-phase
+     * group's segment. Working through the decoder's actual overlap-add
+     * (dolby_e.c transform(): dst[i] += src[i]*win[i]) for that shared
+     * region shows the phs=0 -> phs=2 direction leaves a genuine
+     * uncancelled cross term -- a rotation by the window's own
+     * Princen-Bradley pair (window[a]^2+window[b]^2=1), not a plain
+     * windowed sample -- while phs=2 -> phs0 already cancels exactly with
+     * the plain formula above. This adds the missing cross term for the
+     * one direction that needs it; overlap_half is derived from the
+     * group's own geometry (window_len - HOP), not hardcoded to one size,
+     * since both channel groups this encoder uses (gr_code 0 and 3) share
+     * the same win_len/HOP relationship. */
+    {
+        int overlap_half = (g->win_len - 896) / 2;
+
+        if (g->imdct_phs == 0) {
+            for (int i = n2 - overlap_half; i < n2; i++) {
+                long mt = anchor + (2 * n2 - 1 - i);
+                float ms = (mt >= 0) ? signal[mt] : 0.0f;
+                target[i] += ms * ff_dolby_e_window[win_base - 896 + i];
+            }
+        } else if (g->imdct_phs == 2) {
+            for (int i = 0; i < overlap_half; i++) {
+                long mt = base - 1 - i;
+                float ms = (mt >= 0) ? signal[mt] : 0.0f;
+                target[i] -= ms * ff_dolby_e_window[win_base - 1 - i];
+            }
+        }
+    }
+
+    ff_dolby_e_mdct_calc_fold(&s->transform, g, values, target);
+    av_free(target);
+
+    /* Exponents: mstr_exp fixed at 0, bias_exp carries the full [0,31]
+     * range directly (see dolby-e-encoder-spec.md Phase 4 notes: this
+     * costs some dynamic range on very quiet subbands but keeps the
+     * mstr_exp-fitting problem out of the first working encoder). */
+    c->nb_mstr_exp = ff_dolby_e_nb_mstr_exp_tab[c->gr_code];
+    for (int i = 0; i < c->nb_mstr_exp; i++)
+        c->mstr_exp[i] = 0;
+
+    {
+        int mnt_idx = 0;
+        for (int j = 0; j < g->nb_exponent; j++) {
+            int count = g->nb_mantissa[j];
+            float mag = 0.0f;
+            for (int k = 0; k < count; k++)
+                mag = FFMAX(mag, fabsf(values[mnt_idx + k]));
+            c->bias_exp[j]   = choose_exponent(mag, 31);
+            c->exponents[j]  = c->bias_exp[j];
+            mnt_idx += count;
+        }
+        c->nb_mantissas = mnt_idx;
+    }
+
+    /* Masking-curve shape knobs (AVOptions, for tuning); snr_ofs is the
+     * binary-searched rate-control knob. */
+    c->fg_spc = s->fg_spc;
+    c->fg_ofs = s->fg_ofs;
+    c->msk_mod = s->msk_mod;
+
+    /* fixed_bits = mstr_exp + bias_exp + fg_spc + fg_ofs + msk_mod +
+     * delta_bit_alloc + snr_ofs + idx_present (+ gr_code + bw_code for
+     * non-LFE channels). */
+    fixed_bits = c->nb_mstr_exp * 2 + g->nb_exponent * 5
+               + 2 + 3 + 1
+               + 1 + 8 + 1;
+    if (!c->is_lfe)
+        fixed_bits += 2 + 3;
+
+    avail_mantissa_bits = budget_bits - fixed_bits;
+
+    lo_snr = 1;
+    hi_snr = 255;
+    best_snr = 0;
+    while (lo_snr <= hi_snr) {
+        int mid = (lo_snr + hi_snr) / 2;
+        int bits;
+
+        ff_dolby_e_bit_allocate(g->nb_exponent, g->imdct_idx, s->fr_code,
+                                 c->exponents, c->bap,
+                                 c->fg_spc, c->fg_ofs, c->msk_mod, mid);
+        bits = 0;
+        for (int j = 0; j < g->nb_exponent; j++)
+            bits += g->nb_mantissa[j] * ff_dolby_e_mantissa_size1[c->bap[j]][0];
+
+        if (bits <= avail_mantissa_bits) {
+            best_snr = mid;
+            lo_snr = mid + 1;
+        } else {
+            hi_snr = mid - 1;
+        }
+    }
+    c->snr_ofs = best_snr;
+    if (c->snr_ofs) {
+        ff_dolby_e_bit_allocate(g->nb_exponent, g->imdct_idx, s->fr_code,
+                                 c->exponents, c->bap,
+                                 c->fg_spc, c->fg_ofs, c->msk_mod, c->snr_ofs);
+    } else {
+        memset(c->bap, 0, sizeof(c->bap));
+    }
+
+    {
+        int mnt_idx = 0;
+        for (int j = 0; j < g->nb_exponent; j++) {
+            int size1 = ff_dolby_e_mantissa_size1[c->bap[j]][0];
+            float exp_val = dbe_exp_val(c->exponents[j]);
+            for (int k = 0; k < g->nb_mantissa[j]; k++, mnt_idx++)
+                c->mantissa[mnt_idx] =
+                    quantize_mantissa(values[mnt_idx], size1, exp_val);
+        }
+    }
+
+    av_free(values);
+}
+
+static void serialize_channel(PutBitContext *pb, const DBEEncChannel *c)
+{
+    const DBEGroup *g = &c->group;
+
+    if (!c->is_lfe) {
+        put_bits(pb, 2, c->gr_code);
+        put_bits(pb, 3, c->bw_code);
+    }
+
+    for (int i = 0; i < c->nb_mstr_exp; i++)
+        put_bits(pb, 2, c->mstr_exp[i]);
+    for (int j = 0; j < g->nb_exponent; j++)
+        put_bits(pb, 5, c->bias_exp[j]);
+
+    put_bits(pb, 2, c->fg_spc);
+    put_bits(pb, 3, c->fg_ofs);
+    put_bits(pb, 1, c->msk_mod);
+
+    put_bits(pb, 1, 0); /* delta_bit_alloc: always 0 */
+    put_bits(pb, 8, c->snr_ofs);
+
+    put_bits(pb, 1, 0); /* idx_present: always 0 -> decoder defaults idx[]=0 */
+
+    {
+        int mnt_idx = 0;
+        for (int j = 0; j < g->nb_exponent; j++) {
+            int size1 = ff_dolby_e_mantissa_size1[c->bap[j]][0];
+            if (!size1) {
+                mnt_idx += g->nb_mantissa[j];
+                continue;
+            }
+            for (int k = 0; k < g->nb_mantissa[j]; k++, mnt_idx++)
+                put_sbits(pb, size1, c->mantissa[mnt_idx]);
+        }
+    }
+}
+
+/* Converts a tightly-packed (word_bits-per-word) flat buffer into the
+ * on-wire word container: 16-bit words fill their 2-byte container exactly;
+ * 20- and 24-bit words are carried in a 3-byte container, left-justified
+ * (matching ff_dolby_e_parse_header()'s own sync-word masks -- e.g. the
+ * 20-bit sync 07 88 e0 has its low nibble free for the key, so the 20-bit
+ * payload is shifted up by 24-20=4 bits; 24-bit words fill the container
+ * exactly, shift 0). */
+static void pack_words(uint8_t *dst, const uint8_t *flat_buf, int flat_size,
+                        int nb_words, int word_bits)
+{
+    GetBitContext gb;
+    int word_bytes = (word_bits + 7) / 8;
+    init_get_bits8(&gb, flat_buf, flat_size);
+
+    for (int i = 0; i < nb_words; i++) {
+        unsigned w = get_bits_long(&gb, word_bits);
+        if (word_bits == 16)
+            AV_WB16(dst, w);
+        else
+            AV_WB24(dst, w << (24 - word_bits));
+        dst += word_bytes;
+    }
+}
+
+static int dolby_e_encode_frame(AVCodecContext *avctx, AVPacket *avpkt,
+                                 const AVFrame *frame, int *got_packet_ptr)
+{
+    DBEEncContext *s = avctx->priv_data;
+    int i = s->nb_channels / 2, j = s->nb_channels;
+    int mtd_size, mtd_known_bits, tail_bits, ac3_meta_bits;
+    int mtd_ext_size, meter_size;
+    int overhead_words, ch_budget_words, lfe_budget_words, nonlfe_budget_words;
+    int nb_lfe = (s->lfe_channel >= 0) ? 1 : 0;
+    int ret;
+    uint8_t *dst;
+    long frame_anchor;
+
+    if (frame) {
+        for (int ch = 0; ch < s->nb_channels; ch++) {
+            memmove(s->samples[ch], s->samples[ch] + FRAME_SAMPLES,
+                    FRAME_SAMPLES * sizeof(float));
+            memcpy(s->samples[ch] + FRAME_SAMPLES,
+                   frame->extended_data[s->ch_reorder[ch]],
+                   frame->nb_samples * sizeof(float));
+        }
+        if ((ret = ff_af_queue_add(&s->afq, frame)) < 0)
+            return ret;
+        s->buffered_frames++;
+    } else {
+        if (!s->afq.remaining_samples) {
+            *got_packet_ptr = 0;
+            return 0;
+        }
+        for (int ch = 0; ch < s->nb_channels; ch++) {
+            memmove(s->samples[ch], s->samples[ch] + FRAME_SAMPLES,
+                    FRAME_SAMPLES * sizeof(float));
+            memset(s->samples[ch] + FRAME_SAMPLES, 0,
+                   FRAME_SAMPLES * sizeof(float));
+        }
+    }
+
+    if (s->buffered_frames < 2 && frame) {
+        *got_packet_ptr = 0;
+        return 0;
+    }
+
+    /* Metadata segment size and word-budget bookkeeping. The total packet
+     * must consume the FULL total_words burst envelope exactly: the s337m
+     * demuxer's data_size/word_bits lookup (libavformat/s337m.c) only
+     * recognizes a handful of exact values (3648 for 25 fps) and rejects
+     * anything else with AVERROR_PATCHWELCOME -- there is no such thing as
+     * a "slightly short" Dolby E frame here, unlike most codecs' packets. */
+    mtd_known_bits = 132 + 35 * s->nb_channels + 10 * s->nb_programs;
+    ac3_meta_bits = AC3_PGM_BITS * s->nb_programs;
+    mtd_size = (mtd_known_bits + ac3_meta_bits + s->word_bits +
+                s->word_bits - 1) / s->word_bits;
+
+    mtd_ext_size = 2 * s->nb_programs + 1;
+    meter_size   = s->nb_channels;
+
+    /* sync(1) + mtd_size + mtd's own trailing gap(1) + four parse_audio
+     * trailing gaps(4) + mtd_ext blob(mtd_ext_size+1) + meter
+     * blob(meter_size+1). */
+    overhead_words = 1 + mtd_size + 1 + 4 + (mtd_ext_size + 1) +
+                      (meter_size + 1);
+    if ((s->total_words - overhead_words) & 1) {
+        /* ch_size is spent identically in both segments, so the remaining
+         * budget must be even; absorb the odd word as metadata padding. */
+        mtd_size++;
+        overhead_words++;
+    }
+    tail_bits = mtd_size * s->word_bits - mtd_known_bits - s->word_bits;
+    av_assert0(tail_bits >= 0 && tail_bits < MAX_TAIL_BITS);
+
+    ch_budget_words = (s->total_words - overhead_words) / 2;
+
+    {
+        int nb_nonlfe = s->nb_channels - nb_lfe;
+        int nonlfe_total, nonlfe_remainder, first_nonlfe = -1;
+
+        lfe_budget_words = nb_lfe ? FFMIN(40, ch_budget_words) : 0;
+        nonlfe_total = ch_budget_words - nb_lfe * lfe_budget_words;
+        nonlfe_budget_words = nb_nonlfe > 0 ? nonlfe_total / nb_nonlfe : 0;
+        nonlfe_remainder     = nb_nonlfe > 0 ? nonlfe_total % nb_nonlfe : 0;
+
+        for (int ch = 0; ch < s->nb_channels; ch++) {
+            if (ch == s->lfe_channel) {
+                s->ch_size[ch] = lfe_budget_words;
+            } else {
+                s->ch_size[ch] = nonlfe_budget_words;
+                if (first_nonlfe < 0)
+                    first_nonlfe = ch;
+            }
+        }
+        if (first_nonlfe >= 0)
+            s->ch_size[first_nonlfe] += nonlfe_remainder;
+    }
+
+    /* frame_anchor: absolute sample index of THIS frame's start within the
+     * per-channel sliding buffer (segment 0 always starts at buffer[0]). */
+    frame_anchor = 0;
+
+    for (int ch = 0; ch < s->nb_channels; ch++) {
+        for (int seg_id = 0; seg_id < 2; seg_id++) {
+            DBEEncChannel *c = &s->enc_ch[seg_id][ch];
+            long anchor = frame_anchor + seg_id * 896;
+
+            memset(c, 0, sizeof(*c));
+            c->present = 1;
+            c->is_lfe  = (ch == s->lfe_channel);
+            c->gr_code = c->is_lfe ? 3 : 0;
+            c->bw_code = c->is_lfe ? 29 : 0;
+            c->group   = ff_dolby_e_frm_ofs_tab[seg_id][c->gr_code][0];
+
+            encode_channel(s, c, seg_id, s->samples[ch], anchor,
+                           s->ch_size[ch] * s->word_bits);
+        }
+    }
+
+    ret = ff_get_encode_buffer(avctx, avpkt,
+                                s->total_words * s->word_bytes, 0);
+    if (ret < 0)
+        return ret;
+    dst = avpkt->data;
+
+    /* --- metadata segment --- */
+    /* Sync word per word_bits, taken verbatim from ff_dolby_e_parse_header()'s
+     * own detection masks (dolby_e_parse.c): 16-bit=07 8e, 20-bit=07 88 e0,
+     * 24-bit=07 88 8e; key_present is the low bit of the last byte,
+     * always 0. */
+    if (s->word_bits == 16) {
+        dst[0] = 0x07; dst[1] = 0x8e;
+    } else if (s->word_bits == 20) {
+        dst[0] = 0x07; dst[1] = 0x88; dst[2] = 0xe0;
+    } else {
+        dst[0] = 0x07; dst[1] = 0x88; dst[2] = 0x8e;
+    }
+    dst += s->word_bytes;
+    {
+        int flat_bytes = (mtd_size * s->word_bits + 7) / 8 +
+                         AV_INPUT_BUFFER_PADDING_SIZE;
+        uint8_t *flat = av_malloc(flat_bytes);
+        PutBitContext pb;
+
+        init_put_bits(&pb, flat, flat_bytes);
+        put_bits(&pb, 4, 1);           /* metadata_revision_id */
+        put_bits(&pb, 10, mtd_size);
+        put_bits(&pb, 6, s->prog_conf);
+        put_bits(&pb, 4, s->fr_code);
+        put_bits(&pb, 4, s->fr_code);   /* fr_code_orig */
+
+        put_bits(&pb, 16, (int)(s->frame_count & 0xffff));
+        put_smpte_timecode(&pb, s);
+        put_bits(&pb, 1, 0);           /* evolution_data_exists: always 0 */
+        put_bits(&pb, 7, 0);           /* metadata_reserved_bits */
+
+        for (int ch = 0; ch < s->nb_channels; ch++)
+            put_bits(&pb, 10, s->ch_size[ch]);
+        put_bits(&pb, 8, mtd_ext_size);
+        put_bits(&pb, 8, meter_size);
+
+        for (int p = 0; p < s->nb_programs; p++) {
+            put_bits(&pb, 8, 0); /* description_text */
+            put_bits(&pb, 2, 0); /* bandwidth_id */
+        }
+        for (int ch = 0; ch < s->nb_channels; ch++) {
+            put_bits(&pb, 4, 1);    /* rev_id */
+            put_bits(&pb, 1, 0);    /* reserved */
+            put_bits(&pb, 10, 960); /* begin_gain: unity */
+            put_bits(&pb, 10, 960); /* end_gain: unity */
+        }
+        /* GATE-B: per-program AC-3-style metadata (dialnorm et al.), see
+         * AC3_PGM_BITS above. LFE, per EBU Tech 3285 Suppl. 6 Table 5 / RDD 6,
+         * is always carried by program 0 when present at all. */
+        for (int p = 0; p < s->nb_programs; p++) {
+            int pch = prog_ch_counts_tab[s->prog_conf][p];
+            int has_lfe = (p == 0) && (s->lfe_channel >= 0);
+            int acmod, lfeon;
+
+            ac3_acmod_for_program(pch, has_lfe, &acmod, &lfeon);
+            put_ac3_program_metadata(&pb, p, acmod, lfeon, -s->dialogue_level);
+        }
+        for (int k = 0; k < tail_bits - ac3_meta_bits; k++)
+            put_bits(&pb, 1, 0);
+        /* metadata_crc: 0, matches every measured real sample */
+        put_bits(&pb, s->word_bits, 0);
+
+        flush_put_bits(&pb);
+        pack_words(dst, flat, flat_bytes, mtd_size, s->word_bits);
+        av_free(flat);
+    }
+    dst += mtd_size * s->word_bytes;
+    memset(dst, 0, s->word_bytes); /* metadata's own trailing gap word */
+    dst += s->word_bytes;
+
+    /* --- audio segments (seg 0 halves, ext, seg 1 halves, meter) --- */
+    for (int seg_id = 0; seg_id < 2; seg_id++) {
+        int ranges[2][2] = { { 0, i }, { i, j } };
+
+        for (int r = 0; r < 2; r++) {
+            for (int ch = ranges[r][0]; ch < ranges[r][1]; ch++) {
+                DBEEncChannel *c = &s->enc_ch[seg_id][ch];
+                int words = s->ch_size[ch];
+                int flat_bytes = (words * s->word_bits + 7) / 8 +
+                                 AV_INPUT_BUFFER_PADDING_SIZE;
+                uint8_t *flat = av_malloc(flat_bytes);
+                PutBitContext pb;
+
+                init_put_bits(&pb, flat, flat_bytes);
+                serialize_channel(&pb, c);
+                c->tail_bits = words * s->word_bits - put_bits_count(&pb);
+                av_assert0(c->tail_bits >= 0 && c->tail_bits < MAX_TAIL_BITS);
+                for (int k = 0; k < c->tail_bits; k++)
+                    put_bits(&pb, 1, 0);
+                flush_put_bits(&pb);
+
+                pack_words(dst, flat, flat_bytes, words, s->word_bits);
+                av_free(flat);
+                dst += words * s->word_bytes;
+            }
+            /* trailing gap word per parse_audio() call */
+            memset(dst, 0, s->word_bytes);
+            dst += s->word_bytes;
+        }
+
+        if (seg_id == 0) {
+            memset(dst, 0, (mtd_ext_size + 1) * s->word_bytes);
+            dst += (mtd_ext_size + 1) * s->word_bytes;
+        }
+    }
+    memset(dst, 0, (meter_size + 1) * s->word_bytes);
+    dst += (meter_size + 1) * s->word_bytes;
+
+    /* Must land exactly on total_words: the s337m demuxer's data_size
+     * lookup requires it (see the word-budget comment above). */
+    av_assert0(dst - avpkt->data == s->total_words * s->word_bytes);
+
+    ff_af_queue_remove(&s->afq, FRAME_SAMPLES, &avpkt->pts, &avpkt->duration);
+    s->frame_count++;
+    *got_packet_ptr = 1;
+    return 0;
+}
+
+static av_cold int dolby_e_encode_init(AVCodecContext *avctx)
+{
+    DBEEncContext *s = avctx->priv_data;
+    int ret;
+
+    s->fr_code = 0;
+    for (int fc = 1; fc <= 5; fc++) {
+        if (fr_code_sample_rate_tab[fc] == avctx->sample_rate) {
+            s->fr_code = fc;
+            break;
+        }
+    }
+    if (!s->fr_code) {
+        av_log(avctx, AV_LOG_ERROR,
+               "Unsupported sample_rate %d Hz. Dolby E is locked to one of "
+               "the 5 SMPTE-defined frame rates via this synthetic "
+               "sample_rate convention (23.98/24/25/29.97/30 fps -> "
+               "%d/%d/%d/%d/%d Hz).\n", avctx->sample_rate,
+               fr_code_sample_rate_tab[1], fr_code_sample_rate_tab[2],
+               fr_code_sample_rate_tab[3], fr_code_sample_rate_tab[4],
+               fr_code_sample_rate_tab[5]);
+        return AVERROR(EINVAL);
+    }
+    s->total_words = fr_code_total_words_tab[s->fr_code];
+
+    if (s->word_bits != 16 && s->word_bits != 20 && s->word_bits != 24) {
+        av_log(avctx, AV_LOG_ERROR,
+               "Invalid word_bits %d (must be 16, 20 or 24)\n", s->word_bits);
+        return AVERROR(EINVAL);
+    }
+    s->word_bytes = (s->word_bits + 7) / 8;
+
+    if (s->start_timecode) {
+        int ret2 = av_timecode_init_from_string(&s->tc,
+                                                 fr_code_rate_tab[s->fr_code],
+                                                 s->start_timecode, avctx);
+        if (ret2 < 0)
+            return ret2;
+        s->have_tc = 1;
+    }
+
+    if (s->prog_conf < 0 || s->prog_conf > MAX_PROG_CONF) {
+        av_log(avctx, AV_LOG_ERROR, "Invalid program_config %d (0-%d)\n",
+               s->prog_conf, MAX_PROG_CONF);
+        return AVERROR(EINVAL);
+    }
+
+    {
+        static const uint8_t nb_channels_tab[MAX_PROG_CONF + 1] = {
+            8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 6,
+            6, 6, 6, 6, 6, 6, 4, 4, 4, 4, 8, 8
+        };
+        static const uint8_t nb_programs_tab[MAX_PROG_CONF + 1] = {
+            2, 3, 2, 3, 4, 5, 4, 5, 6, 7, 8, 1,
+            2, 3, 3, 4, 5, 6, 1, 2, 3, 4, 1, 1
+        };
+        s->nb_channels = nb_channels_tab[s->prog_conf];
+        s->nb_programs = nb_programs_tab[s->prog_conf];
+        s->lfe_channel = ff_dolby_e_lfe_channel_tab[s->prog_conf];
+
+        /* Mirrors dolby_e.c's filter_frame() channel-reorder selection
+         * (minus output_channel_order, a decode-only option -- the encoder
+         * always assumes the decoder's default/friendly output order as its
+         * input convention, since that's what a real pipeline decoding then
+         * re-encoding Dolby E would actually hand it). */
+        if (s->nb_channels == 4)
+            s->ch_reorder = ch_reorder_4;
+        else if (s->nb_channels == 6)
+            s->ch_reorder = ch_reorder_6;
+        else if (s->nb_programs == 1)
+            s->ch_reorder = ch_reorder_8;
+        else
+            s->ch_reorder = ch_reorder_n;
+    }
+
+    if (avctx->ch_layout.nb_channels != s->nb_channels) {
+        av_log(avctx, AV_LOG_ERROR,
+               "program_config %d requires %d channels, got %d\n",
+               s->prog_conf, s->nb_channels, avctx->ch_layout.nb_channels);
+        return AVERROR(EINVAL);
+    }
+
+    if ((ret = ff_dolby_e_transform_init(&s->transform)) < 0)
+        return ret;
+
+    s->samples = av_calloc(s->nb_channels, sizeof(*s->samples));
+    if (!s->samples)
+        return AVERROR(ENOMEM);
+    for (int ch = 0; ch < s->nb_channels; ch++) {
+        s->samples[ch] = av_calloc(BUF_SAMPLES, sizeof(float));
+        if (!s->samples[ch])
+            return AVERROR(ENOMEM);
+    }
+
+    avctx->frame_size = FRAME_SAMPLES;
+    avctx->initial_padding = FRAME_SAMPLES;
+    ff_af_queue_init(avctx, &s->afq);
+
+    return 0;
+}
+
+static av_cold int dolby_e_encode_close(AVCodecContext *avctx)
+{
+    DBEEncContext *s = avctx->priv_data;
+
+    ff_dolby_e_transform_uninit(&s->transform);
+    ff_af_queue_close(&s->afq);
+    if (s->samples) {
+        for (int ch = 0; ch < s->nb_channels; ch++)
+            av_freep(&s->samples[ch]);
+        av_freep(&s->samples);
+    }
+    return 0;
+}
+
+#define OFFSET(x) offsetof(DBEEncContext, x)
+#define FLAGS (AV_OPT_FLAG_ENCODING_PARAM | AV_OPT_FLAG_AUDIO_PARAM)
+static const AVOption options[] = {
+    { "program_config", "Program configuration (see dolby_e.h prog_conf table)",
+        OFFSET(prog_conf), AV_OPT_TYPE_INT,
+        { .i64 = 11 }, 0, MAX_PROG_CONF, FLAGS },
+    { "dialnorm", "Dialogue Level (dB)",
+        OFFSET(dialogue_level), AV_OPT_TYPE_INT,
+        { .i64 = -31 }, -31, -1, FLAGS },
+    { "word_bits", "SMPTE 337M word size in bits (16, 20 or 24)",
+        OFFSET(word_bits), AV_OPT_TYPE_INT, { .i64 = 20 }, 16, 24, FLAGS },
+    { "start_timecode",
+        "Starting SMPTE timecode (hh:mm:ss:ff, or hh:mm:ss;ff for "
+        "drop-frame); auto-increments once per encoded frame. Unset "
+        "leaves the field explicitly marked invalid.",
+        OFFSET(start_timecode), AV_OPT_TYPE_STRING,
+        { .str = NULL }, 0, 0, FLAGS },
+    { "fg_spc",
+        "Fast gain spectral-extension band offset (masking-curve tuning)",
+        OFFSET(fg_spc), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 3, FLAGS },
+    { "fg_ofs", "Fast gain offset (masking-curve tuning)",
+        OFFSET(fg_ofs), AV_OPT_TYPE_INT, { .i64 = 4 }, 0, 7, FLAGS },
+    { "msk_mod", "Masking model select (masking-curve tuning)",
+        OFFSET(msk_mod), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, FLAGS },
+    { NULL },
+};
+
+static const AVClass dolby_e_encoder_class = {
+    .class_name = "Dolby E encoder",
+    .item_name  = av_default_item_name,
+    .option     = options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
+const FFCodec ff_dolby_e_encoder = {
+    .p.name         = "dolby_e",
+    CODEC_LONG_NAME("Dolby E"),
+    .p.type         = AVMEDIA_TYPE_AUDIO,
+    .p.id           = AV_CODEC_ID_DOLBY_E,
+    .p.capabilities = AV_CODEC_CAP_DELAY,
+    .p.priv_class   = &dolby_e_encoder_class,
+    CODEC_SAMPLEFMTS(AV_SAMPLE_FMT_FLTP),
+    .priv_data_size = sizeof(DBEEncContext),
+    .init           = dolby_e_encode_init,
+    .close          = dolby_e_encode_close,
+    FF_CODEC_ENCODE_CB(dolby_e_encode_frame),
+};
-- 
2.52.0


From ec573dc0e527a758a9eec6c8697306ce0ee212b0 Mon Sep 17 00:00:00 2001
From: Oren Jack Maurice <[email protected]>
Date: Sun, 9 Aug 2026 12:03:02 +0300
Subject: [PATCH 174/180] avformat/s337m: add muxer

Write-side counterpart to the existing s337m demuxer: wraps a raw
Dolby E frame (as produced by the new dolby_e encoder) in SMPTE 337M
non-PCM framing.

Burst padding is sized against this demuxer's own fixed frame-rate ->
offset table (s337m_get_offset_and_codec()) rather than any broadcast
spec value, so that encode -> demux round-trips frame-accurately within
FFmpeg itself; real Dolby E hardware/software output is not guaranteed
to demux frame-accurately here for the same reason (this demuxer always
reads a fixed offset per frame rate regardless of how much of it is
real coded data, and third-party encoders may pack bursts back-to-back
with no such padding).

Development-method: AI-assisted (Claude Code); reviewed by the submitter
before posting.

Signed-off-by: Oren Jack Maurice <[email protected]>
---
 doc/muxers.texi          |  16 ++++
 libavformat/Makefile     |   1 +
 libavformat/allformats.c |   1 +
 libavformat/s337menc.c   | 196 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 214 insertions(+)
 create mode 100644 libavformat/s337menc.c

diff --git a/doc/muxers.texi b/doc/muxers.texi
index 03f0bcd24d..ba44d4c81a 100644
--- a/doc/muxers.texi
+++ b/doc/muxers.texi
@@ -3336,6 +3336,22 @@ ffmpeg -f lavfi -i "movie=INPUT.mkv[out+subcc]" -map 0:s:0 -c:s copy -f rcwt CC.
 @end example
 @end itemize
 
+@anchor{s337m}
+@section s337m
+
+SMPTE 337M muxer.
+
+Wraps a non-PCM audio bitstream (currently Dolby E only) in the SMPTE
+337M framing used to carry compressed audio over an otherwise-PCM AES3
+link. This is the write side of the @code{s337m} demuxer.
+
+Padding within each burst is sized to match this demuxer's own
+frame-rate-keyed offset table exactly, so a @code{dolby_e} stream muxed
+and then demuxed by FFmpeg itself round-trips frame-accurately. This is
+not necessarily the same padding real Dolby E hardware/software
+produces; third-party-encoded SMPTE 337M streams are not guaranteed to
+demux frame-accurately through this same demuxer for this reason.
+
 @anchor{segment}
 @section segment, stream_segment, ssegment
 
diff --git a/libavformat/Makefile b/libavformat/Makefile
index 0d2e757b27..8f9846165a 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -550,6 +550,7 @@ OBJS-$(CONFIG_RTP_MUXER)                 += rtp.o         \
 OBJS-$(CONFIG_RTSP_DEMUXER)              += rtsp.o rtspdec.o httpauth.o
 OBJS-$(CONFIG_RTSP_MUXER)                += rtsp.o rtspenc.o httpauth.o
 OBJS-$(CONFIG_S337M_DEMUXER)             += s337m.o spdif.o
+OBJS-$(CONFIG_S337M_MUXER)               += s337menc.o
 OBJS-$(CONFIG_SAMI_DEMUXER)              += samidec.o subtitles.o
 OBJS-$(CONFIG_SAP_DEMUXER)               += sapdec.o
 OBJS-$(CONFIG_SAP_MUXER)                 += sapenc.o
diff --git a/libavformat/allformats.c b/libavformat/allformats.c
index af7eea5e5c..61e1686212 100644
--- a/libavformat/allformats.c
+++ b/libavformat/allformats.c
@@ -418,6 +418,7 @@ extern const FFOutputFormat ff_rtp_mpegts_muxer;
 extern const FFInputFormat  ff_rtsp_demuxer;
 extern const FFOutputFormat ff_rtsp_muxer;
 extern const FFInputFormat  ff_s337m_demuxer;
+extern const FFOutputFormat ff_s337m_muxer;
 extern const FFInputFormat  ff_sami_demuxer;
 extern const FFInputFormat  ff_sap_demuxer;
 extern const FFOutputFormat ff_sap_muxer;
diff --git a/libavformat/s337menc.c b/libavformat/s337menc.c
new file mode 100644
index 0000000000..fbaa7fd135
--- /dev/null
+++ b/libavformat/s337menc.c
@@ -0,0 +1,196 @@
+/*
+ * SMPTE 337M muxer
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * Wraps raw Dolby E frames (as produced by libavcodec's dolby_e encoder)
+ * in SMPTE 337M non-PCM bitstream framing, inverting
+ * libavformat/s337m.c's s337m_get_offset_and_codec().
+ *
+ * GATE-D (guard band length) is resolved here specifically for
+ * round-tripping through FFmpeg's OWN s337m demuxer (dolby-e-encoder-spec.md
+ * Gate 5's actual bar), not as a claim about the broadcast-standard guard
+ * band value: s337m_read_packet() always calls av_get_packet(pb, pkt,
+ * offset) for a FIXED `offset` derived purely from the frame-rate case
+ * table, regardless of how many of those bytes are real coded data. If a
+ * burst is shorter than that, the demuxer's next marker search starts
+ * partway into what should have been the NEXT burst's header, and frames
+ * get silently dropped (confirmed empirically against Dolby's own sample
+ * files, which are back-to-back with no gap and do NOT round-trip cleanly
+ * through this demuxer for exactly this reason). Padding each burst with
+ * silence/zero words up to the demuxer's assumed `offset` is what makes
+ * extraction frame-accurate.
+ */
+
+#include "libavutil/intreadwrite.h"
+#include "libavutil/macros.h"
+#include "libavutil/mem.h"
+
+#include "avformat.h"
+#include "mux.h"
+
+/* word_bits -> {Pa,Pb} byte sequences, matching s337m.c's MARKER_*LE
+ * constants exactly (verified against both demuxer probing and real
+ * Dolby-authored sample files). */
+static const uint8_t sync16[4] = { 0x72, 0xF8, 0x1F, 0x4E };
+static const uint8_t sync20[6] = { 0x20, 0x87, 0x6F, 0xF0, 0xE1, 0x54 };
+static const uint8_t sync24[6] = { 0x72, 0xF8, 0x96, 0x1F, 0x4E, 0xA5 };
+
+/* Burst payload word count the existing demuxer's av_get_packet() call
+ * will actually consume, for each recognized (word_bits, coded nwords)
+ * pair -- i.e. the inverse of s337m_get_offset_and_codec()'s switch table,
+ * expressed directly in payload words instead of bytes. */
+static int burst_payload_words(int word_bits, int nwords)
+{
+    static const struct { int nwords, samples; } tab[] = {
+        { 3648, 1920 }, { 3644, 2002 }, { 3640, 2000 }, { 3040, 1601 },
+    };
+    int word_bytes = (word_bits + 7) / 8;
+
+    for (int i = 0; i < FF_ARRAY_ELEMS(tab); i++) {
+        if (tab[i].nwords == nwords) {
+            int offset_bytes = (tab[i].samples - 4) * word_bytes * 2;
+            return offset_bytes / word_bytes;
+        }
+    }
+    return -1;
+}
+
+static int s337m_write_packet(AVFormatContext *s, AVPacket *pkt)
+{
+    uint32_t hdr;
+    int word_bits, word_bytes, nwords, payload_words, guard_words;
+    const uint8_t *sync;
+    int sync_bytes;
+    uint8_t *buf;
+
+    if (pkt->size < 3)
+        return AVERROR_INVALIDDATA;
+
+    hdr = AV_RB24(pkt->data);
+    if ((hdr & 0xfffffe) == 0x7888e) {
+        word_bits = 24;
+    } else if ((hdr & 0xffffe0) == 0x788e0) {
+        word_bits = 20;
+    } else if ((hdr & 0xfffe00) == 0x78e00) {
+        word_bits = 16;
+    } else {
+        av_log(s, AV_LOG_ERROR, "Invalid Dolby E frame header\n");
+        return AVERROR_INVALIDDATA;
+    }
+    word_bytes = (word_bits + 7) / 8;
+
+    if (pkt->size % word_bytes) {
+        av_log(s, AV_LOG_ERROR, "Packet size %d not a multiple of word size %d\n",
+               pkt->size, word_bytes);
+        return AVERROR_INVALIDDATA;
+    }
+    nwords = pkt->size / word_bytes;
+
+    payload_words = burst_payload_words(word_bits, nwords);
+    if (payload_words < 0) {
+        avpriv_report_missing_feature(s, "Dolby E frame word count %d at %d-bit", nwords, word_bits);
+        return AVERROR_PATCHWELCOME;
+    }
+    guard_words = payload_words - nwords;
+    if (guard_words < 0) {
+        av_log(s, AV_LOG_ERROR, "Dolby E frame (%d words) exceeds its own burst envelope (%d words)\n",
+               nwords, payload_words);
+        return AVERROR_INVALIDDATA;
+    }
+
+    switch (word_bits) {
+    case 16: sync = sync16; sync_bytes = 4; break;
+    case 20: sync = sync20; sync_bytes = 6; break;
+    default:  sync = sync24; sync_bytes = 6; break;
+    }
+    avio_write(s->pb, sync, sync_bytes);
+
+    /* Pc (data_type) / Pd (data_size, in bits): fixed patterns matching
+     * real Dolby-authored samples, byte-for-byte (dolby-e-encoder-spec.md
+     * 6.2). Pd encodes nwords*word_bits, left-shifted for 20-bit's >>4
+     * convention (mirrors s337m_get_offset_and_codec() exactly). */
+    {
+        int64_t data_size_bits = (int64_t)nwords * word_bits;
+        uint8_t pc[3], pd[3];
+
+        switch (word_bits) {
+        case 16:
+            pc[0] = 0x1C; pc[1] = 0x00;
+            AV_WL16(pd, (uint16_t)data_size_bits);
+            avio_write(s->pb, pc, 2);
+            avio_write(s->pb, pd, 2);
+            break;
+        case 20:
+            pc[0] = 0x00; pc[1] = 0x3C; pc[2] = 0x00;
+            AV_WL24(pd, (uint32_t)(data_size_bits << 4));
+            avio_write(s->pb, pc, 3);
+            avio_write(s->pb, pd, 3);
+            break;
+        default: /* 24: demuxer applies data_type>>=8 same as the 20-bit case */
+            pc[0] = 0x00; pc[1] = 0x3C; pc[2] = 0x00;
+            AV_WL24(pd, (uint32_t)data_size_bits);
+            avio_write(s->pb, pc, 3);
+            avio_write(s->pb, pd, 3);
+            break;
+        }
+    }
+
+    /* Payload: byte-swap each word from the decoder-native big-endian
+     * packing (what libavcodec/dolby_e_enc.c emits, matching what
+     * ff_dolby_e_parse_header() expects) to the little-endian container
+     * s337m carries on the wire -- the exact inverse of s337m.c's
+     * bswap_buf24()/ff_spdif_bswap_buf16(). */
+    buf = av_malloc(pkt->size);
+    if (!buf)
+        return AVERROR(ENOMEM);
+    if (word_bits == 16) {
+        for (int i = 0; i < nwords; i++)
+            AV_WL16(buf + i * 2, AV_RB16(pkt->data + i * 2));
+    } else {
+        for (int i = 0; i < nwords; i++) {
+            const uint8_t *src = pkt->data + i * 3;
+            uint8_t *dst = buf + i * 3;
+            dst[0] = src[2]; dst[1] = src[1]; dst[2] = src[0];
+        }
+    }
+    avio_write(s->pb, buf, pkt->size);
+    av_free(buf);
+
+    /* Guard band: zero-filled words up to the demuxer's assumed burst
+     * envelope (see the file-level comment above). */
+    if (guard_words > 0) {
+        int guard_bytes = guard_words * word_bytes;
+        uint8_t *zero = av_mallocz(guard_bytes);
+        if (!zero)
+            return AVERROR(ENOMEM);
+        avio_write(s->pb, zero, guard_bytes);
+        av_free(zero);
+    }
+
+    return 0;
+}
+
+const FFOutputFormat ff_s337m_muxer = {
+    .p.name         = "s337m",
+    .p.long_name    = NULL_IF_CONFIG_SMALL("SMPTE 337M"),
+    .p.audio_codec  = AV_CODEC_ID_DOLBY_E,
+    .p.flags        = AVFMT_NOTIMESTAMPS,
+    .write_packet   = s337m_write_packet,
+};
-- 
2.52.0


From 693bc11dcdfb2539368b56a432e2dfd5153f80ca Mon Sep 17 00:00:00 2001
From: Oren Jack Maurice <[email protected]>
Date: Sun, 9 Aug 2026 12:03:19 +0300
Subject: [PATCH 175/180] avformat/spdif: support Dolby E (data type 0x1C)

Add IEC61937_DOLBY_E and wire up both directions:

 - spdifenc.c: header_info callback for AV_CODEC_ID_DOLBY_E, mirroring
   the existing AC-3/DTS/AAC callbacks but with a frame-rate-dependent
   burst period (from the coded word count) instead of a fixed
   per-format offset, matching s337m.c's own envelope table.
 - spdifdec.c: matching probe/read-side offset lookup, handling both
   byte orders the two call sites present it in (native-order for real
   packet reads after ff_spdif_bswap_buf16(), still file-byte-order for
   the raw probe path).

Only 16-bit-word Dolby E frames are handled: IEC 61937's 16-bit-word
byte swap has no meaning for 20/24-bit AES3 words, which is not a
carriage this transport supports.

Development-method: AI-assisted (Claude Code); reviewed by the submitter
before posting.

Signed-off-by: Oren Jack Maurice <[email protected]>
---
 libavformat/spdif.h    |  1 +
 libavformat/spdifdec.c | 43 ++++++++++++++++++++++++++++++++++++++++++
 libavformat/spdifenc.c | 39 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 83 insertions(+)

diff --git a/libavformat/spdif.h b/libavformat/spdif.h
index 0b6567fac8..1d68737381 100644
--- a/libavformat/spdif.h
+++ b/libavformat/spdif.h
@@ -50,6 +50,7 @@ enum IEC61937DataType {
     IEC61937_MPEG2_AAC_LSF_4096 = 0x13 | 0x20,   ///< MPEG-2 AAC ADTS quarter-rate low sampling frequency
     IEC61937_EAC3               = 0x15,          ///< E-AC-3 data
     IEC61937_TRUEHD             = 0x16,          ///< TrueHD data
+    IEC61937_DOLBY_E            = 0x1C,          ///< Dolby E data (SMPTE 338M data type, shared with SMPTE 337M)
     IEC61937_MAX_ENUM           = 0xFFFF         ///< to force the underlying type to be at least 16bits
 };
 
diff --git a/libavformat/spdifdec.c b/libavformat/spdifdec.c
index fe45deb157..14bf3b0334 100644
--- a/libavformat/spdifdec.c
+++ b/libavformat/spdifdec.c
@@ -99,6 +99,49 @@ static int spdif_get_offset_and_codec(AVFormatContext *s,
         *offset = 24576;
         *codec = AV_CODEC_ID_EAC3;
         break;
+    case IEC61937_DOLBY_E: {
+        /* Burst period depends on frame rate, unlike the fixed per-format
+         * offsets above -- derived from fr_code (4 bits: sync word(16 bits)
+         * + revision_id(4) + mtd_size(10) + program_config(6) leaves
+         * fr_code occupying bits [20:24) of the flat metadata stream, i.e.
+         * the low nibble of the 3rd 16-bit word) rather than from packet
+         * size, so this needs no signature change. Table mirrors s337m.c's
+         * s337m_get_offset_and_codec() exactly.
+         *
+         * Called from two contexts with different byte order: real packet
+         * reads (ff_spdif_read_packet) already ran ff_spdif_bswap_buf16,
+         * so buf is native/big-endian (07 8e ...); the probe path
+         * (ff_spdif_probe) scans raw, still-16-bit-byte-swapped file bytes
+         * (8e 07 ...), so the fr_code nibble shifts from byte 4 to byte 5. */
+        static const int total_words_tab[6] = { 0, 3644, 3640, 3648, 3040, 3040 };
+        static const struct { int nwords, samples; } tab[] = {
+            { 3648, 1920 }, { 3644, 2002 }, { 3640, 2000 }, { 3040, 1601 },
+        };
+        int fr_code, nwords;
+        uint8_t b0 = buf[0], b1 = buf[1];
+
+        if (b0 == 0x07 && b1 == 0x8e)
+            fr_code = buf[4] & 0x0F;
+        else if (b0 == 0x8e && b1 == 0x07)
+            fr_code = buf[5] & 0x0F;
+        else
+            return AVERROR_INVALIDDATA;
+        if (fr_code < 1 || fr_code > 5)
+            return AVERROR_INVALIDDATA;
+        nwords = total_words_tab[fr_code];
+
+        *offset = 0;
+        for (int i = 0; i < FF_ARRAY_ELEMS(tab); i++) {
+            if (tab[i].nwords == nwords) {
+                *offset = (tab[i].samples - 4) * 4;
+                break;
+            }
+        }
+        if (!*offset)
+            return AVERROR_PATCHWELCOME;
+        *codec = AV_CODEC_ID_DOLBY_E;
+        break;
+    }
     default:
         if (s) { /* be silent during a probe */
             avpriv_request_sample(s, "Data type 0x%04x in IEC 61937",
diff --git a/libavformat/spdifenc.c b/libavformat/spdifenc.c
index 16eebda01c..a197d083ff 100644
--- a/libavformat/spdifenc.c
+++ b/libavformat/spdifenc.c
@@ -54,6 +54,7 @@
 #include "libavcodec/adts_parser.h"
 #include "libavcodec/dca.h"
 #include "libavcodec/dca_syncwords.h"
+#include "libavutil/intreadwrite.h"
 #include "libavutil/mem.h"
 #include "libavutil/opt.h"
 
@@ -119,6 +120,41 @@ static int spdif_header_ac3(AVFormatContext *s, AVPacket *pkt)
     return 0;
 }
 
+/* Dolby E's own burst repetition period (in output bytes) depends on frame
+ * rate, unlike AC-3/DTS/AAC's fixed per-format offsets above -- mirrors
+ * libavformat/s337m.c's s337m_get_offset_and_codec()/burst_payload_words()
+ * exactly (same SMPTE 337M envelope table), since IEC 61937 and SMPTE 337M
+ * share the same underlying data-type/data-size framing, just carried over
+ * a 16-bit-word consumer interface instead of professional AES3. Only
+ * 16-bit Dolby E words are supported here: IEC 61937's 16-bit-word byte
+ * swap (ff_spdif_bswap_buf16) isn't meaningful for 20/24-bit AES3 words. */
+static int spdif_header_dolby_e(AVFormatContext *s, AVPacket *pkt)
+{
+    static const struct { int nwords, samples; } tab[] = {
+        { 3648, 1920 }, { 3644, 2002 }, { 3640, 2000 }, { 3040, 1601 },
+    };
+    IEC61937Context *ctx = s->priv_data;
+    int nwords;
+
+    if (pkt->size < 2 || AV_RB16(pkt->data) != 0x078E) {
+        av_log(s, AV_LOG_ERROR, "Only 16-bit-word Dolby E frames can be "
+               "carried over IEC 61937\n");
+        return AVERROR_PATCHWELCOME;
+    }
+
+    nwords = pkt->size >> 1;
+    ctx->data_type = IEC61937_DOLBY_E;
+
+    for (int i = 0; i < FF_ARRAY_ELEMS(tab); i++) {
+        if (tab[i].nwords == nwords) {
+            ctx->pkt_offset = (tab[i].samples - 4) * 4;
+            return 0;
+        }
+    }
+    avpriv_report_missing_feature(s, "Dolby E frame word count %d in IEC 61937", nwords);
+    return AVERROR_PATCHWELCOME;
+}
+
 static int spdif_header_eac3(AVFormatContext *s, AVPacket *pkt)
 {
     IEC61937Context *ctx = s->priv_data;
@@ -589,6 +625,9 @@ static int spdif_write_header(AVFormatContext *s)
     case AV_CODEC_ID_AAC:
         ctx->header_info = spdif_header_aac;
         break;
+    case AV_CODEC_ID_DOLBY_E:
+        ctx->header_info = spdif_header_dolby_e;
+        break;
     case AV_CODEC_ID_TRUEHD:
     case AV_CODEC_ID_MLP:
         ctx->header_info = spdif_header_truehd;
-- 
2.52.0


From 2f5e43ea768b595c77f309a83d91538f9ee20c99 Mon Sep 17 00:00:00 2001
From: Oren Jack Maurice <[email protected]>
Date: Sun, 9 Aug 2026 12:03:50 +0300
Subject: [PATCH 176/180] avformat/wavenc: support carrying Dolby E over WAV

Dolby E has no registered WAVE_FORMAT tag of its own, and EBU Tech 3285
Supplement 6's dbmd chunk only covers PCM audio metadata, not non-PCM
bitstream carriage. Follow the same real-world convention already used
for AC-3/DTS "receiver test" WAV files: advertise the fmt chunk as
ordinary 16-bit PCM (wFormatTag 1) and let the SMPTE 337M/IEC 61937
burst preamble self-identify the payload. wavdec.c's existing
set_spdif() probe for codec_tag==1 streams already handles the read
side, so no demuxer changes are needed.

Only 16-bit-word Dolby E frames can be carried this way, matching the
spdif muxer's own limitation.

Development-method: AI-assisted (Claude Code); reviewed by the submitter
before posting.

Signed-off-by: Oren Jack Maurice <[email protected]>
---
 doc/muxers.texi      |  6 ++++
 libavformat/wavenc.c | 85 ++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 89 insertions(+), 2 deletions(-)

diff --git a/doc/muxers.texi b/doc/muxers.texi
index ba44d4c81a..4a277e6ed8 100644
--- a/doc/muxers.texi
+++ b/doc/muxers.texi
@@ -3861,6 +3861,12 @@ ffmpeg -i ... -map 0 -flags +global_header -c:v libx264 -c:a aac
 
 RIFF Wave Audio muxer.
 
+Dolby E input is carried per the same convention used by AC-3/DTS
+"receiver test" WAV files: the @code{fmt } chunk advertises ordinary
+16-bit PCM (@code{wFormatTag} 1), and the SMPTE 337M/IEC 61937 burst
+preamble self-identifies the actual payload to a compliant reader. Only
+16-bit-word Dolby E frames can be carried this way.
+
 @subsection Options
 
 @table @option
diff --git a/libavformat/wavenc.c b/libavformat/wavenc.c
index 01fffaafe5..2d53ec8f4b 100644
--- a/libavformat/wavenc.c
+++ b/libavformat/wavenc.c
@@ -48,6 +48,7 @@
 #include "avio.h"
 #include "avio_internal.h"
 #include "internal.h"
+#include "spdif.h"
 #include "mux.h"
 #include "riff.h"
 
@@ -299,6 +300,79 @@ static int peak_write_chunk(AVFormatContext *s)
     return 0;
 }
 
+/* Dolby E has no registered WAVE_FORMAT tag of its own (checked: neither
+ * Microsoft's mmreg.h registry nor Dolby's own tooling define one), and
+ * EBU Tech 3285 Supplement 6's dbmd chunk explicitly only describes PCM
+ * audio metadata, not non-PCM bitstream carriage (Suppl. 6 section 5.5).
+ * The real-world convention for exactly this situation -- compressed
+ * professional audio in a WAV file with no format tag of its own -- is the
+ * same IEC 61937/SMPTE 337M burst-in-disguised-PCM trick used for
+ * AC-3/DTS "receiver test" WAV files: advertise as ordinary 16-bit PCM
+ * (wFormatTag=1) and let the burst preamble self-identify the payload.
+ * wavdec.c's own set_spdif() already probes exactly this for codec_tag==1
+ * streams, so no wavdec.c changes are needed for the read side. */
+static void write_dolby_e_fmt_body(AVFormatContext *s)
+{
+    AVIOContext *pb = s->pb;
+    AVCodecParameters *par = s->streams[0]->codecpar;
+    int block_align = par->ch_layout.nb_channels * 2;
+
+    avio_wl16(pb, 1); /* wFormatTag: WAVE_FORMAT_PCM */
+    avio_wl16(pb, par->ch_layout.nb_channels);
+    avio_wl32(pb, par->sample_rate);
+    avio_wl32(pb, par->sample_rate * block_align); /* nAvgBytesPerSec */
+    avio_wl16(pb, block_align);
+    avio_wl16(pb, 16); /* wBitsPerSample */
+}
+
+/* Mirrors spdifenc.c's Dolby E IEC 61937 burst framing exactly (same data
+ * type 0x1C, same burst-period table derived from fr_code) -- reimplemented
+ * here rather than shared, since spdifenc.c's helpers are tied to its own
+ * per-context IEC61937Context state, not something this muxer has. */
+static int write_dolby_e_packet(AVFormatContext *s, AVPacket *pkt)
+{
+    static const struct { int nwords, samples; } tab[] = {
+        { 3648, 1920 }, { 3644, 2002 }, { 3640, 2000 }, { 3040, 1601 },
+    };
+    AVIOContext *pb = s->pb;
+    int nwords, pkt_offset = 0, padding;
+    uint8_t *swapped;
+
+    if (pkt->size < 2 || AV_RB16(pkt->data) != 0x078E) {
+        av_log(s, AV_LOG_ERROR,
+               "Only 16-bit-word Dolby E frames can be carried in WAV\n");
+        return AVERROR_PATCHWELCOME;
+    }
+    nwords = pkt->size >> 1;
+    for (int i = 0; i < FF_ARRAY_ELEMS(tab); i++) {
+        if (tab[i].nwords == nwords) {
+            pkt_offset = (tab[i].samples - 4) * 4;
+            break;
+        }
+    }
+    if (!pkt_offset) {
+        avpriv_report_missing_feature(s, "Dolby E frame word count %d in WAV", nwords);
+        return AVERROR_PATCHWELCOME;
+    }
+
+    avio_wl16(pb, SYNCWORD1);
+    avio_wl16(pb, SYNCWORD2);
+    avio_wl16(pb, IEC61937_DOLBY_E);
+    avio_wl16(pb, pkt->size << 3);
+
+    swapped = av_malloc(pkt->size);
+    if (!swapped)
+        return AVERROR(ENOMEM);
+    ff_spdif_bswap_buf16((uint16_t *)swapped, (const uint16_t *)pkt->data, pkt->size >> 1);
+    avio_write(pb, swapped, pkt->size);
+    av_free(swapped);
+
+    padding = pkt_offset - BURST_HEADER_SIZE - pkt->size;
+    if (padding > 0)
+        ffio_fill(pb, 0, padding);
+    return 0;
+}
+
 static int wav_write_header(AVFormatContext *s)
 {
     WAVMuxContext *wav = s->priv_data;
@@ -326,7 +400,10 @@ static int wav_write_header(AVFormatContext *s)
     if (wav->write_peak != PEAK_ONLY) {
         /* format header */
         fmt = ff_start_tag(pb, "fmt ");
-        if (ff_put_wav_header(s, pb, s->streams[0]->codecpar, 0) < 0) {
+        if (s->streams[0]->codecpar->codec_id == AV_CODEC_ID_DOLBY_E) {
+            s->streams[0]->codecpar->codec_tag = 1; /* advertise as PCM, see write_dolby_e_fmt_body() */
+            write_dolby_e_fmt_body(s);
+        } else if (ff_put_wav_header(s, pb, s->streams[0]->codecpar, 0) < 0) {
             av_log(s, AV_LOG_ERROR, "Codec %s not supported in WAVE format\n",
                    avcodec_get_name(s->streams[0]->codecpar->codec_id));
             return AVERROR(ENOSYS);
@@ -380,7 +457,11 @@ static int wav_write_packet(AVFormatContext *s, AVPacket *pkt)
     AVIOContext *pb  = s->pb;
     WAVMuxContext    *wav = s->priv_data;
 
-    if (wav->write_peak != PEAK_ONLY)
+    if (s->streams[0]->codecpar->codec_id == AV_CODEC_ID_DOLBY_E) {
+        int ret = write_dolby_e_packet(s, pkt);
+        if (ret < 0)
+            return ret;
+    } else if (wav->write_peak != PEAK_ONLY)
         avio_write(pb, pkt->data, pkt->size);
 
     if (wav->write_peak) {
-- 
2.52.0


From 6dc2cbff40fb59b1d8c9435d694cef455b7f3766 Mon Sep 17 00:00:00 2001
From: Oren Jack Maurice <[email protected]>
Date: Sun, 9 Aug 2026 12:04:00 +0300
Subject: [PATCH 177/180] avformat/mxf: enable Dolby E SoundEssenceCompression
 codec UL

Un-comment the existing Dolby E entry in ff_mxf_codec_uls
(...04.02.02.02.03.02.1C.00): 0x1C is the SMPTE 338M Dolby E data type,
shared with SMPTE 337M, and this UL sits in the same registry branch as
AC-3's own .01 and MP2's .05 immediately above it.

Development-method: AI-assisted (Claude Code); reviewed by the submitter
before posting.

Signed-off-by: Oren Jack Maurice <[email protected]>
---
 libavformat/mxf.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libavformat/mxf.c b/libavformat/mxf.c
index b6c1f176a1..a402885899 100644
--- a/libavformat/mxf.c
+++ b/libavformat/mxf.c
@@ -78,7 +78,7 @@ const MXFCodecUL ff_mxf_codec_uls[] = {
     { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x04,0x04,0x02,0x02,0x02,0x03,0x01,0x01,0x00 }, 15,   AV_CODEC_ID_PCM_ALAW }, /* XDCAM Proxy C0023S01.mxf */
     { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x04,0x02,0x02,0x02,0x03,0x02,0x01,0x00 }, 15,        AV_CODEC_ID_AC3 },
     { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x04,0x02,0x02,0x02,0x03,0x02,0x05,0x00 }, 15,        AV_CODEC_ID_MP2 }, /* MP2 or MP3 */
-  //{ { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x04,0x02,0x02,0x02,0x03,0x02,0x1C,0x00 }, 15,    AV_CODEC_ID_DOLBY_E }, /* Dolby-E */
+    { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x04,0x02,0x02,0x02,0x03,0x02,0x1C,0x00 }, 15,    AV_CODEC_ID_DOLBY_E }, /* Dolby-E: SoundEssenceCompression 04.02.02.02.03.02.1C, matching AC-3's own .01 and MP2's .05 in the same registry branch (0x1C = SMPTE 338M Dolby E data type, shared with SMPTE 337M) */
     { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0D,0x04,0x02,0x02,0x02,0x04,0x03,0x00,0x00 }, 14,        AV_CODEC_ID_AAC }, /* MPEG-2 AAC SMPTE 381-4 */
     { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0D,0x04,0x02,0x02,0x02,0x04,0x04,0x00,0x00 }, 14,        AV_CODEC_ID_AAC }, /* MPEG-4 AAC SMPTE 381-4 */
     { { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 },  0,       AV_CODEC_ID_NONE },
-- 
2.52.0


From ca36f84281527207bc199f825b1136b686e3ab1a Mon Sep 17 00:00:00 2001
From: Oren Jack Maurice <[email protected]>
Date: Sun, 9 Aug 2026 12:07:07 +0300
Subject: [PATCH 178/180] avformat/mpegts, mpegtsenc: carry Dolby E over SMPTE
 302M in MPEG-TS

Dolby E has no MPEG-TS stream_type/descriptor of its own, so ride the
existing SMPTE 302M "AES3 data in MPEG-2 TS" signaling (BSSD
registration descriptor, already mapped to AV_CODEC_ID_S302M) with the
Dolby E bitstream hidden inside as an SMPTE 337M/IEC 61937 burst
disguised as 16-bit PCM samples, matching the same convention used for
the spdif and wavenc Dolby E support already added.

mpegtsenc.c (write side): builds the burst, then packs it through
SMPTE 302M's AES3-header + per-sample bit-reversal framing (mirrors
s302menc.c's bits_per_raw_sample==16 path) as a 2-channel AES3 pair, and
extends the existing BSSD registration descriptor condition to cover
AV_CODEC_ID_DOLBY_E alongside AV_CODEC_ID_S302M.

mpegts.c (read side): the inverse -- undoes the AES3 header and bit
reversal, and if the reconstructed samples start with the IEC 61937
Dolby E burst preamble, replaces the packet with the raw Dolby E frame
bytes and switches the stream's codec_id from S302M to DOLBY_E so
playback/decoding uses the real dolby_e decoder.

The bit-reversal helper is reimplemented locally in both files rather
than reusing libavutil's ff_reverse[256] table (already used by
s302menc.c), since that table is not part of libavutil's exported ABI
and is therefore not linkable from these shared libraries.

Also mark the stream's FFStream.need_context_update and clear
request_probe when switching codec_id mid-stream: without this, the
generic stream-info probing machinery in avformat_find_stream_info()
keeps feeding these now-unwrapped, no-longer-AES3-shaped packets to a
decoder probe already opened against the stale S302M guess, and every
one of them fails ("frame has invalid header" from the s302m decoder).
Found by actually running the mux -> demux round trip end to end
(ffprobe reported codec_name=s302m instead of dolby_e until this fix);
verified with `ffmpeg -i asynth... -c:a dolby_e -word_bits 16 -f mpegts`
followed by demux+decode.

Development-method: AI-assisted (Claude Code); reviewed by the submitter
before posting.

Signed-off-by: Oren Jack Maurice <[email protected]>
---
 doc/muxers.texi         |  9 ++++
 libavformat/mpegts.c    | 94 +++++++++++++++++++++++++++++++++++++++++
 libavformat/mpegtsenc.c | 92 +++++++++++++++++++++++++++++++++++++++-
 3 files changed, 194 insertions(+), 1 deletion(-)

diff --git a/doc/muxers.texi b/doc/muxers.texi
index 4a277e6ed8..845bcfeb9e 100644
--- a/doc/muxers.texi
+++ b/doc/muxers.texi
@@ -3074,6 +3074,15 @@ and @code{service_name}. If they are not set the default for
 @code{service_provider} is @samp{FFmpeg} and the default for
 @code{service_name} is @samp{Service01}.
 
+Dolby E has no MPEG-TS stream_type/descriptor of its own; a
+@code{dolby_e} input stream is muxed inside SMPTE 302M's "AES3 data in
+MPEG-2 TS" signaling (the BSSD registration descriptor), with the actual
+Dolby E frame carried as an SMPTE 337M/IEC 61937 burst disguised as
+16-bit PCM samples inside that AES3 payload. Only 16-bit-word Dolby E
+frames are supported. The demuxer reverses this automatically and
+switches the stream's codec to @code{dolby_e} once it recognizes the
+burst preamble.
+
 @subsection Options
 
 The muxer options are:
diff --git a/libavformat/mpegts.c b/libavformat/mpegts.c
index 076020509d..b5916b19b6 100644
--- a/libavformat/mpegts.c
+++ b/libavformat/mpegts.c
@@ -40,6 +40,7 @@
 #include "mpegts.h"
 #include "internal.h"
 #include "avio_internal.h"
+#include "spdif.h"
 #include "demux.h"
 #include "mpeg.h"
 #include "isom.h"
@@ -1064,6 +1065,97 @@ end:
     return ret;
 }
 
+#define AES3_HEADER_LEN 4 /* SMPTE 302M AES3 subframe header, matches libavcodec/s302m{,enc}.c */
+
+static uint8_t mpegts_reverse_bits8(uint8_t b)
+{
+    b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
+    b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
+    b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
+    return b;
+}
+
+/* Dolby E has no MPEG-TS stream_type/descriptor of its own; it rides SMPTE
+ * 302M's "AES3 data in MPEG-2 TS" signaling (BSSD registration descriptor,
+ * already mapped to AV_CODEC_ID_S302M above) with the actual Dolby E
+ * bitstream hidden inside as an SMPTE 337M/IEC 61937 burst disguised as
+ * 16-bit PCM samples -- see libavformat/mpegtsenc.c's Dolby E case, which
+ * builds exactly this. This is the inverse: undo the AES3 header + bit
+ * reversal (mirrors libavcodec/s302m.c's own decode math for the 16-bit,
+ * 2-channel case), then look for the burst preamble in the reconstructed
+ * samples and, if present, replace the packet with the raw Dolby E frame
+ * bytes and switch this stream's codec_id so playback (or decoding) uses
+ * the real dolby_e decoder instead of s302m. Runs once per BSSD stream
+ * (mpegts_find_stream_type() only settles on S302M initially; codec_id is
+ * overridden here as soon as a burst is actually found in the payload). */
+static void try_unwrap_dolby_e_from_s302m(AVStream *st, AVPacket *pkt)
+{
+    int nb_pairs;
+    uint16_t *samples;
+
+    if ((st->codecpar->codec_id != AV_CODEC_ID_S302M &&
+         st->codecpar->codec_id != AV_CODEC_ID_DOLBY_E) ||
+        st->codecpar->codec_tag != MKTAG('B', 'S', 'S', 'D') ||
+        pkt->size < AES3_HEADER_LEN + 5 * 4)
+        return;
+
+    /* AES3 header: size(16) + channels(2) + channel_id(8) + bits(2) + align(4).
+     * Only the 16-bit, 2-channel case is attempted -- that's what the
+     * encode side (mpegtsenc.c) ever produces for Dolby E. */
+    if (((pkt->data[2] >> 6) & 0x3) != 0 /* nb_channels != 2 */ ||
+        ((pkt->data[3] >> 4) & 0x3) != 0 /* bits_per_sample != 16 */)
+        return;
+
+    nb_pairs = (pkt->size - AES3_HEADER_LEN) / 5;
+    if (nb_pairs < 4)
+        return;
+
+    samples = av_malloc_array(nb_pairs * 2, sizeof(*samples));
+    if (!samples)
+        return;
+    {
+        const uint8_t *b = pkt->data + AES3_HEADER_LEN;
+        for (int i = 0; i < nb_pairs; i++, b += 5) {
+            samples[2 * i]     = mpegts_reverse_bits8(b[0]) |
+                                  mpegts_reverse_bits8(b[1]) << 8;
+            samples[2 * i + 1] = mpegts_reverse_bits8(b[2]) >> 4 |
+                                  mpegts_reverse_bits8(b[3]) << 4 |
+                                  (mpegts_reverse_bits8(b[4] & 0xF0) << 12);
+        }
+    }
+
+    if (samples[0] == SYNCWORD1 && samples[1] == SYNCWORD2 &&
+        samples[2] == IEC61937_DOLBY_E) {
+        int nwords = samples[3] >> 4; /* length_code is pkt->size<<3 (bits); /16 to get 16-bit words */
+        AVBufferRef *buf;
+
+        if (nwords > 0 && 4 + nwords <= nb_pairs * 2) {
+            buf = av_buffer_alloc(nwords * 2 + AV_INPUT_BUFFER_PADDING_SIZE);
+            if (buf) {
+                for (int i = 0; i < nwords; i++)
+                    AV_WB16(buf->data + 2 * i, samples[4 + i]);
+                memset(buf->data + nwords * 2, 0, AV_INPUT_BUFFER_PADDING_SIZE);
+                av_buffer_unref(&pkt->buf);
+                pkt->buf  = buf;
+                pkt->data = buf->data;
+                pkt->size = nwords * 2;
+                if (st->codecpar->codec_id != AV_CODEC_ID_DOLBY_E) {
+                    FFStream *const sti = ffstream(st);
+                    st->codecpar->codec_id = AV_CODEC_ID_DOLBY_E;
+                    /* Tell the generic stream-info probing machinery the
+                     * codec changed mid-stream: without this, a decoder
+                     * probe already opened against the stale S302M guess
+                     * keeps being fed these now-unwrapped, no-longer-AES3
+                     * packets and fails every time. */
+                    sti->need_context_update = 1;
+                    sti->request_probe = 0;
+                }
+            }
+        }
+    }
+    av_free(samples);
+}
+
 static int new_pes_packet(PESContext *pes, AVPacket *pkt)
 {
     uint8_t *sd;
@@ -1081,6 +1173,8 @@ static int new_pes_packet(PESContext *pes, AVPacket *pkt)
         pes->flags |= AV_PKT_FLAG_CORRUPT;
     }
 
+    try_unwrap_dolby_e_from_s302m(pes->st, pkt);
+
     // JPEG-XS PES payload
     if (pes->stream_id == 0xbd && pes->stream_type == 0x32 &&
         pkt->size >= 8 && memcmp(pkt->data + 4, "jxes", 4) == 0)
diff --git a/libavformat/mpegtsenc.c b/libavformat/mpegtsenc.c
index 7f76883531..f4a8647ae6 100644
--- a/libavformat/mpegtsenc.c
+++ b/libavformat/mpegtsenc.c
@@ -41,6 +41,7 @@
 #include "internal.h"
 #include "mpegts.h"
 #include "mux.h"
+#include "spdif.h"
 
 #define PCR_TIME_BASE 27000000
 
@@ -606,7 +607,13 @@ static int mpegts_write_pmt(AVFormatContext *s, MpegTSService *service)
                     *q++=0; // omit all fields...
                 }
             }
-            if (codec_id == AV_CODEC_ID_S302M)
+            if (codec_id == AV_CODEC_ID_S302M || codec_id == AV_CODEC_ID_DOLBY_E)
+                /* Dolby E has no MPEG-TS stream_type/descriptor of its own;
+                 * it rides the same SMPTE 302M "AES3 data in MPEG-2 TS"
+                 * signaling real receivers use for exactly this situation
+                 * (SMPTE 337M-disguised professional audio), matching
+                 * libavcodec/s302m.c's own decoder, which already searches
+                 * reconstructed S302M samples for this same burst pattern. */
                 put_registration_descriptor(&q, MKTAG('B', 'S', 'S', 'D'));
             if (codec_id == AV_CODEC_ID_OPUS) {
                 int ch = st->codecpar->ch_layout.nb_channels;
@@ -1863,6 +1870,20 @@ static uint8_t *h26x_prefix_aud(const uint8_t *aud, const int aud_size,
     return data;
 }
 
+#define AES3_HEADER_LEN 4 /* SMPTE 302M AES3 subframe header, matches libavcodec/s302m{,enc}.c */
+
+/* Bit-reverse a byte for SMPTE 302M packing. libavutil's ff_reverse[256]
+ * (used by libavcodec/s302menc.c) isn't part of libavutil's exported ABI
+ * (its version script only exports av*-prefixed symbols), so it isn't
+ * linkable from this shared library; this is the standard equivalent. */
+static uint8_t reverse_bits8(uint8_t b)
+{
+    b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
+    b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
+    b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
+    return b;
+}
+
 #define H264_NAL_TYPE(state) (state & 0x1f)
 #define HEVC_NAL_TYPE(state) ((state & 0x7e) >> 1)
 #define VVC_NAL_TYPE(state)  ((state >> 11) & 0x1f)
@@ -2002,6 +2023,75 @@ static int mpegts_write_packet_internal(AVFormatContext *s, AVPacket *pkt)
                 buf             = data;
             }
         }
+    } else if (st->codecpar->codec_id == AV_CODEC_ID_DOLBY_E) {
+        /* Two nested wrapping layers, both already established elsewhere in
+         * this codebase: (1) the same SMPTE 337M/IEC 61937 burst (sync,
+         * data-type 0x1C, length, frame-rate-dependent padding) built for
+         * spdifenc.c/wavenc.c's Dolby E support, disguised as a stream of
+         * 16-bit PCM sample VALUES; (2) SMPTE 302M's own AES3-header +
+         * per-sample bit-reversal packing (mirrors libavcodec/s302menc.c's
+         * bits_per_raw_sample==16 path exactly) over that fake-PCM stream,
+         * as a 2-channel AES3 pair -- matching how libavcodec/s302m.c's
+         * decoder searches reconstructed 2-channel samples for exactly
+         * this burst pattern (its "channels == 2" non-PCM detection path).
+         * The registration descriptor above already flags the stream as
+         * SMPTE 302M (BSSD) to any receiver expecting this convention. */
+        static const struct { int nwords, samples; } tab[] = {
+            { 3648, 1920 }, { 3644, 2002 }, { 3640, 2000 }, { 3040, 1601 },
+        };
+        int nwords, burst_words = 0;
+        uint16_t *burst;
+        uint8_t *o;
+
+        if (pkt->size < 2 || AV_RB16(pkt->data) != 0x078E) {
+            av_log(s, AV_LOG_ERROR, "Only 16-bit-word Dolby E frames can be "
+                   "carried over SMPTE 302M in MPEG-TS\n");
+            return AVERROR_PATCHWELCOME;
+        }
+        nwords = pkt->size >> 1;
+        for (int i = 0; i < FF_ARRAY_ELEMS(tab); i++) {
+            if (tab[i].nwords == nwords) {
+                burst_words = (tab[i].samples - 4) * 2;
+                break;
+            }
+        }
+        if (!burst_words) {
+            avpriv_report_missing_feature(s, "Dolby E frame word count %d "
+                                          "over SMPTE 302M", nwords);
+            return AVERROR_PATCHWELCOME;
+        }
+
+        burst = av_calloc(burst_words, sizeof(*burst));
+        if (!burst)
+            return AVERROR(ENOMEM);
+        burst[0] = SYNCWORD1;
+        burst[1] = SYNCWORD2;
+        burst[2] = IEC61937_DOLBY_E;
+        burst[3] = pkt->size << 3;
+        for (int i = 0; i < nwords; i++)
+            burst[4 + i] = AV_RB16(pkt->data + 2 * i);
+
+        size = AES3_HEADER_LEN + (burst_words / 2) * 5;
+        data = av_malloc(size);
+        if (!data) {
+            av_free(burst);
+            return AVERROR(ENOMEM);
+        }
+        AV_WB16(data, size - AES3_HEADER_LEN);
+        data[2] = 0;    /* number of channels: (2-2)>>1 = 0 -> 2 channels */
+        data[3] = 0;    /* bits per sample: 0 -> 16 bit; alignment: 0 */
+        o = data + AES3_HEADER_LEN;
+        for (int i = 0; i < burst_words; i += 2) {
+            uint16_t l = burst[i], r = burst[i + 1];
+            o[0] = reverse_bits8(l & 0xFF);
+            o[1] = reverse_bits8((l & 0xFF00) >> 8);
+            o[2] = reverse_bits8((r & 0x0F) << 4);
+            o[3] = reverse_bits8((r & 0x0FF0) >> 4);
+            o[4] = reverse_bits8((r & 0xF000) >> 12);
+            o += 5;
+        }
+        av_free(burst);
+        buf = data;
     } else if (st->codecpar->codec_id == AV_CODEC_ID_HEVC) {
         const uint8_t *p = buf, *buf_end = p + size;
         uint32_t state = -1;
-- 
2.52.0


From 2cd4e69880b08ac31c0d17fc0874121a9d365ae9 Mon Sep 17 00:00:00 2001
From: Oren Jack Maurice <[email protected]>
Date: Sun, 9 Aug 2026 12:07:38 +0300
Subject: [PATCH 179/180] MAINTAINERS: add self as co-maintainer for dolby_e*
 and s337menc.c

Add myself alongside foo86 (the existing dolby_e/s337m maintainer) for
the files added/extended by the preceding Dolby E encoder patches.

Signed-off-by: Oren Jack Maurice <[email protected]>
---
 MAINTAINERS | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/MAINTAINERS b/MAINTAINERS
index 7ffa6982fc..8734fb9466 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -175,7 +175,7 @@ Codecs:
   dfpwm*                                Jack Bruienne
   dirac*                                Rostislav Pehlivanov
   dnxhd*                                Baptiste Coudurier
-  dolby_e*                              foo86
+  dolby_e*                              foo86, Oren Jack Maurice
   dpcm.c                                Mike Melanson
   dss_sp.c                              Oleksij Rempel
   dv.c                                  Roman Shaposhnik
@@ -475,7 +475,7 @@ Muxers/Demuxers:
   rtpdec_vc2hq.*, rtpenc_vc2hq.*        Thomas Volkert
   rtpdec_vp9.c                          Thomas Volkert
   rtpenc_mpv.*, rtpenc_aac.*            Martin Storsjo
-  s337m.c                               foo86
+  s337m.c, s337menc.c                   foo86, Oren Jack Maurice
   sbgdec.c                              Nicolas George
   sdp.c                                 Martin Storsjo
   segafilm.c                            Mike Melanson
-- 
2.52.0


From 5f5d720c9d441917b3a1ae635616b0858f46ed7d Mon Sep 17 00:00:00 2001
From: Oren Jack Maurice <[email protected]>
Date: Sun, 9 Aug 2026 12:15:14 +0300
Subject: [PATCH 180/180] fate: add dolby_e encoder round-trip smoke test

Encodes synthetic 6-channel PCM (tests/data/asynth-44800-6.wav, 44800 Hz
selecting the 25fps SMPTE frame rate) through the new dolby_e encoder
into s337m, then demuxes/decodes it back and compares against the
source with stddev.

This is a smoke test against FFmpeg's own decoder, not a claim of
interop with third-party Dolby E equipment -- no real Dolby-authored
sample is involved. FUZZ is set generously (200) since the encoder's
rate control runs a binary search against the MDCT/IMDCT transform,
which is not guaranteed bit-exact across SIMD backends; SIZE_TOLERANCE
accounts for the output covering whole encoded frames only, which does
not evenly divide the 6-second input duration.

Verified passing locally (stddev ~11919, PSNR ~14.8dB, matching the
committed CMP_TARGET within FUZZ).

Development-method: AI-assisted (Claude Code); reviewed by the submitter
before posting.

Signed-off-by: Oren Jack Maurice <[email protected]>
---
 tests/fate/audio.mak | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/tests/fate/audio.mak b/tests/fate/audio.mak
index c21578947a..33725d2713 100644
--- a/tests/fate/audio.mak
+++ b/tests/fate/audio.mak
@@ -31,6 +31,23 @@ fate-dolby-e: CMD = pcm -i $(TARGET_SAMPLES)/dolby_e/16-11
 fate-dolby-e: CMP = oneoff
 fate-dolby-e: REF = $(SAMPLES)/dolby_e/16-11.pcm
 
+# Encoder smoke test: no real Dolby-authored sample involved, just an
+# encode -> s337m mux -> demux -> decode round trip against this
+# codebase's own decoder, using synthetic multichannel PCM. Not a claim
+# of interop with third-party Dolby E equipment (see the encoder's own
+# commit message); a large FUZZ is used since the encoder's rate control
+# depends on the MDCT/IMDCT transform, which is not guaranteed bit-exact
+# across SIMD backends.
+FATE_SAMPLES_AUDIO-$(call ENCDEC, DOLBY_E, S337M, PCM_S16LE_ENCODER WAV_MUXER PIPE_PROTOCOL) += fate-dolby-e-encode
+fate-dolby-e-encode: $(AREF) tests/data/asynth-44800-6.wav
+fate-dolby-e-encode: CMD = enc_dec_pcm s337m wav s16le tests/data/asynth-44800-6.wav -c:a dolby_e
+fate-dolby-e-encode: CMP = stddev
+fate-dolby-e-encode: REF = tests/data/asynth-44800-6.wav
+fate-dolby-e-encode: CMP_SHIFT = -256
+fate-dolby-e-encode: CMP_TARGET = 11920
+fate-dolby-e-encode: FUZZ = 200
+fate-dolby-e-encode: SIZE_TOLERANCE = 22000
+
 FATE_SAMPLES_AUDIO-$(call FRAMECRC, DSS, DSS_SP, ARESAMPLE_FILTER) += fate-dss-lp
 fate-dss-lp: CMD = framecrc -i $(TARGET_SAMPLES)/dss/lp.dss -frames 30 -af aresample
 
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.