PR #22654 opened by Niklas Haas (haasn)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22654
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22654.patch

The main take-away is that we now ensure our internally allocated frames are 
appropriately padded.


>From 295fc06def962b778ae005ca73680d8ec2e999da Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Tue, 24 Mar 2026 18:26:26 +0100
Subject: [PATCH 01/13] avfilter/framepool: actually use specified allocator
 for audio frames

This bug means audio buffers were never correctly zero'd as intended by
libavfilter/audio.c

Signed-off-by: Niklas Haas <[email protected]>
---
 libavfilter/framepool.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libavfilter/framepool.c b/libavfilter/framepool.c
index 0259415620..15ea66cdc6 100644
--- a/libavfilter/framepool.c
+++ b/libavfilter/framepool.c
@@ -137,7 +137,7 @@ av_cold FFFramePool *ff_frame_pool_audio_init(AVBufferRef* 
(*alloc)(size_t size)
 
     if (pool->linesize[0] > SIZE_MAX - align)
         goto fail;
-    pool->pools[0] = av_buffer_pool_init(pool->linesize[0] + align, NULL);
+    pool->pools[0] = av_buffer_pool_init(pool->linesize[0] + align, alloc);
     if (!pool->pools[0])
         goto fail;
 
-- 
2.52.0


>From c3f408054f1c09e3d6ced1fd801d7b866263eb8e Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Tue, 24 Mar 2026 18:29:56 +0100
Subject: [PATCH 02/13] avfilter/framepool: remove `alloc` argument

Not really needed by anything and makes this API a bit clunkier to extend.

Signed-off-by: Niklas Haas <[email protected]>
---
 libavfilter/audio.c     |  8 ++++----
 libavfilter/framepool.c | 14 ++++++++------
 libavfilter/framepool.h | 12 ++----------
 libavfilter/video.c     | 10 ++--------
 4 files changed, 16 insertions(+), 28 deletions(-)

diff --git a/libavfilter/audio.c b/libavfilter/audio.c
index 7d87e9e9a0..308d04cf43 100644
--- a/libavfilter/audio.c
+++ b/libavfilter/audio.c
@@ -51,8 +51,8 @@ AVFrame *ff_default_get_audio_buffer(AVFilterLink *link, int 
nb_samples)
     int align = av_cpu_max_align();
 
     if (!li->frame_pool) {
-        li->frame_pool = ff_frame_pool_audio_init(av_buffer_allocz, channels,
-                                                  nb_samples, link->format, 
align);
+        li->frame_pool = ff_frame_pool_audio_init(channels, nb_samples,
+                                                  link->format, align);
         if (!li->frame_pool)
             return NULL;
     } else {
@@ -71,8 +71,8 @@ AVFrame *ff_default_get_audio_buffer(AVFilterLink *link, int 
nb_samples)
             pool_format != link->format || pool_align != align) {
 
             ff_frame_pool_uninit(&li->frame_pool);
-            li->frame_pool = ff_frame_pool_audio_init(av_buffer_allocz, 
channels,
-                                                      nb_samples, 
link->format, align);
+            li->frame_pool = ff_frame_pool_audio_init(channels, nb_samples,
+                                                      link->format, align);
             if (!li->frame_pool)
                 return NULL;
         }
diff --git a/libavfilter/framepool.c b/libavfilter/framepool.c
index 15ea66cdc6..d0d8dceffa 100644
--- a/libavfilter/framepool.c
+++ b/libavfilter/framepool.c
@@ -49,8 +49,7 @@ struct FFFramePool {
 
 };
 
-av_cold FFFramePool *ff_frame_pool_video_init(AVBufferRef* (*alloc)(size_t 
size),
-                                              int width, int height,
+av_cold FFFramePool *ff_frame_pool_video_init(int width, int height,
                                               enum AVPixelFormat format,
                                               int align)
 {
@@ -97,7 +96,10 @@ av_cold FFFramePool *ff_frame_pool_video_init(AVBufferRef* 
(*alloc)(size_t size)
     for (i = 0; i < 4 && sizes[i]; i++) {
         if (sizes[i] > SIZE_MAX - align)
             goto fail;
-        pool->pools[i] = av_buffer_pool_init(sizes[i] + align, alloc);
+        pool->pools[i] = av_buffer_pool_init(sizes[i] + align,
+                                             CONFIG_MEMORY_POISONING
+                                                 ? NULL
+                                                 : av_buffer_allocz);
         if (!pool->pools[i])
             goto fail;
     }
@@ -109,8 +111,7 @@ fail:
     return NULL;
 }
 
-av_cold FFFramePool *ff_frame_pool_audio_init(AVBufferRef* (*alloc)(size_t 
size),
-                                              int channels, int nb_samples,
+av_cold FFFramePool *ff_frame_pool_audio_init(int channels, int nb_samples,
                                               enum AVSampleFormat format,
                                               int align)
 {
@@ -137,7 +138,8 @@ av_cold FFFramePool *ff_frame_pool_audio_init(AVBufferRef* 
(*alloc)(size_t size)
 
     if (pool->linesize[0] > SIZE_MAX - align)
         goto fail;
-    pool->pools[0] = av_buffer_pool_init(pool->linesize[0] + align, alloc);
+    pool->pools[0] = av_buffer_pool_init(pool->linesize[0] + align,
+                                         av_buffer_allocz);
     if (!pool->pools[0])
         goto fail;
 
diff --git a/libavfilter/framepool.h b/libavfilter/framepool.h
index 2bef1fe0b6..f18d3c1536 100644
--- a/libavfilter/framepool.h
+++ b/libavfilter/framepool.h
@@ -35,17 +35,13 @@ typedef struct FFFramePool FFFramePool;
 /**
  * Allocate and initialize a video frame pool.
  *
- * @param alloc a function that will be used to allocate new frame buffers when
- * the pool is empty. May be NULL, then the default allocator will be used
- * (av_buffer_alloc()).
  * @param width width of each frame in this pool
  * @param height height of each frame in this pool
  * @param format format of each frame in this pool
  * @param align buffers alignment of each frame in this pool
  * @return newly created video frame pool on success, NULL on error.
  */
-FFFramePool *ff_frame_pool_video_init(AVBufferRef* (*alloc)(size_t size),
-                                      int width,
+FFFramePool *ff_frame_pool_video_init(int width,
                                       int height,
                                       enum AVPixelFormat format,
                                       int align);
@@ -53,17 +49,13 @@ FFFramePool *ff_frame_pool_video_init(AVBufferRef* 
(*alloc)(size_t size),
 /**
  * Allocate and initialize an audio frame pool.
  *
- * @param alloc a function that will be used to allocate new frame buffers when
- * the pool is empty. May be NULL, then the default allocator will be used
- * (av_buffer_alloc()).
  * @param channels channels of each frame in this pool
  * @param nb_samples number of samples of each frame in this pool
  * @param format format of each frame in this pool
  * @param align buffers alignment of each frame in this pool
  * @return newly created audio frame pool on success, NULL on error.
  */
-FFFramePool *ff_frame_pool_audio_init(AVBufferRef* (*alloc)(size_t size),
-                                      int channels,
+FFFramePool *ff_frame_pool_audio_init(int channels,
                                       int samples,
                                       enum AVSampleFormat format,
                                       int align);
diff --git a/libavfilter/video.c b/libavfilter/video.c
index 3f23a12c07..2727b2c28d 100644
--- a/libavfilter/video.c
+++ b/libavfilter/video.c
@@ -71,10 +71,7 @@ AVFrame *ff_default_get_video_buffer2(AVFilterLink *link, 
int w, int h, int alig
     }
 
     if (!li->frame_pool) {
-        li->frame_pool = ff_frame_pool_video_init(CONFIG_MEMORY_POISONING
-                                                     ? NULL
-                                                     : av_buffer_allocz,
-                                                  w, h, link->format, align);
+        li->frame_pool = ff_frame_pool_video_init(w, h, link->format, align);
         if (!li->frame_pool)
             return NULL;
     } else {
@@ -89,10 +86,7 @@ AVFrame *ff_default_get_video_buffer2(AVFilterLink *link, 
int w, int h, int alig
             pool_format != link->format || pool_align != align) {
 
             ff_frame_pool_uninit(&li->frame_pool);
-            li->frame_pool = ff_frame_pool_video_init(CONFIG_MEMORY_POISONING
-                                                         ? NULL
-                                                         : av_buffer_allocz,
-                                                      w, h, link->format, 
align);
+            li->frame_pool = ff_frame_pool_video_init(w, h, link->format, 
align);
             if (!li->frame_pool)
                 return NULL;
         }
-- 
2.52.0


>From 4187ad6b4e623cbac9059e63e5d9cfe94e724f5f Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Tue, 24 Mar 2026 18:02:32 +0100
Subject: [PATCH 03/13] avfilter/framepool: add ff_frame_pool_*_reinit()
 helpers

This moves the check-uninit-reinit logic out of audio.c/video.c and into
framepool.c, where it can be more conveniently re-used by future users.

Signed-off-by: Niklas Haas <[email protected]>
---
 libavfilter/framepool.c | 42 +++++++++++++++++++++++++++++++++++++++++
 libavfilter/framepool.h | 38 +++++++++++++++++++++++++++++++++++++
 2 files changed, 80 insertions(+)

diff --git a/libavfilter/framepool.c b/libavfilter/framepool.c
index d0d8dceffa..2f61463dcb 100644
--- a/libavfilter/framepool.c
+++ b/libavfilter/framepool.c
@@ -291,3 +291,45 @@ av_cold void ff_frame_pool_uninit(FFFramePool **pool)
 
     av_freep(pool);
 }
+
+int ff_frame_pool_video_reinit(FFFramePool **pool,
+                               int width,
+                               int height,
+                               enum AVPixelFormat format,
+                               int align)
+{
+    FFFramePool *cur = *pool;
+    if (cur && cur->type == AVMEDIA_TYPE_VIDEO &&
+        FFALIGN(cur->width, cur->align) == FFALIGN(width, align) &&
+        FFALIGN(cur->height, cur->align) == FFALIGN(height, align) &&
+        cur->format == format && cur->align == align)
+        return 0;
+
+    FFFramePool *new = ff_frame_pool_video_init(width, height, format, align);
+    if (!new)
+        return AVERROR(ENOMEM);
+
+    ff_frame_pool_uninit(&cur);
+    *pool = new;
+    return 0;
+}
+
+int ff_frame_pool_audio_reinit(FFFramePool **pool,
+                               int channels,
+                               int nb_samples,
+                               enum AVSampleFormat format,
+                               int align)
+{
+    FFFramePool *cur = *pool;
+    if (cur && cur->type == AVMEDIA_TYPE_AUDIO && cur->channels == channels &&
+        cur->nb_samples == nb_samples && cur->format == format && cur->align 
== align)
+        return 0;
+
+    FFFramePool *new = ff_frame_pool_audio_init(channels, nb_samples, format, 
align);
+    if (!new)
+        return AVERROR(ENOMEM);
+
+    ff_frame_pool_uninit(&cur);
+    *pool = new;
+    return 0;
+}
diff --git a/libavfilter/framepool.h b/libavfilter/framepool.h
index f18d3c1536..e73ab6cb79 100644
--- a/libavfilter/framepool.h
+++ b/libavfilter/framepool.h
@@ -68,6 +68,44 @@ FFFramePool *ff_frame_pool_audio_init(int channels,
  */
 void ff_frame_pool_uninit(FFFramePool **pool);
 
+/**
+ * Recreate the video frame pool if its current configuration differs from the
+ * provided configuration. If initialization fails, the old pool is kept
+ * unchanged.
+ *
+ * @param pool pointer to the frame pool to be reinitialized, or a pointer to
+ *        NULL to create a new pool.
+ * @param width width of each frame in this pool
+ * @param height height of each frame in this pool
+ * @param format format of each frame in this pool
+ * @param align buffers alignment of each frame in this pool
+ * @return 0 on success, a negative AVERROR otherwise.
+ */
+int ff_frame_pool_video_reinit(FFFramePool **pool,
+                               int width,
+                               int height,
+                               enum AVPixelFormat format,
+                               int align);
+
+/**
+ * Recreate the audio frame pool if its current configuration differs from the
+ * provided configuration. If initialization fails, the old pool is kept
+ * unchanged.
+ *
+ * @param pool pointer to the frame pool to be reinitialized, or a pointer to
+ *        NULL to create a new pool.
+ * @param channels channels of each frame in this pool
+ * @param nb_samples number of samples of each frame in this pool
+ * @param format format of each frame in this pool
+ * @param align buffers alignment of each frame in this pool
+ * @return 0 on success, a negative AVERROR otherwise.
+ */
+int ff_frame_pool_audio_reinit(FFFramePool **pool,
+                               int channels,
+                               int nb_samples,
+                               enum AVSampleFormat format,
+                               int align);
+
 /**
  * Get the video frame pool configuration.
  *
-- 
2.52.0


>From 5e526214e178c7aa25a5401457ea2a14e8132853 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Tue, 24 Mar 2026 18:32:45 +0100
Subject: [PATCH 04/13] avfilter/{audio,video): switch to
 ff_frame_pool_*_reinit()

Signed-off-by: Niklas Haas <[email protected]>
---
 libavfilter/audio.c | 30 +++---------------------------
 libavfilter/video.c | 27 ++-------------------------
 2 files changed, 5 insertions(+), 52 deletions(-)

diff --git a/libavfilter/audio.c b/libavfilter/audio.c
index 308d04cf43..0ac2e58d8c 100644
--- a/libavfilter/audio.c
+++ b/libavfilter/audio.c
@@ -50,33 +50,9 @@ AVFrame *ff_default_get_audio_buffer(AVFilterLink *link, int 
nb_samples)
     int channels = link->ch_layout.nb_channels;
     int align = av_cpu_max_align();
 
-    if (!li->frame_pool) {
-        li->frame_pool = ff_frame_pool_audio_init(channels, nb_samples,
-                                                  link->format, align);
-        if (!li->frame_pool)
-            return NULL;
-    } else {
-        int pool_channels = 0;
-        int pool_nb_samples = 0;
-        int pool_align = 0;
-        enum AVSampleFormat pool_format = AV_SAMPLE_FMT_NONE;
-
-        if (ff_frame_pool_get_audio_config(li->frame_pool,
-                                           &pool_channels, &pool_nb_samples,
-                                           &pool_format, &pool_align) < 0) {
-            return NULL;
-        }
-
-        if (pool_channels != channels || pool_nb_samples < nb_samples ||
-            pool_format != link->format || pool_align != align) {
-
-            ff_frame_pool_uninit(&li->frame_pool);
-            li->frame_pool = ff_frame_pool_audio_init(channels, nb_samples,
-                                                      link->format, align);
-            if (!li->frame_pool)
-                return NULL;
-        }
-    }
+    if (ff_frame_pool_audio_reinit(&li->frame_pool, channels, nb_samples,
+                                   link->format, align) < 0)
+        return NULL;
 
     frame = ff_frame_pool_get(li->frame_pool);
     if (!frame)
diff --git a/libavfilter/video.c b/libavfilter/video.c
index 2727b2c28d..7c63bcac05 100644
--- a/libavfilter/video.c
+++ b/libavfilter/video.c
@@ -50,10 +50,6 @@ AVFrame *ff_default_get_video_buffer2(AVFilterLink *link, 
int w, int h, int alig
 {
     FilterLinkInternal *const li = ff_link_internal(link);
     AVFrame *frame = NULL;
-    int pool_width = 0;
-    int pool_height = 0;
-    int pool_align = 0;
-    enum AVPixelFormat pool_format = AV_PIX_FMT_NONE;
 
     if (li->l.hw_frames_ctx &&
         ((AVHWFramesContext*)li->l.hw_frames_ctx->data)->format == 
link->format) {
@@ -70,27 +66,8 @@ AVFrame *ff_default_get_video_buffer2(AVFilterLink *link, 
int w, int h, int alig
         return frame;
     }
 
-    if (!li->frame_pool) {
-        li->frame_pool = ff_frame_pool_video_init(w, h, link->format, align);
-        if (!li->frame_pool)
-            return NULL;
-    } else {
-        if (ff_frame_pool_get_video_config(li->frame_pool,
-                                           &pool_width, &pool_height,
-                                           &pool_format, &pool_align) < 0) {
-            return NULL;
-        }
-
-        if (FFALIGN(pool_width,  pool_align) != FFALIGN(w, align) ||
-            FFALIGN(pool_height, pool_align) != FFALIGN(h, align) ||
-            pool_format != link->format || pool_align != align) {
-
-            ff_frame_pool_uninit(&li->frame_pool);
-            li->frame_pool = ff_frame_pool_video_init(w, h, link->format, 
align);
-            if (!li->frame_pool)
-                return NULL;
-        }
-    }
+    if (ff_frame_pool_video_reinit(&li->frame_pool, w, h, link->format, align) 
< 0)
+        return NULL;
 
     frame = ff_frame_pool_get(li->frame_pool);
     if (!frame)
-- 
2.52.0


>From 8b6da19d25f1c9de9d3eca70f549ff1843698a8c Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Tue, 24 Mar 2026 18:10:17 +0100
Subject: [PATCH 05/13] avfilter/framepool: update frame dimensions on reinit

The previous logic (ported from libavfilter/video.c) would leave the frame
pool intact if the linesize did not change as a result of changing the frame
dimensions. However, this caused ff_default_get_video_buffer2() to return
frames with the old width/height.

I think this bug was avoided in practice because the only filters to actually
support changing the resolution at runtime already always explicitly overrode
the width/height of allocated output buffers by the link properties.

Signed-off-by: Niklas Haas <[email protected]>
---
 libavfilter/framepool.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/libavfilter/framepool.c b/libavfilter/framepool.c
index 2f61463dcb..9899ade177 100644
--- a/libavfilter/framepool.c
+++ b/libavfilter/framepool.c
@@ -303,7 +303,11 @@ int ff_frame_pool_video_reinit(FFFramePool **pool,
         FFALIGN(cur->width, cur->align) == FFALIGN(width, align) &&
         FFALIGN(cur->height, cur->align) == FFALIGN(height, align) &&
         cur->format == format && cur->align == align)
+    {
+        cur->width = width;
+        cur->height = height;
         return 0;
+    }
 
     FFFramePool *new = ff_frame_pool_video_init(width, height, format, align);
     if (!new)
-- 
2.52.0


>From f9a8d48cb3f68ffd786a39c49104cbec7de95bf5 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Tue, 24 Mar 2026 18:14:13 +0100
Subject: [PATCH 06/13] avfilter/framepool: nuke ff_frame_pool_get_*_config

This helper is of dubious utility - it was only used to reinitialize the
frame pools, which is better handled by `ff_frame_pool_reinit()`, and at
present only serves to make extending the API harder.

Users who really need to randomly query the state of the frame pool can
already keep track of the values they set.

Signed-off-by: Niklas Haas <[email protected]>
---
 libavfilter/framepool.c | 38 --------------------------------------
 libavfilter/framepool.h | 31 -------------------------------
 2 files changed, 69 deletions(-)

diff --git a/libavfilter/framepool.c b/libavfilter/framepool.c
index 9899ade177..9e1c1a3758 100644
--- a/libavfilter/framepool.c
+++ b/libavfilter/framepool.c
@@ -150,44 +150,6 @@ fail:
     return NULL;
 }
 
-int ff_frame_pool_get_video_config(FFFramePool *pool,
-                                   int *width,
-                                   int *height,
-                                   enum AVPixelFormat *format,
-                                   int *align)
-{
-    if (!pool)
-        return AVERROR(EINVAL);
-
-    av_assert0(pool->type == AVMEDIA_TYPE_VIDEO);
-
-    *width = pool->width;
-    *height = pool->height;
-    *format = pool->format;
-    *align = pool->align;
-
-    return 0;
-}
-
-int ff_frame_pool_get_audio_config(FFFramePool *pool,
-                                   int *channels,
-                                   int *nb_samples,
-                                   enum AVSampleFormat *format,
-                                   int *align)
-{
-    if (!pool)
-        return AVERROR(EINVAL);
-
-    av_assert0(pool->type == AVMEDIA_TYPE_AUDIO);
-
-    *channels = pool->channels;
-    *nb_samples = pool->nb_samples;
-    *format = pool->format;
-    *align = pool->align;
-
-    return 0;
-}
-
 AVFrame *ff_frame_pool_get(FFFramePool *pool)
 {
     int i;
diff --git a/libavfilter/framepool.h b/libavfilter/framepool.h
index e73ab6cb79..f16c9b9d4f 100644
--- a/libavfilter/framepool.h
+++ b/libavfilter/framepool.h
@@ -106,37 +106,6 @@ int ff_frame_pool_audio_reinit(FFFramePool **pool,
                                enum AVSampleFormat format,
                                int align);
 
-/**
- * Get the video frame pool configuration.
- *
- * @param width width of each frame in this pool
- * @param height height of each frame in this pool
- * @param format format of each frame in this pool
- * @param align buffers alignment of each frame in this pool
- * @return 0 on success, a negative AVERROR otherwise.
- */
-int ff_frame_pool_get_video_config(FFFramePool *pool,
-                                   int *width,
-                                   int *height,
-                                   enum AVPixelFormat *format,
-                                   int *align);
-
-/**
- * Get the audio frame pool configuration.
- *
- * @param channels channels of each frame in this pool
- * @param nb_samples number of samples of each frame in this pool
- * @param format format of each frame in this pool
- * @param align buffers alignment of each frame in this pool
- * @return 0 on success, a negative AVERROR otherwise.
- */
-int ff_frame_pool_get_audio_config(FFFramePool *pool,
-                                   int *channels,
-                                   int *nb_samples,
-                                   enum AVSampleFormat *format,
-                                   int *align);
-
-
 /**
  * Allocate a new AVFrame, reusing old buffers from the pool when available.
  * This function may be called simultaneously from multiple threads.
-- 
2.52.0


>From e6c3ec06228c28900312d81ec9f384bb0d3a5c19 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Tue, 24 Mar 2026 18:41:51 +0100
Subject: [PATCH 07/13] avfilter/framepool: add function to allocate individual
 video plane

I need this inside libswscale, but it can also replace the loop inside the
alloc code.

I considered making it usable for audio planes as well, but it runs into
awkward questions regarding data vs extended_data, so I decided to cross that
bridge when we get to it.

Signed-off-by: Niklas Haas <[email protected]>
---
 libavfilter/framepool.c | 33 +++++++++++++++++++++++++--------
 libavfilter/framepool.h |  6 ++++++
 2 files changed, 31 insertions(+), 8 deletions(-)

diff --git a/libavfilter/framepool.c b/libavfilter/framepool.c
index 9e1c1a3758..18f45621ad 100644
--- a/libavfilter/framepool.c
+++ b/libavfilter/framepool.c
@@ -150,6 +150,30 @@ fail:
     return NULL;
 }
 
+int ff_frame_pool_get_video_plane(FFFramePool *pool, AVFrame *frame, int plane)
+{
+    av_assert0(plane >= 0 && plane < FF_ARRAY_ELEMS(pool->pools));
+    if (pool->type != AVMEDIA_TYPE_VIDEO || !pool->pools[plane])
+        return AVERROR(EINVAL);
+
+    AVBufferRef *buf = av_buffer_pool_get(pool->pools[plane]);
+    if (!buf)
+        return AVERROR(ENOMEM);
+
+    for (int i = 0; i < FF_ARRAY_ELEMS(frame->buf); i++) {
+        if (frame->buf[i])
+            continue;
+        frame->buf[i] = buf;
+        frame->data[i] = (uint8_t *) FFALIGN((uintptr_t) buf->data, 
pool->align);
+        frame->linesize[plane] = pool->linesize[plane];
+        return 0;
+    }
+
+    /* No free buffer slot found? */
+    av_buffer_unref(&buf);
+    return AVERROR(EINVAL);
+}
+
 AVFrame *ff_frame_pool_get(FFFramePool *pool)
 {
     int i;
@@ -173,15 +197,8 @@ AVFrame *ff_frame_pool_get(FFFramePool *pool)
         frame->format = pool->format;
 
         for (i = 0; i < 4; i++) {
-            frame->linesize[i] = pool->linesize[i];
-            if (!pool->pools[i])
-                break;
-
-            frame->buf[i] = av_buffer_pool_get(pool->pools[i]);
-            if (!frame->buf[i])
+            if (ff_frame_pool_get_video_plane(pool, frame, i) < 0)
                 goto fail;
-
-            frame->data[i] = (uint8_t 
*)FFALIGN((uintptr_t)frame->buf[i]->data, pool->align);
         }
 
         if (desc->flags & AV_PIX_FMT_FLAG_PAL) {
diff --git a/libavfilter/framepool.h b/libavfilter/framepool.h
index f16c9b9d4f..9024044ee3 100644
--- a/libavfilter/framepool.h
+++ b/libavfilter/framepool.h
@@ -114,5 +114,11 @@ int ff_frame_pool_audio_reinit(FFFramePool **pool,
  */
 AVFrame *ff_frame_pool_get(FFFramePool *pool);
 
+/**
+ * Allocate only the given video plane from the pool, and attach it to `frame`.
+ *
+ * @return 0 on success, a negative AVERROR on error.
+ */
+int ff_frame_pool_get_video_plane(FFFramePool *pool, AVFrame *frame, int 
plane);
 
 #endif /* AVFILTER_FRAMEPOOL_H */
-- 
2.52.0


>From a82bce604fd8bb73eeaddd92ebe4e24e390d46b4 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Tue, 24 Mar 2026 19:35:38 +0100
Subject: [PATCH 08/13] swscale: add FFFramePool and use it for allocating
 planes

The major consequence of this is that we start allocating buffers per plane,
instead of allocating one contiguous buffer. This makes the no-op/refcopy
case slightly slower, but doesn't meaningfully affect the rest:

yuva444p -> yuva444p, time=157/1000 us (ref=78/1000 us), speedup=0.497x slower
Overall speedup=1.016x faster, min=0.983x max=1.092x

However, this is a necessary consequence of the desire to allow partial plane
allocations / single plane refcopies. This slowdown also does not affect
vf_scale, which already uses avfilter/framepool.c (via ff_get_video_buffer).

Signed-off-by: Niklas Haas <[email protected]>
---
 libswscale/Makefile           |  1 +
 libswscale/framepool.c        | 19 +++++++++++++++++++
 libswscale/swscale.c          | 26 +++++++++++++++++++++++++-
 libswscale/swscale_internal.h |  3 +++
 libswscale/utils.c            |  1 +
 5 files changed, 49 insertions(+), 1 deletion(-)
 create mode 100644 libswscale/framepool.c

diff --git a/libswscale/Makefile b/libswscale/Makefile
index f33754ce67..f426c89934 100644
--- a/libswscale/Makefile
+++ b/libswscale/Makefile
@@ -12,6 +12,7 @@ OBJS = alphablend.o                                     \
        hscale_fast_bilinear.o                           \
        filters.o                                        \
        format.o                                         \
+       framepool.o                                      \
        gamma.o                                          \
        graph.o                                          \
        input.o                                          \
diff --git a/libswscale/framepool.c b/libswscale/framepool.c
new file mode 100644
index 0000000000..2d6bcb15de
--- /dev/null
+++ b/libswscale/framepool.c
@@ -0,0 +1,19 @@
+/*
+ * 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 "libavfilter/framepool.c"
diff --git a/libswscale/swscale.c b/libswscale/swscale.c
index b2090209d4..607b19f23e 100644
--- a/libswscale/swscale.c
+++ b/libswscale/swscale.c
@@ -1215,6 +1215,24 @@ void sws_frame_end(SwsContext *sws)
     c->src_ranges.nb_ranges = 0;
 }
 
+static int frame_alloc_buffers(SwsContext *sws, AVFrame *frame)
+{
+    SwsInternal *c = sws_internal(sws);
+    FFFramePool *pool = c->frame_pool;
+
+    av_assert0(!frame->hw_frames_ctx);
+    const int nb_planes = av_pix_fmt_count_planes(frame->format);
+    for (int i = 0; i < nb_planes; i++) {
+        int ret = ff_frame_pool_get_video_plane(pool, frame, i);
+        if (ret < 0) {
+            av_frame_unref(frame);
+            return ret;
+        }
+    }
+
+    return 0;
+}
+
 int sws_frame_start(SwsContext *sws, AVFrame *dst, const AVFrame *src)
 {
     SwsInternal *c = sws_internal(sws);
@@ -1390,7 +1408,7 @@ int sws_scale_frame(SwsContext *sws, AVFrame *dst, const 
AVFrame *src)
     if (src->buf[0] && top->noop && (!bot || bot->noop))
         return frame_ref(dst, src);
 
-    ret = av_frame_get_buffer(dst, 0);
+    ret = frame_alloc_buffers(sws, dst);
     if (ret < 0)
         return ret;
 
@@ -1457,6 +1475,12 @@ int sws_frame_setup(SwsContext *ctx, const AVFrame *dst, 
const AVFrame *src)
         if (ret < 0)
             return ret;
 #endif
+    } else {
+        /* Software frames */
+        ret = ff_frame_pool_video_reinit(&s->frame_pool, dst->width, 
dst->height,
+                                         dst->format, av_cpu_max_align());
+        if (ret < 0)
+            return ret;
     }
 
     for (int field = 0; field < 2; field++) {
diff --git a/libswscale/swscale_internal.h b/libswscale/swscale_internal.h
index 7d5eeae203..6a73997f10 100644
--- a/libswscale/swscale_internal.h
+++ b/libswscale/swscale_internal.h
@@ -28,6 +28,7 @@
 #include "swscale.h"
 #include "graph.h"
 
+#include "libavfilter/framepool.h"
 #include "libavutil/avassert.h"
 #include "libavutil/common.h"
 #include "libavutil/frame.h"
@@ -699,6 +700,8 @@ struct SwsInternal {
     void *hw_priv; /* refstruct */
 
     int is_legacy_init;
+
+    FFFramePool *frame_pool; /* for sws_scale_frame() data allocations */
 };
 //FIXME check init (where 0)
 
diff --git a/libswscale/utils.c b/libswscale/utils.c
index 84d4834903..41c1cc5bb6 100644
--- a/libswscale/utils.c
+++ b/libswscale/utils.c
@@ -2293,6 +2293,7 @@ void sws_freeContext(SwsContext *sws)
 
     for (i = 0; i < FF_ARRAY_ELEMS(c->graph); i++)
         ff_sws_graph_free(&c->graph[i]);
+    ff_frame_pool_uninit(&c->frame_pool);
 
     for (i = 0; i < c->nb_slice_ctx; i++)
         sws_freeContext(c->slice_ctx[i]);
-- 
2.52.0


>From 1d04c142c35cb5e4eecd997f4c4ef0aebfebcfdf Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Wed, 25 Mar 2026 11:14:06 +0100
Subject: [PATCH 09/13] swscale: clean up allocated frames on error

Matches the semantics of sws_frame_begin(), which also cleans up any
allocated buffers on error.

This is an issue introduced by the commit that allowed ff_sws_graph_run()
to fail in the first place.

Fixes: 563cc8216bd9253d2563641341f4af611aaf9554
---
 libswscale/swscale.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/libswscale/swscale.c b/libswscale/swscale.c
index 607b19f23e..46571e363f 100644
--- a/libswscale/swscale.c
+++ b/libswscale/swscale.c
@@ -1366,7 +1366,7 @@ static int frame_ref(AVFrame *dst, const AVFrame *src)
 
 int sws_scale_frame(SwsContext *sws, AVFrame *dst, const AVFrame *src)
 {
-    int ret;
+    int ret, allocated = 0;
     SwsInternal *c = sws_internal(sws);
     if (!src || !dst)
         return AVERROR(EINVAL);
@@ -1411,12 +1411,16 @@ int sws_scale_frame(SwsContext *sws, AVFrame *dst, 
const AVFrame *src)
     ret = frame_alloc_buffers(sws, dst);
     if (ret < 0)
         return ret;
+    allocated = 1;
 
 process_frame:
     for (int field = 0; field < (bot ? 2 : 1); field++) {
         ret = ff_sws_graph_run(c->graph[field], dst, src);
-        if (ret < 0)
+        if (ret < 0) {
+            if (allocated)
+                av_frame_unref(dst);
             return ret;
+        }
     }
 
     return 0;
-- 
2.52.0


>From b66e50205d267879e2a8d8f6f49c207a5fd589b7 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Wed, 25 Mar 2026 11:49:45 +0100
Subject: [PATCH 10/13] swscale/graph: add optimal alignment/padding hints

Allows the pass buffer allocator to make smarter decisions based on the actual
alignment requirements of the specific pass.

Signed-off-by: Niklas Haas <[email protected]>
---
 libswscale/graph.c | 3 ++-
 libswscale/graph.h | 4 ++++
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/libswscale/graph.c b/libswscale/graph.c
index 1c36ea3957..6836f8cf60 100644
--- a/libswscale/graph.c
+++ b/libswscale/graph.c
@@ -151,8 +151,9 @@ int ff_sws_graph_add_pass(SwsGraph *graph, enum 
AVPixelFormat fmt,
     }
 
     /* Align output buffer to include extra slice padding */
-    pass->output->width  = pass->width;
     pass->output->height = pass->slice_h * pass->num_slices;
+    pass->output->width  = pass->width;
+    pass->output->width_align = 1;
 
     ret = av_dynarray_add_nofree(&graph->passes, &graph->num_passes, pass);
     if (ret < 0)
diff --git a/libswscale/graph.h b/libswscale/graph.h
index a139afe2ef..886f0d2996 100644
--- a/libswscale/graph.h
+++ b/libswscale/graph.h
@@ -61,6 +61,10 @@ typedef struct SwsPassBuffer {
 
     int width, height; /* dimensions of this buffer */
     AVFrame *avframe;  /* backing storage for `frame` */
+
+    /* Optional allocation hints for optimal performance */
+    int width_align;   /* Align width to multiple of this */
+    int width_pad;     /* Extra padding pixels */
 } SwsPassBuffer;
 
 /**
-- 
2.52.0


>From 01711c6171a2088cbb0b27e27957876e62bad71c Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Wed, 25 Mar 2026 12:04:09 +0100
Subject: [PATCH 11/13] swscale/ops_dispatch: forward correct pass alignment

As a consequence of the fact that the frame pool API doesn't let us directly
access the linesize, we have to "un-translate" the over_read/write back to
the nearest multiple of the pixel size.

Signed-off-by: Niklas Haas <[email protected]>
---
 libswscale/ops_dispatch.c | 25 ++++++++++++++++++++++---
 1 file changed, 22 insertions(+), 3 deletions(-)

diff --git a/libswscale/ops_dispatch.c b/libswscale/ops_dispatch.c
index f842196265..60ef405286 100644
--- a/libswscale/ops_dispatch.c
+++ b/libswscale/ops_dispatch.c
@@ -383,6 +383,19 @@ static int rw_pixel_bits(const SwsOp *op)
     return elems * size * bits;
 }
 
+static void align_pass(SwsPass *pass, int block_size, int over_rw, int 
pixel_bits)
+{
+    if (!pass)
+        return;
+
+    /* Add at least as many pixels as needed to cover the padding requirement 
*/
+    const int pad = (over_rw * 8 + pixel_bits - 1) / pixel_bits;
+
+    SwsPassBuffer *buf = pass->output;
+    buf->width_align = FFMAX(buf->width_align, block_size);
+    buf->width_pad = FFMAX(buf->width_pad, pad);
+}
+
 static int compile(SwsGraph *graph, const SwsOpList *ops, SwsPass *input,
                    SwsPass **output)
 {
@@ -459,9 +472,15 @@ static int compile(SwsGraph *graph, const SwsOpList *ops, 
SwsPass *input,
         p->filter_size = filter->filter_size;
     }
 
-    return ff_sws_graph_add_pass(graph, dst->format, dst->width, dst->height,
-                                 input, p->comp.slice_align, op_pass_run,
-                                 op_pass_setup, p, op_pass_free, output);
+    ret = ff_sws_graph_add_pass(graph, dst->format, dst->width, dst->height,
+                                input, p->comp.slice_align, op_pass_run,
+                                op_pass_setup, p, op_pass_free, output);
+    if (ret < 0)
+        return ret;
+
+    align_pass(input,   p->comp.block_size, p->comp.over_read,  
p->pixel_bits_in);
+    align_pass(*output, p->comp.block_size, p->comp.over_write, 
p->pixel_bits_out);
+    return 0;
 
 fail:
     op_pass_free(p);
-- 
2.52.0


>From e862a6069f2da04a7d860179aa59c22074c996f6 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Wed, 25 Mar 2026 14:14:44 +0100
Subject: [PATCH 12/13] swscale/ops_dispatch: cosmetic

Signed-off-by: Niklas Haas <[email protected]>
---
 libswscale/ops_dispatch.c | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/libswscale/ops_dispatch.c b/libswscale/ops_dispatch.c
index 60ef405286..9abb3df8ec 100644
--- a/libswscale/ops_dispatch.c
+++ b/libswscale/ops_dispatch.c
@@ -408,9 +408,10 @@ static int compile(SwsGraph *graph, const SwsOpList *ops, 
SwsPass *input,
     if (ret < 0)
         goto fail;
 
+    const SwsCompiledOp *comp = &p->comp;
     const SwsFormat *dst = &ops->dst;
     if (p->comp.opaque) {
-        SwsCompiledOp c = p->comp;
+        SwsCompiledOp c = *comp;
         av_free(p);
         return ff_sws_graph_add_pass(graph, dst->format, dst->width, 
dst->height,
                                      input, c.slice_align, c.func_opaque,
@@ -426,8 +427,8 @@ static int compile(SwsGraph *graph, const SwsOpList *ops, 
SwsPass *input,
     p->exec_base = (SwsOpExec) {
         .width  = dst->width,
         .height = dst->height,
-        .block_size_in  = p->comp.block_size * p->pixel_bits_in  >> 3,
-        .block_size_out = p->comp.block_size * p->pixel_bits_out >> 3,
+        .block_size_in  = comp->block_size * p->pixel_bits_in  >> 3,
+        .block_size_out = comp->block_size * p->pixel_bits_out >> 3,
     };
 
     for (int i = 0; i < 4; i++) {
@@ -473,13 +474,13 @@ static int compile(SwsGraph *graph, const SwsOpList *ops, 
SwsPass *input,
     }
 
     ret = ff_sws_graph_add_pass(graph, dst->format, dst->width, dst->height,
-                                input, p->comp.slice_align, op_pass_run,
+                                input, comp->slice_align, op_pass_run,
                                 op_pass_setup, p, op_pass_free, output);
     if (ret < 0)
         return ret;
 
-    align_pass(input,   p->comp.block_size, p->comp.over_read,  
p->pixel_bits_in);
-    align_pass(*output, p->comp.block_size, p->comp.over_write, 
p->pixel_bits_out);
+    align_pass(input,   comp->block_size, comp->over_read,  p->pixel_bits_in);
+    align_pass(*output, comp->block_size, comp->over_write, p->pixel_bits_out);
     return 0;
 
 fail:
-- 
2.52.0


>From 9cbbb1c55e16ca823a54dff97e0e3c9e3a43196f Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Wed, 25 Mar 2026 14:09:30 +0100
Subject: [PATCH 13/13] swscale: align allocated frame buffers to SwsPass hints

This avoids hitting the slow memcpy fallback paths altogether, whenever
swscale.c is handling plane allocation.

Signed-off-by: Niklas Haas <[email protected]>
---
 libswscale/graph.c   | 11 +++++++++++
 libswscale/graph.h   |  5 +++++
 libswscale/swscale.c | 24 +++++++++++++++++-------
 3 files changed, 33 insertions(+), 7 deletions(-)

diff --git a/libswscale/graph.c b/libswscale/graph.c
index 6836f8cf60..2020fcf8c7 100644
--- a/libswscale/graph.c
+++ b/libswscale/graph.c
@@ -38,6 +38,17 @@
 #include "graph.h"
 #include "ops.h"
 
+int ff_sws_pass_aligned_width(const SwsPass *pass, int width)
+{
+    if (!pass)
+        return width;
+
+    size_t aligned_w = width;
+    aligned_w = FFALIGN(aligned_w, pass->output->width_align);
+    aligned_w += pass->output->width_pad;
+    return aligned_w <= INT_MAX ? aligned_w : width;
+}
+
 /* Allocates one buffer per plane */
 static int frame_alloc_planes(AVFrame *dst)
 {
diff --git a/libswscale/graph.h b/libswscale/graph.h
index 886f0d2996..116f2a89bf 100644
--- a/libswscale/graph.h
+++ b/libswscale/graph.h
@@ -110,6 +110,11 @@ struct SwsPass {
     void *priv;
 };
 
+/**
+ * Align `width` to the optimal size for `pass`.
+ */
+int ff_sws_pass_aligned_width(const SwsPass *pass, int width);
+
 /**
  * Filter graph, which represents a 'baked' pixel format conversion.
  */
diff --git a/libswscale/swscale.c b/libswscale/swscale.c
index 46571e363f..8b90c419e1 100644
--- a/libswscale/swscale.c
+++ b/libswscale/swscale.c
@@ -1479,14 +1479,9 @@ int sws_frame_setup(SwsContext *ctx, const AVFrame *dst, 
const AVFrame *src)
         if (ret < 0)
             return ret;
 #endif
-    } else {
-        /* Software frames */
-        ret = ff_frame_pool_video_reinit(&s->frame_pool, dst->width, 
dst->height,
-                                         dst->format, av_cpu_max_align());
-        if (ret < 0)
-            return ret;
     }
 
+    int dst_width = dst->width;
     for (int field = 0; field < 2; field++) {
         SwsFormat src_fmt = ff_fmt_from_frame(src, field);
         SwsFormat dst_fmt = ff_fmt_from_frame(dst, field);
@@ -1512,12 +1507,20 @@ int sws_frame_setup(SwsContext *ctx, const AVFrame 
*dst, const AVFrame *src)
             goto fail;
         }
 
-        if (s->graph[field]->incomplete && ctx->flags & SWS_STRICT) {
+        const SwsGraph *graph = s->graph[field];
+        if (graph->incomplete && ctx->flags & SWS_STRICT) {
             err_msg = "Incomplete scaling graph";
             ret = AVERROR(EINVAL);
             goto fail;
         }
 
+        if (!graph->noop) {
+            av_assert0(graph->num_passes);
+            const SwsPass *last_pass = graph->passes[graph->num_passes - 1];
+            const int aligned_w = ff_sws_pass_aligned_width(last_pass, 
dst->width);
+            dst_width = FFMAX(dst_width, aligned_w);
+        }
+
         if (!src_fmt.interlaced) {
             ff_sws_graph_free(&s->graph[FIELD_BOTTOM]);
             break;
@@ -1540,6 +1543,13 @@ int sws_frame_setup(SwsContext *ctx, const AVFrame *dst, 
const AVFrame *src)
         return ret;
     }
 
+    if (!dst->hw_frames_ctx) {
+        ret = ff_frame_pool_video_reinit(&s->frame_pool, dst_width, 
dst->height,
+                                         dst->format, av_cpu_max_align());
+        if (ret < 0)
+            return ret;
+    }
+
     return 0;
 }
 
-- 
2.52.0

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

Reply via email to