PR #22972 opened by AdityaTeltia
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22972
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22972.patch

Fixes #20153

**Summary of changes:**
This PR optimizes `vf_alphamerge` by eliminating an expensive `memcpy` for 
planar pixel formats.

Previously, `vf_alphamerge` forced a full copy of the main video frame using 
`ff_framesync_dualinput_get_writable` and explicitly copied the alpha plane 
data using `av_image_copy_plane()`. 

This patch updates the filter to use `ff_framesync_dualinput_get`. For planar 
formats, it dynamically acquires the `AVBufferRef` backing the incoming alpha 
frame's Y plane and appends it to `main_buf->buf` (or `extended_buf`). It then 
overrides the pointers (`data[A]` and `linesize[A]`) to point directly at the 
data in the incoming alpha frame. This completely eliminates the memory copy 
while ensuring the memory lives on through standard FFmpeg frame reference 
counting.

For packed RGB formats where the alpha channel is interleaved, we explicitly 
call `av_frame_make_writable(main_buf)` and retain the standard pixel-by-pixel 
copying loop.

**Performance Impact:**
Local benchmarking merging two 4K (3840x2160) streams at 60fps for 30 seconds 
(1,800 frames) using a null muxer demonstrates a significant reduction in 
memory bandwidth and CPU time overhead:

- **Old Code (Memory Copy)**: 7.422s Wall-Clock (20.97s User CPU)
- **New Code (Zero-Copy)**: 4.872s Wall-Clock (16.75s User CPU)

*Result:* **~34% reduction in total execution time** (a ~1.52x speedup) for the 
4K `alphamerge` operation, freeing up critical memory bandwidth for other 
pipeline stages.

**Verification:**
Passes both `fate-filter-alphaextract_alphamerge_yuv` and 
`fate-filter-alphaextract_alphamerge_rgb` flawlessly.



>From 01870ec320c2b3f3528e4ddfcabe2b898b514e7a Mon Sep 17 00:00:00 2001
From: AdityaTeltia <[email protected]>
Date: Thu, 30 Apr 2026 21:33:05 +0530
Subject: [PATCH] avfilter/vf_alphamerge: use buffer refcounting instead of
 copying alpha plane

---
 libavfilter/vf_alphamerge.c | 62 +++++++++++++++++++++++++++++++++----
 1 file changed, 56 insertions(+), 6 deletions(-)

diff --git a/libavfilter/vf_alphamerge.c b/libavfilter/vf_alphamerge.c
index b5c7b3d9af..725554256c 100644
--- a/libavfilter/vf_alphamerge.c
+++ b/libavfilter/vf_alphamerge.c
@@ -26,6 +26,7 @@
 #include <string.h>
 
 #include "libavutil/imgutils.h"
+#include "libavutil/mem.h"
 #include "libavutil/opt.h"
 #include "libavutil/pixfmt.h"
 #include "avfilter.h"
@@ -54,7 +55,7 @@ static int do_alphamerge(FFFrameSync *fs)
     AVFrame *main_buf, *alpha_buf;
     int ret;
 
-    ret = ff_framesync_dualinput_get_writable(fs, &main_buf, &alpha_buf);
+    ret = ff_framesync_dualinput_get(fs, &main_buf, &alpha_buf);
     if (ret < 0)
         return ret;
     main_buf->alpha_mode = outlink->alpha_mode;
@@ -70,6 +71,13 @@ static int do_alphamerge(FFFrameSync *fs)
     if (s->is_packed_rgb) {
         int x, y;
         uint8_t *pin, *pout;
+
+        ret = av_frame_make_writable(main_buf);
+        if (ret < 0) {
+            av_frame_free(&main_buf);
+            return ret;
+        }
+
         for (y = 0; y < main_buf->height; y++) {
             pin = alpha_buf->data[0] + y * alpha_buf->linesize[0];
             pout = main_buf->data[0] + y * main_buf->linesize[0] + 
s->rgba_map[A];
@@ -80,11 +88,53 @@ static int do_alphamerge(FFFrameSync *fs)
             }
         }
     } else {
-        const int main_linesize = main_buf->linesize[A];
-        const int alpha_linesize = alpha_buf->linesize[Y];
-        av_image_copy_plane(main_buf->data[A], main_linesize,
-                            alpha_buf->data[Y], alpha_linesize,
-                            FFMIN(main_linesize, alpha_linesize), 
alpha_buf->height);
+        AVBufferRef *alpha_buf_ref = av_frame_get_plane_buffer(alpha_buf, Y);
+        if (alpha_buf_ref) {
+            int i;
+            for (i = 0; i < FF_ARRAY_ELEMS(main_buf->buf); i++) {
+                if (!main_buf->buf[i]) {
+                    main_buf->buf[i] = av_buffer_ref(alpha_buf_ref);
+                    if (!main_buf->buf[i]) {
+                        av_frame_free(&main_buf);
+                        return AVERROR(ENOMEM);
+                    }
+                    break;
+                }
+            }
+            if (i == FF_ARRAY_ELEMS(main_buf->buf)) {
+                AVBufferRef **tmp = av_realloc_array(main_buf->extended_buf,
+                                                     main_buf->nb_extended_buf 
+ 1,
+                                                     sizeof(*tmp));
+                if (!tmp) {
+                    av_frame_free(&main_buf);
+                    return AVERROR(ENOMEM);
+                }
+                main_buf->extended_buf = tmp;
+
+                main_buf->extended_buf[main_buf->nb_extended_buf] = 
av_buffer_ref(alpha_buf_ref);
+                if (!main_buf->extended_buf[main_buf->nb_extended_buf]) {
+                    av_frame_free(&main_buf);
+                    return AVERROR(ENOMEM);
+                }
+                main_buf->nb_extended_buf++;
+            }
+
+            main_buf->data[A] = alpha_buf->data[Y];
+            main_buf->linesize[A] = alpha_buf->linesize[Y];
+        } else {
+            const int main_linesize = main_buf->linesize[A];
+            const int alpha_linesize = alpha_buf->linesize[Y];
+
+            ret = av_frame_make_writable(main_buf);
+            if (ret < 0) {
+                av_frame_free(&main_buf);
+                return ret;
+            }
+
+            av_image_copy_plane(main_buf->data[A], main_linesize,
+                                alpha_buf->data[Y], alpha_linesize,
+                                FFMIN(main_linesize, alpha_linesize), 
alpha_buf->height);
+        }
     }
 
     return ff_filter_frame(ctx->outputs[0], main_buf);
-- 
2.52.0

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

Reply via email to