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]