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

With pre-analysis enabled, either by `-preanalysis 1` or by `-usage 
high_quality`, the AMF encoders hang or fail on most inputs other than NV12 
software frames. Three commits:

- **`avcodec/amfenc: copy hardware input frames for pre-analysis`**
  - D3D11 input from a d3d11va decoder or from hwupload produces no packet and 
waits forever.
  - DXVA2 input loses frames (87 to 106 of 120).
  - Causes:
    - AMF needs `D3D11_BIND_SHADER_RESOURCE` for pre-analysis (AMF#455). 
Without it, the encoder stalls without returning an error.
    - AMF also stalls on texture-array slices that do have the flag (AMF#631).
    - The encoder holds each wrapped frame for the lookahead window, which 
drains a decoder's fixed-size pool.
  - Fix: the commit reads whether pre-analysis is enabled after `Init()`. If it 
is, input that lacks the flag or comes from a fixed-size pool is copied into an 
`AllocSurface()` surface, and the source frame is released at once.
- **`avcodec/amfenc: retry Init() without the usage's default pre-analysis`**
  - `-usage high_quality` enables pre-analysis, and `Init()` then fails with 
P010, X2BGR10, BGRA and RGBA input (AMF#399).
  - Fix: when `-preanalysis` is not set, `Init()` is retried once without 
pre-analysis. I did not use a format list: the formats pre-analysis accepts 
differ from the documentation (YUV420P and ARGB work as well), and AMD said 
that `Init()` is the final validator.
  - An explicit `-preanalysis 1` is passed on unchanged.
- **`avcodec/amfenc: submit YUV420P input as NV12`**
  - With pre-analysis on YUV420P input, the encoder intermittently hangs: 3 of 
20 runs with av1_amf, 1 of 20 with h264_amf.
  - The cause is AMF's own conversion to NV12 combined with the 1 ms query 
timeout (AMF#631, with a standalone repro).
  - Fix: the chroma planes are interleaved during the host copy that is already 
done.
  - This is a workaround for the AMD bug, with a TODO naming the issue. Output 
is bit-identical to before, and CPU time is unchanged.

Results on Windows 11, RX 9070 XT, driver 32.0.32015.7029, packets out of 120 
(60 for P010):

| input, encoder, setting | master | this PR |
|---|---|---|
| d3d11va decode, h264/hevc/av1, `-preanalysis 1` | 0 | 120 |
| d3d11va decode, h264, `-usage high_quality` | 0 | 120 |
| hwupload D3D11, h264, `-preanalysis 1` | 0 | 120 |
| DXVA2 decode, h264, `-preanalysis 1` | 90 | 120 |
| P010 software, hevc, `-usage high_quality` | 0 (Init) | 60 |
| BGRA software, av1, `-usage high_quality` | 0 (Init) | 120 |
| YUV420P software, av1, `-preanalysis 1`, 20 runs | 3 hangs | 0 hangs |

- The encoded quality of the copied input matches software input with 
pre-analysis: 29.34 dB PSNR in all three codecs.
- Output without pre-analysis, and with NV12 or AMF-surface input, is unchanged.

Related PRs:
- #24696 changes the same lines of `amf_submit_frame()` for RGB render-target 
textures. Whichever is merged second needs a trivial rebase.
- #25166 sizes the queue from the lookahead depth. I left a note there, because 
the depth read before `Init()` is 0.

Written with Claude Opus 5.5 in Claude Code; `Assisted-by` trailers on the 
commits.


>From 09e57c0d036e07b10fa63f38ba11a6526c9a059a Mon Sep 17 00:00:00 2001
From: Julius Bairaktaris <[email protected]>
Date: Mon, 26 Oct 2026 14:48:02 +0100
Subject: [PATCH 1/3] avcodec/amfenc: copy hardware input frames for
 pre-analysis

With pre-analysis enabled, h264_amf, hevc_amf and av1_amf hang on most
D3D11 and DXVA2 input: ffmpeg -hwaccel d3d11va -hwaccel_output_format
d3d11 with -preanalysis 1, or with -usage high_quality, which enables
pre-analysis, outputs no packet and waits forever. With DXVA2 input,
frames are lost instead.

Pre-analysis runs shaders on the input and keeps a lookahead window of
input frames. AMF requires D3D11 input with D3D11_BIND_SHADER_RESOURCE
for it (GPUOpen-LibrariesAndSDKs/AMF#455) and stalls without returning
an error otherwise. It also stalls on slices of a texture array, which
is how decoders allocate their pool, even when the flag is set
(GPUOpen-LibrariesAndSDKs/AMF#631). The encoder also holds each wrapped
frame until it is encoded, so with the lookahead it holds more frames
than a decoder's fixed-size pool has to spare. The decoder then fails to
get a surface and drops frames, as with DXVA2 input.

Read whether pre-analysis is enabled after Init(), since a usage can
enable it by default, and if it is, copy the input into a surface from
AllocSurface() when it lacks the flag or comes from a fixed-size pool.
The frame is then released at once. Other input is still wrapped.

Tested on Windows 11, RX 9070 XT, driver 32.0.32015.7029, 120 frames of
1080p H.264: with -preanalysis 1, d3d11va decoding into h264_amf,
hevc_amf and av1_amf, hwupload'ed D3D11 frames and DXVA2 decoding output
all 120 packets (0, and 87 to 106 with DXVA2, before), and so does
-usage high_quality on d3d11va input (0 before). The encoded quality
matches software input with pre-analysis (29.34 dB PSNR in all three
codecs). Output without pre-analysis is unchanged.

Assisted-by: Claude Opus 5.5
Signed-off-by: Julius Bairaktaris <[email protected]>
---
 libavcodec/amfenc.c      | 93 ++++++++++++++++++++++++++++++++++++++++
 libavcodec/amfenc.h      |  1 +
 libavcodec/amfenc_av1.c  |  3 ++
 libavcodec/amfenc_h264.c |  3 ++
 libavcodec/amfenc_hevc.c |  3 ++
 5 files changed, 103 insertions(+)

diff --git a/libavcodec/amfenc.c b/libavcodec/amfenc.c
index 20bc22d98c5..e7a4a82ef77 100644
--- a/libavcodec/amfenc.c
+++ b/libavcodec/amfenc.c
@@ -389,6 +389,83 @@ static AMF_RESULT 
amf_release_attached_frame_ref(AMFEncoderContext *ctx, AMFBuff
     return res;
 }
 
+#if CONFIG_D3D11VA
+static int amf_copy_d3d11_texture(AVCodecContext *avctx, const AVFrame *frame,
+                                  int index, AMFSurface **surface)
+{
+    AMFEncoderContext      *ctx = avctx->priv_data;
+    AVHWDeviceContext      *hw_device_ctx = 
(AVHWDeviceContext*)ctx->device_ctx_ref->data;
+    AVAMFDeviceContext     *amf_device_ctx = (AVAMFDeviceContext 
*)hw_device_ctx->hwctx;
+    AVHWFramesContext      *frames = 
(AVHWFramesContext*)frame->hw_frames_ctx->data;
+    AVD3D11VADeviceContext *d3d11_hwctx = frames->device_ctx->hwctx;
+    ID3D11Texture2D        *texture = (ID3D11Texture2D*)frame->data[0];
+    D3D11_TEXTURE2D_DESC    desc;
+    D3D11_BOX               box;
+    AMFPlane               *plane;
+    AMF_RESULT              res;
+
+    res = 
amf_device_ctx->context->pVtbl->AllocSurface(amf_device_ctx->context, 
AMF_MEMORY_DX11,
+                                                       ctx->format, 
frame->width, frame->height, surface);
+    AMF_RETURN_IF_FALSE(ctx, res == AMF_OK, AVERROR(ENOMEM), "AllocSurface() 
failed with error %d\n", res);
+
+    plane = (*surface)->pVtbl->GetPlaneAt(*surface, 0);
+    if (!plane) {
+        (*surface)->pVtbl->Release(*surface);
+        *surface = NULL;
+        return AVERROR(ENOMEM);
+    }
+
+    texture->lpVtbl->GetDesc(texture, &desc);
+    box.left   = 0;
+    box.top    = 0;
+    box.front  = 0;
+    box.right  = FFMIN(FFALIGN(frame->width,  2), desc.Width);
+    box.bottom = FFMIN(FFALIGN(frame->height, 2), desc.Height);
+    box.back   = 1;
+
+    d3d11_hwctx->lock(d3d11_hwctx->lock_ctx);
+    
d3d11_hwctx->device_context->lpVtbl->CopySubresourceRegion(d3d11_hwctx->device_context,
+        (ID3D11Resource*)plane->pVtbl->GetNative(plane), 0, 0, 0, 0,
+        (ID3D11Resource*)texture, index, &box);
+    d3d11_hwctx->unlock(d3d11_hwctx->lock_ctx);
+    return 0;
+}
+#endif
+
+#if CONFIG_DXVA2
+static int amf_copy_dxva2_surface(AVCodecContext *avctx, const AVFrame *frame, 
AMFSurface **surface)
+{
+    AMFEncoderContext      *ctx = avctx->priv_data;
+    AVHWDeviceContext      *hw_device_ctx = 
(AVHWDeviceContext*)ctx->device_ctx_ref->data;
+    AVAMFDeviceContext     *amf_device_ctx = (AVAMFDeviceContext 
*)hw_device_ctx->hwctx;
+    IDirect3DSurface9      *src = (IDirect3DSurface9 *)frame->data[3];
+    RECT                    rect = { 0, 0, frame->width, frame->height };
+    IDirect3DDevice9       *device;
+    AMFPlane               *plane;
+    AMF_RESULT              res;
+    HRESULT                 hr;
+
+    res = 
amf_device_ctx->context->pVtbl->AllocSurface(amf_device_ctx->context, 
AMF_MEMORY_DX9,
+                                                       ctx->format, 
frame->width, frame->height, surface);
+    AMF_RETURN_IF_FALSE(ctx, res == AMF_OK, AVERROR(ENOMEM), "AllocSurface() 
failed with error %d\n", res);
+
+    plane = (*surface)->pVtbl->GetPlaneAt(*surface, 0);
+    hr = plane ? IDirect3DSurface9_GetDevice(src, &device) : E_FAIL;
+    if (SUCCEEDED(hr)) {
+        hr = IDirect3DDevice9_StretchRect(device, src, &rect, 
(IDirect3DSurface9 *)plane->pVtbl->GetNative(plane),
+                                          &rect, D3DTEXF_NONE);
+        IDirect3DDevice9_Release(device);
+    }
+    if (FAILED(hr)) {
+        (*surface)->pVtbl->Release(*surface);
+        *surface = NULL;
+        av_log(avctx, AV_LOG_ERROR, "Failed to copy the DXVA2 surface: %lx\n", 
(unsigned long)hr);
+        return AVERROR_EXTERNAL;
+    }
+    return 0;
+}
+#endif
+
 static int amf_submit_frame(AVCodecContext *avctx, AVFrame    *frame, 
AMFSurface **surface_resubmit)
 {
     AMFEncoderContext      *ctx = avctx->priv_data;
@@ -408,8 +485,17 @@ static int amf_submit_frame(AVCodecContext *avctx, AVFrame 
   *frame, AMFSurface
             static const GUID AMFTextureArrayIndexGUID = { 0x28115527, 0xe7c3, 
0x4b66, { 0x99, 0xd3, 0x4f, 0x2a, 0xe6, 0xb4, 0x7f, 0xaf } };
             ID3D11Texture2D *texture = (ID3D11Texture2D*)frame->data[0]; // 
actual texture
             int index = (intptr_t)frame->data[1]; // index is a slice in 
texture array is - set to tell AMF which slice to use
+            AVHWFramesContext *frames = 
(AVHWFramesContext*)frame->hw_frames_ctx->data;
+            D3D11_TEXTURE2D_DESC desc;
                 av_assert0(frame->hw_frames_ctx       && avctx->hw_frames_ctx 
&&
                     frame->hw_frames_ctx->data == avctx->hw_frames_ctx->data);
+            texture->lpVtbl->GetDesc(texture, &desc);
+            if (ctx->pa_enabled && (!(desc.BindFlags & 
D3D11_BIND_SHADER_RESOURCE) || frames->initial_pool_size)) {
+                ret = amf_copy_d3d11_texture(avctx, frame, index, &surface);
+                if (ret < 0)
+                    return ret;
+                break;
+            }
                 texture->lpVtbl->SetPrivateData(texture, 
&AMFTextureArrayIndexGUID, sizeof(index), &index);
                 res = 
amf_device_ctx->context->pVtbl->CreateSurfaceFromDX11Native(amf_device_ctx->context,
 texture, &surface, NULL); // wrap to AMF surface
             AMF_RETURN_IF_FALSE(ctx, res == AMF_OK, AVERROR(ENOMEM), 
"CreateSurfaceFromDX11Native() failed  with error %d\n", res);
@@ -421,6 +507,13 @@ static int amf_submit_frame(AVCodecContext *avctx, AVFrame 
   *frame, AMFSurface
     case AV_PIX_FMT_DXVA2_VLD:
         {
             IDirect3DSurface9 *texture = (IDirect3DSurface9 *)frame->data[3]; 
// actual texture
+            AVHWFramesContext *frames = 
(AVHWFramesContext*)frame->hw_frames_ctx->data;
+            if (ctx->pa_enabled && frames->initial_pool_size) {
+                ret = amf_copy_dxva2_surface(avctx, frame, &surface);
+                if (ret < 0)
+                    return ret;
+                break;
+            }
                 res = 
amf_device_ctx->context->pVtbl->CreateSurfaceFromDX9Native(amf_device_ctx->context,
 texture, &surface, NULL); // wrap to AMF surface
             AMF_RETURN_IF_FALSE(ctx, res == AMF_OK, AVERROR(ENOMEM), 
"CreateSurfaceFromDX9Native() failed  with error %d\n", res);
                 hw_surface = 1;
diff --git a/libavcodec/amfenc.h b/libavcodec/amfenc.h
index 6a380208d14..f732586991c 100644
--- a/libavcodec/amfenc.h
+++ b/libavcodec/amfenc.h
@@ -51,6 +51,7 @@ typedef struct AMFEncoderContext {
 
     int                 hwsurfaces_in_queue;
     int                 hwsurfaces_in_queue_max;
+    int                 pa_enabled;
     int                 query_timeout_supported;
 
     // helpers to handle async calls
diff --git a/libavcodec/amfenc_av1.c b/libavcodec/amfenc_av1.c
index a572d439a7e..303cf482d2d 100644
--- a/libavcodec/amfenc_av1.c
+++ b/libavcodec/amfenc_av1.c
@@ -578,6 +578,9 @@ static av_cold int amf_encode_init_av1(AVCodecContext* 
avctx)
     res = ctx->encoder->pVtbl->Init(ctx->encoder, ctx->format, avctx->width, 
avctx->height);
     AMF_RETURN_IF_FALSE(ctx, res == AMF_OK, AVERROR_BUG, "encoder->Init() 
failed with error %d\n", res);
 
+    res = ctx->encoder->pVtbl->GetProperty(ctx->encoder, 
AMF_VIDEO_ENCODER_AV1_PRE_ANALYSIS_ENABLE, &var);
+    ctx->pa_enabled = res == AMF_OK && var.int64Value;
+
     // init dynamic picture control params
     if (ctx->min_qp_i != -1) {
         AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, 
AMF_VIDEO_ENCODER_AV1_MIN_Q_INDEX_INTRA, ctx->min_qp_i);
diff --git a/libavcodec/amfenc_h264.c b/libavcodec/amfenc_h264.c
index 214fac69096..da514956684 100644
--- a/libavcodec/amfenc_h264.c
+++ b/libavcodec/amfenc_h264.c
@@ -556,6 +556,9 @@ static av_cold int amf_encode_init_h264(AVCodecContext 
*avctx)
     res = ctx->encoder->pVtbl->Init(ctx->encoder, ctx->format, avctx->width, 
avctx->height);
     AMF_RETURN_IF_FALSE(ctx, res == AMF_OK, AVERROR_BUG, "encoder->Init() 
failed with error %d\n", res);
 
+    res = ctx->encoder->pVtbl->GetProperty(ctx->encoder, 
AMF_VIDEO_ENCODER_PRE_ANALYSIS_ENABLE, &var);
+    ctx->pa_enabled = res == AMF_OK && var.int64Value;
+
     // Enforce HRD, Filler Data, VBAQ, Frame Skipping, Deblocking Filter
     if (ctx->enforce_hrd != -1) {
         AMF_ASSIGN_PROPERTY_BOOL(res, ctx->encoder, 
AMF_VIDEO_ENCODER_ENFORCE_HRD, ((ctx->enforce_hrd == 0) ? false : true));
diff --git a/libavcodec/amfenc_hevc.c b/libavcodec/amfenc_hevc.c
index 4e280fcf152..94509d4fe47 100644
--- a/libavcodec/amfenc_hevc.c
+++ b/libavcodec/amfenc_hevc.c
@@ -488,6 +488,9 @@ static av_cold int amf_encode_init_hevc(AVCodecContext 
*avctx)
     res = ctx->encoder->pVtbl->Init(ctx->encoder, ctx->format, avctx->width, 
avctx->height);
     AMF_RETURN_IF_FALSE(ctx, res == AMF_OK, AVERROR_BUG, "encoder->Init() 
failed with error %d\n", res);
 
+    res = ctx->encoder->pVtbl->GetProperty(ctx->encoder, 
AMF_VIDEO_ENCODER_HEVC_PRE_ANALYSIS_ENABLE, &var);
+    ctx->pa_enabled = res == AMF_OK && var.int64Value;
+
     // init dynamic picture control params
     if (ctx->max_au_size != -1) {
         AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, 
AMF_VIDEO_ENCODER_HEVC_MAX_AU_SIZE, ctx->max_au_size);
-- 
2.54.0


>From 00f8b471f5030e24ad7bee62376d2cbc90068b69 Mon Sep 17 00:00:00 2001
From: Julius Bairaktaris <[email protected]>
Date: Mon, 26 Oct 2026 15:09:23 +0100
Subject: [PATCH 2/3] avcodec/amfenc: retry Init() without the usage's default
 pre-analysis

The high_quality usage enables pre-analysis, which AMF does not support
for every input format: Init() fails with AMF_INVALID_ARG for P010,
X2BGR10, BGRA and RGBA input (GPUOpen-LibrariesAndSDKs/AMF#399), so
-usage high_quality cannot encode 10-bit or most RGB input with
hevc_amf, av1_amf and, for RGB, h264_amf.

When the preanalysis option is unset and Init() fails with pre-analysis
enabled, retry once without it. AMF says Init() is where the combination
of settings is validated, so this does not need a list of the formats
pre-analysis supports, which differs from the documentation (YUV420P and
ARGB work as well) and will change when AMF adds 10-bit support. An
explicit -preanalysis 1 is passed on unchanged. Move Init() and the
read-back of the pre-analysis state into one function for the three
encoders.

Tested on Windows 11, RX 9070 XT, driver 32.0.32015.7029: -usage
high_quality now encodes P010 with hevc_amf and av1_amf, and BGRA with
all three encoders (Init() failed before). Output with NV12, YUV420P and
ARGB input, where Init() succeeds, is identical to before. QVBR and
HQVBR still fail with 10-bit input, as they need pre-analysis
(GPUOpen-LibrariesAndSDKs/AMF#591).

Assisted-by: Claude Opus 5.5
Signed-off-by: Julius Bairaktaris <[email protected]>
---
 libavcodec/amfenc.c      | 21 +++++++++++++++++++++
 libavcodec/amfenc.h      |  6 ++++++
 libavcodec/amfenc_av1.c  |  8 +++-----
 libavcodec/amfenc_h264.c |  8 +++-----
 libavcodec/amfenc_hevc.c |  8 +++-----
 5 files changed, 36 insertions(+), 15 deletions(-)

diff --git a/libavcodec/amfenc.c b/libavcodec/amfenc.c
index e7a4a82ef77..4352d8182be 100644
--- a/libavcodec/amfenc.c
+++ b/libavcodec/amfenc.c
@@ -278,6 +278,27 @@ static int amf_copy_buffer(AVCodecContext *avctx, AVPacket 
*pkt, AMFBuffer *buff
     return 0;
 }
 
+int ff_amf_init_encoder_component(AVCodecContext *avctx, const wchar_t 
*pa_enable)
+{
+    AMFEncoderContext *ctx = avctx->priv_data;
+    AMFVariantStruct   var = { 0 };
+    AMF_RESULT         res;
+
+    res = ctx->encoder->pVtbl->Init(ctx->encoder, ctx->format, avctx->width, 
avctx->height);
+    if (res != AMF_OK && ctx->preanalysis == -1 &&
+        ctx->encoder->pVtbl->GetProperty(ctx->encoder, pa_enable, &var) == 
AMF_OK && var.int64Value) {
+        av_log(avctx, AV_LOG_VERBOSE, "Init() failed with error %d with 
pre-analysis, which the usage "
+               "enabled; retrying without it.\n", res);
+        AMF_ASSIGN_PROPERTY_BOOL(res, ctx->encoder, pa_enable, false);
+        res = ctx->encoder->pVtbl->Init(ctx->encoder, ctx->format, 
avctx->width, avctx->height);
+    }
+    AMF_RETURN_IF_FALSE(ctx, res == AMF_OK, AVERROR_BUG, "encoder->Init() 
failed with error %d\n", res);
+
+    res = ctx->encoder->pVtbl->GetProperty(ctx->encoder, pa_enable, &var);
+    ctx->pa_enabled = res == AMF_OK && var.int64Value;
+    return 0;
+}
+
 // amfenc API implementation
 int ff_amf_encode_init(AVCodecContext *avctx)
 {
diff --git a/libavcodec/amfenc.h b/libavcodec/amfenc.h
index f732586991c..78fe4039368 100644
--- a/libavcodec/amfenc.h
+++ b/libavcodec/amfenc.h
@@ -155,6 +155,12 @@ int ff_amf_encode_init(AVCodecContext *avctx);
 */
 int ff_amf_encode_close(AVCodecContext *avctx);
 
+/**
+* Initialize the encoder component. If Init() fails while the preanalysis
+* option is unset and the usage enabled pre-analysis, retry without it.
+*/
+int ff_amf_init_encoder_component(AVCodecContext *avctx, const wchar_t 
*pa_enable);
+
 /**
 * Ecoding one frame - common function for all AMF encoders
 */
diff --git a/libavcodec/amfenc_av1.c b/libavcodec/amfenc_av1.c
index 303cf482d2d..95a1aa30e66 100644
--- a/libavcodec/amfenc_av1.c
+++ b/libavcodec/amfenc_av1.c
@@ -575,11 +575,9 @@ static av_cold int amf_encode_init_av1(AVCodecContext* 
avctx)
     ctx->query_timeout_supported = res == AMF_OK && var.int64Value;
 
     // init encoder
-    res = ctx->encoder->pVtbl->Init(ctx->encoder, ctx->format, avctx->width, 
avctx->height);
-    AMF_RETURN_IF_FALSE(ctx, res == AMF_OK, AVERROR_BUG, "encoder->Init() 
failed with error %d\n", res);
-
-    res = ctx->encoder->pVtbl->GetProperty(ctx->encoder, 
AMF_VIDEO_ENCODER_AV1_PRE_ANALYSIS_ENABLE, &var);
-    ctx->pa_enabled = res == AMF_OK && var.int64Value;
+    ret = ff_amf_init_encoder_component(avctx, 
AMF_VIDEO_ENCODER_AV1_PRE_ANALYSIS_ENABLE);
+    if (ret < 0)
+        return ret;
 
     // init dynamic picture control params
     if (ctx->min_qp_i != -1) {
diff --git a/libavcodec/amfenc_h264.c b/libavcodec/amfenc_h264.c
index da514956684..52612a60d55 100644
--- a/libavcodec/amfenc_h264.c
+++ b/libavcodec/amfenc_h264.c
@@ -553,11 +553,9 @@ static av_cold int amf_encode_init_h264(AVCodecContext 
*avctx)
     ctx->query_timeout_supported = res == AMF_OK && var.int64Value;
 
     // Initialize Encoder
-    res = ctx->encoder->pVtbl->Init(ctx->encoder, ctx->format, avctx->width, 
avctx->height);
-    AMF_RETURN_IF_FALSE(ctx, res == AMF_OK, AVERROR_BUG, "encoder->Init() 
failed with error %d\n", res);
-
-    res = ctx->encoder->pVtbl->GetProperty(ctx->encoder, 
AMF_VIDEO_ENCODER_PRE_ANALYSIS_ENABLE, &var);
-    ctx->pa_enabled = res == AMF_OK && var.int64Value;
+    ret = ff_amf_init_encoder_component(avctx, 
AMF_VIDEO_ENCODER_PRE_ANALYSIS_ENABLE);
+    if (ret < 0)
+        return ret;
 
     // Enforce HRD, Filler Data, VBAQ, Frame Skipping, Deblocking Filter
     if (ctx->enforce_hrd != -1) {
diff --git a/libavcodec/amfenc_hevc.c b/libavcodec/amfenc_hevc.c
index 94509d4fe47..3d9ee04ed7f 100644
--- a/libavcodec/amfenc_hevc.c
+++ b/libavcodec/amfenc_hevc.c
@@ -485,11 +485,9 @@ static av_cold int amf_encode_init_hevc(AVCodecContext 
*avctx)
     ctx->query_timeout_supported = res == AMF_OK && var.int64Value;
 
     // init encoder
-    res = ctx->encoder->pVtbl->Init(ctx->encoder, ctx->format, avctx->width, 
avctx->height);
-    AMF_RETURN_IF_FALSE(ctx, res == AMF_OK, AVERROR_BUG, "encoder->Init() 
failed with error %d\n", res);
-
-    res = ctx->encoder->pVtbl->GetProperty(ctx->encoder, 
AMF_VIDEO_ENCODER_HEVC_PRE_ANALYSIS_ENABLE, &var);
-    ctx->pa_enabled = res == AMF_OK && var.int64Value;
+    ret = ff_amf_init_encoder_component(avctx, 
AMF_VIDEO_ENCODER_HEVC_PRE_ANALYSIS_ENABLE);
+    if (ret < 0)
+        return ret;
 
     // init dynamic picture control params
     if (ctx->max_au_size != -1) {
-- 
2.54.0


>From 1250507d1a16be74d6dee892395e24ddf648b9c6 Mon Sep 17 00:00:00 2001
From: Julius Bairaktaris <[email protected]>
Date: Mon, 26 Oct 2026 15:10:55 +0100
Subject: [PATCH 3/3] avcodec/amfenc: submit YUV420P input as NV12

With pre-analysis enabled, the AMF encoders intermittently hang on
YUV420P input: ffmpeg -i <H.264> -c:v av1_amf -preanalysis 1 outputs
nothing and waits forever in 3 of 20 runs, and h264_amf in 1 of 20, as
does -usage high_quality, which enables pre-analysis. The encoder
converts YUV420P to NV12 itself, and with FFmpeg's 1 ms query timeout,
pre-analysis then either fails in its text-detection step and stalls
without returning an error, or the first SubmitInput() deadlocks in that
conversion. NV12 input does not hang (GPUOpen-LibrariesAndSDKs/AMF#631,
which has a standalone repro).

Initialize the encoder for NV12 and interleave the chroma planes while
copying the frame into the host surface, a copy that is already done.

Tested on Windows 11, RX 9070 XT, driver 32.0.32015.7029: 0 of 20 runs
each of av1_amf and h264_amf with -preanalysis 1 and of h264_amf with
-usage high_quality hang (4 of 40 before). The output of h264_amf,
hevc_amf and av1_amf for YUV420P input is identical to before, and the
CPU time of a 4K hevc_amf encode is unchanged.

Assisted-by: Claude Opus 5.5
Signed-off-by: Julius Bairaktaris <[email protected]>
---
 libavcodec/amfenc.c | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/libavcodec/amfenc.c b/libavcodec/amfenc.c
index 4352d8182be..cae8fa1740d 100644
--- a/libavcodec/amfenc.c
+++ b/libavcodec/amfenc.c
@@ -126,6 +126,11 @@ static int amf_init_encoder(AVCodecContext *avctx)
     ctx->format = av_av_to_amf_format(pix_fmt);
     AMF_RETURN_IF_FALSE(ctx, ctx->format != AMF_SURFACE_UNKNOWN, 
AVERROR(EINVAL),
                         "Format %s is not supported\n", 
av_get_pix_fmt_name(pix_fmt));
+    // TODO: pass YUV420P input to AMF again once 
GPUOpen-LibrariesAndSDKs/AMF#631
+    // is fixed: with pre-analysis, AMF's conversion of it to NV12 
intermittently
+    // stalls or deadlocks the encoder.
+    if (ctx->format == AMF_SURFACE_YUV420P)
+        ctx->format = AMF_SURFACE_NV12;
 
     res = 
amf_device_ctx->factory->pVtbl->CreateComponent(amf_device_ctx->factory, 
amf_device_ctx->context, codec_id, &ctx->encoder);
     AMF_RETURN_IF_FALSE(ctx, res == AMF_OK, AVERROR_ENCODER_NOT_FOUND, 
"CreateComponent(%ls) failed with error %d\n", codec_id, res);
@@ -183,6 +188,20 @@ static int amf_copy_surface(AVCodecContext *avctx, const 
AVFrame *frame,
         dst_data[i] = plane->pVtbl->GetNative(plane);
         dst_linesize[i] = plane->pVtbl->GetHPitch(plane);
     }
+    if (frame->format == AV_PIX_FMT_YUV420P && 
surface->pVtbl->GetFormat(surface) == AMF_SURFACE_NV12) {
+        av_image_copy_plane(dst_data[0], dst_linesize[0], frame->data[0], 
frame->linesize[0],
+                            avctx->width, avctx->height);
+        for (int y = 0; y < (avctx->height + 1) >> 1; y++) {
+            const uint8_t *u = frame->data[1] + y * frame->linesize[1];
+            const uint8_t *v = frame->data[2] + y * frame->linesize[2];
+            uint8_t *uv = dst_data[1] + y * dst_linesize[1];
+            for (int x = 0; x < (avctx->width + 1) >> 1; x++) {
+                uv[2 * x]     = u[x];
+                uv[2 * x + 1] = v[x];
+            }
+        }
+        return 0;
+    }
     av_image_copy2(dst_data, dst_linesize,
                    frame->data, frame->linesize, frame->format,
                    avctx->width, avctx->height);
-- 
2.54.0

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

Reply via email to