PR #24365 opened by JuliusBairaktaris
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24365
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24365.patch
Depends on #23351, whose sixteen patches this is stacked on; only the
last three commits belong to this pull request.
**Draft until the AMF release with the fix for
GPUOpen-LibrariesAndSDKs/AMF#610 ships.** AMD has confirmed the fix;
on the current driver (32.0.31041.1004) AMFVQEnhancer still advertises
five packed RGB input formats that Init() rejects, so with this series
packed RGB software input to vqe_amf fails at configuration
("AMFVQEnhancer-Init() failed with error 4") where the hand-written
list in #23351 had libavfilter convert it to NV12 first. Every other
component reports caps that match Init() today.
The AMF filters each carried a hand-written input format list, and
sr_amf a second one for the algorithms that need packed RGB. They were
copied from the programming guides and then corrected by trial (#23351
alone fixed vpp_amf listing formats the converter rejects and vqe_amf
listing four the enhancer rejects), and they cannot follow a driver
that gains or loses a format. The last commit asks the component
instead: amf_setup_input_output_formats() creates a throwaway instance
of the component, enumerates AMFCaps::GetInputCaps(), offers every
pixel format that maps to a reported surface format, and intersects
that with the sw_formats an AMF frames context can hold, because a
filter that cannot convert emits the format it receives. sr_amf passes
a predicate that keeps packed RGB only for point and sr1-1 and pins the
list to an explicit format=, as before. The D3D11VA and DXVA2 device
overrides and the AMF-surface-only output link are unchanged.
Two hwcontext_amf fixes are needed for the wider lists to be usable,
and stand on their own:
- amf_transfer_get_formats() returned a static list of seven formats
while amf_transfer_data_from()/to() reject anything but the frames
context's sw_format. BGR0 is a valid sw_format but was not in the
list, so a BGR0 surface could never be downloaded; conversely
hwdownload accepted any listed format at configuration time and then
failed on every frame with "Failed to download frame: -22". The list
is replaced by the sw_format, as every other hwcontext does, so
hwdownload now reports a mismatch at configuration time by name.
- YUYV422 is in format_map[] and the converter takes it, but it was
missing from supported_formats[], so it could not be a frames context
sw_format and "format=yuyv422,vpp_amf,hwdownload,format=yuyv422"
failed in amf_frames_init().
What changes for users, all on the #23351 baseline:
- libavfilter picks the closest accepted format for software input that
needs converting, and with bgr0 on the list its tie-break prefers it
over rgba for 8-bit input. point, sr1-1 and frc_amf on nv12 or rgb24
software input therefore produce bgr0 surfaces, so a downstream
hwdownload has to name bgr0; the scaled pixels are byte-identical to
the rgba path (framecrc through format=rgb24).
- vpp_amf gains yuyv422 and x2bgr10le as pass-through formats and loses
rgb0, which libavfilter picked for rgb24 input and amf_frames_init()
then rejected, so "format=rgb24,vpp_amf,hwdownload,format=bgr0" now
configures.
- sr_amf accepts bgr0 (and any other packed RGB the scaler reports) for
point and sr1-1 without a conversion, and format= may name it.
- vqe_amf no longer has its own "only accepts nv12 and p010" message for
hardware surfaces; the accepted set is the driver's to report, and
Init() rejects the rest.
- A format= that the scaler does not accept at all on software input
now fails in negotiation ("Impossible to convert between the formats
supported by ...") rather than in sr_amf's config_output; one it
accepts but the algorithm cannot use still gets the filter's own
message.
Tested on Windows 11, RX 9070 XT, driver 32.0.31041.1004, every case
against a build of the #23351 head: vpp_amf, vqe_amf, frc_amf and every
sr_amf algorithm with software nv12, p010, rgba, bgra, yuv420p, yuyv422
and x2bgr10le input, with AMF, D3D11VA and DXVA2 surfaces, with -hwaccel
amf decoding, and with no device given at all. Nothing that worked
before stopped working except the vqe_amf packed RGB case above, and
vpp_amf, vqe_amf and sr_amf give byte-identical framecrc output for
every negotiation that did not change. hwupload,hwdownload round trips
for nv12, yuv420p, yuyv422, bgra, rgba, bgr0, p010le and x2bgr10le are
bit-exact with the source. Graph configuration takes the same 204 ms per
run with and without the caps query. The touched files build
warning-free with mingw-w64 and on Linux with --enable-amf, where
CONFIG_D3D11VA is 0.
Formats the components accept but FFmpeg has no format_map[] entry for
(Y210, AYUV, Y410, P012, P016 on the converter) are still unreachable;
adding them is a separate hwcontext_amf change.
>From 8665caa9d9ec4418cab991c619413000f2341a17 Mon Sep 17 00:00:00 2001
From: Julius Bairaktaris <[email protected]>
Date: Sun, 28 Jun 2026 17:58:44 +0200
Subject: [PATCH 01/19] avfilter/vf_sr_amf: fix solid green frame with
algorithm=sr1-1
Per AMD's AMF_HQ_Scaler_API.md, the HQ Scaler's VideoSR1.1 algorithm is
only supported on a DX11/DX12 engine and with input/output formats other
than NV12 or P010 (it accepts packed RGB: BGRA, RGBA, R10G10B10A2 or
RGBA_F16). The filter however advertised NV12/P010 as valid input and
passed the input format straight to AMFHQScaler::Init(), which returns
AMF_OK even for the unsupported format. The existing result check
therefore never fired and the filter silently emitted a solid green
frame.
When algorithm=sr1-1, restrict the negotiated formats to the packed 8-bit
RGB formats RGBA and BGRA, which round-trip cleanly through the AMF
hwcontext (both upload and hwdownload), so a converter is auto-inserted
for YUV input, and reject an unsupported hardware surface explicitly in
config_output instead of producing green. The other SR1.1-capable formats
(R10G10B10A2, RGBA_F16) are not exposed: they are absent from the AMF
hwcontext's supported_formats[]/supported_transfer_formats[], so an AMF
frames context cannot use them and hwdownload would fail with ENOSYS;
exposing them belongs in a separate hwcontext_amf change.
Note that AMF's VideoSR algorithms (sr1-0 and sr1-1) leave the alpha
channel zeroed and expose no property to control it; drop alpha
downstream, e.g. with format=rgb24, for an opaque result.
Refs AMD AMF_HQ_Scaler_API.md and mpv-player/mpv#18068.
Assisted-by: Claude Opus 5
Signed-off-by: Julius Bairaktaris <[email protected]>
---
doc/filters.texi | 7 +++++++
libavfilter/vf_sr_amf.c | 31 ++++++++++++++++++++++++++++---
2 files changed, 35 insertions(+), 3 deletions(-)
diff --git a/doc/filters.texi b/doc/filters.texi
index b7379d3060..a214ee010d 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -23272,6 +23272,13 @@ Point
@item sr1-1
Video SR1.1
+This algorithm only supports packed RGB formats and requires a DirectX 11 or
+DirectX 12 device, so it is available on Windows only. Inputs in other formats
+such as @code{nv12} or @code{p010} are converted to a packed RGB format
+(@code{rgba} or @code{bgra}) automatically.
+Note that the VideoSR algorithms (@code{sr1-0} and @code{sr1-1}) do not
+preserve the alpha channel (it is left zeroed); append e.g. @code{format=rgb24}
+downstream if an opaque result is required.
@end table
diff --git a/libavfilter/vf_sr_amf.c b/libavfilter/vf_sr_amf.c
index 17bfdc2a03..9ab5b8c2f2 100644
--- a/libavfilter/vf_sr_amf.c
+++ b/libavfilter/vf_sr_amf.c
@@ -54,8 +54,9 @@
static int amf_filter_query_formats(AVFilterContext *avctx)
{
- const enum AVPixelFormat *output_pix_fmts;
- static const enum AVPixelFormat input_pix_fmts[] = {
+ AMFFilterContext *ctx = avctx->priv;
+ const enum AVPixelFormat *input_pix_fmts, *output_pix_fmts;
+ static const enum AVPixelFormat input_pix_fmts_default[] = {
AV_PIX_FMT_NV12,
AV_PIX_FMT_P010,
AV_PIX_FMT_BGRA,
@@ -75,7 +76,21 @@ static int amf_filter_query_formats(AVFilterContext *avctx)
AV_PIX_FMT_RGBAF16,
AV_PIX_FMT_NONE,
};
- output_pix_fmts = output_pix_fmts_default;
+ // VideoSR1.1 needs packed RGB on DX11/DX12
+ static const enum AVPixelFormat pix_fmts_sr1_1[] = {
+ AV_PIX_FMT_RGBA,
+ AV_PIX_FMT_BGRA,
+ AV_PIX_FMT_AMF_SURFACE,
+ AV_PIX_FMT_NONE,
+ };
+
+ if (ctx->algorithm == AMF_HQ_SCALER_ALGORITHM_VIDEOSR1_1) {
+ input_pix_fmts = pix_fmts_sr1_1;
+ output_pix_fmts = pix_fmts_sr1_1;
+ } else {
+ input_pix_fmts = input_pix_fmts_default;
+ output_pix_fmts = output_pix_fmts_default;
+ }
return amf_setup_input_output_formats(avctx, input_pix_fmts,
output_pix_fmts);
}
@@ -95,6 +110,16 @@ static int amf_filter_config_output(AVFilterLink *outlink)
if (err < 0)
return err;
+ if (ctx->algorithm == AMF_HQ_SCALER_ALGORITHM_VIDEOSR1_1 &&
+ (in_format == AV_PIX_FMT_NV12 || in_format == AV_PIX_FMT_P010)) {
+ av_log(avctx, AV_LOG_ERROR,
+ "sr1-1 (VideoSR1.1) requires a packed RGB format (rgba); "
+ "%s is not supported. Convert the input first (e.g.
format=rgba) or "
+ "select another algorithm.\n",
+ av_get_pix_fmt_name(in_format));
+ return AVERROR(EINVAL);
+ }
+
// HQ scaler should be used for upscaling only
if (inlink->w > outlink->w || inlink->h > outlink->h) {
av_log(avctx, AV_LOG_ERROR, "AMF HQ scaler should be used for
upscaling only.\n");
--
2.52.0
>From 53b9f99506ab495ac586bdcc675d78a2c0dcee71 Mon Sep 17 00:00:00 2001
From: Julius Bairaktaris <[email protected]>
Date: Fri, 31 Jul 2026 12:57:58 +0200
Subject: [PATCH 02/19] avfilter/vf_sr_amf: fix solid green frame with
algorithm=point
AMF returns an unwritten surface for AMF_HQ_SCALER_ALGORITHM_POINT when
the surface format is NV12 or P010, exactly as it does for VideoSR1.1 on
YUV: Init(), SubmitInput() and QueryOutput() all report AMF_OK and the
output is a solid green frame. Unlike VideoSR1.1 this is not a documented
limitation - point is listed as a plain scaling algorithm with no format
restriction - and it has been open on AMD's tracker since 2023.
Reuse the sr1-1 handling: negotiate the packed 8-bit RGB formats for
point as well, so a converter is auto-inserted for YUV input, and reject
an NV12/P010-backed hardware surface in config_output instead of emitting
green.
Reproduced with:
ffmpeg -init_hw_device amf -f lavfi -i testsrc2=size=1280x720 \
-vf "format=nv12,hwupload,sr_amf=w=2560:h=1440:algorithm=point,\
hwdownload,format=nv12,format=rgb24" -frames:v 1 out.png
Before the fix the output is a single flat green frame; after it matches
bilinear/bicubic/sr1-0. Tested on Windows 11, RX 9070 XT, driver
32.0.31035.1003.
Refs https://github.com/GPUOpen-LibrariesAndSDKs/AMF/issues/427
Assisted-by: Claude Opus 5
Signed-off-by: Julius Bairaktaris <[email protected]>
---
doc/filters.texi | 3 +++
libavfilter/vf_sr_amf.c | 26 +++++++++++++++++---------
2 files changed, 20 insertions(+), 9 deletions(-)
diff --git a/doc/filters.texi b/doc/filters.texi
index a214ee010d..4b8e9ac32d 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -23269,6 +23269,9 @@ This is a default value
@item point
Point
+AMF returns an unwritten surface for this algorithm when the format is
+@code{nv12} or @code{p010}, so inputs in those formats are converted to a
packed
+RGB format (@code{rgba} or @code{bgra}) automatically.
@item sr1-1
Video SR1.1
diff --git a/libavfilter/vf_sr_amf.c b/libavfilter/vf_sr_amf.c
index 9ab5b8c2f2..e4d56c24a1 100644
--- a/libavfilter/vf_sr_amf.c
+++ b/libavfilter/vf_sr_amf.c
@@ -52,6 +52,12 @@
#endif
+static int amf_hq_scaler_needs_packed_rgb(int algorithm)
+{
+ return algorithm == AMF_HQ_SCALER_ALGORITHM_VIDEOSR1_1 ||
+ algorithm == AMF_HQ_SCALER_ALGORITHM_POINT;
+}
+
static int amf_filter_query_formats(AVFilterContext *avctx)
{
AMFFilterContext *ctx = avctx->priv;
@@ -76,17 +82,17 @@ static int amf_filter_query_formats(AVFilterContext *avctx)
AV_PIX_FMT_RGBAF16,
AV_PIX_FMT_NONE,
};
- // VideoSR1.1 needs packed RGB on DX11/DX12
- static const enum AVPixelFormat pix_fmts_sr1_1[] = {
+ // sr1-1 and point produce a blank surface on YUV input
+ static const enum AVPixelFormat pix_fmts_packed_rgb[] = {
AV_PIX_FMT_RGBA,
AV_PIX_FMT_BGRA,
AV_PIX_FMT_AMF_SURFACE,
AV_PIX_FMT_NONE,
};
- if (ctx->algorithm == AMF_HQ_SCALER_ALGORITHM_VIDEOSR1_1) {
- input_pix_fmts = pix_fmts_sr1_1;
- output_pix_fmts = pix_fmts_sr1_1;
+ if (amf_hq_scaler_needs_packed_rgb(ctx->algorithm)) {
+ input_pix_fmts = pix_fmts_packed_rgb;
+ output_pix_fmts = pix_fmts_packed_rgb;
} else {
input_pix_fmts = input_pix_fmts_default;
output_pix_fmts = output_pix_fmts_default;
@@ -110,12 +116,14 @@ static int amf_filter_config_output(AVFilterLink *outlink)
if (err < 0)
return err;
- if (ctx->algorithm == AMF_HQ_SCALER_ALGORITHM_VIDEOSR1_1 &&
+ if (amf_hq_scaler_needs_packed_rgb(ctx->algorithm) &&
(in_format == AV_PIX_FMT_NV12 || in_format == AV_PIX_FMT_P010)) {
av_log(avctx, AV_LOG_ERROR,
- "sr1-1 (VideoSR1.1) requires a packed RGB format (rgba); "
- "%s is not supported. Convert the input first (e.g.
format=rgba) or "
- "select another algorithm.\n",
+ "%s requires a packed RGB format (rgba); %s is not supported. "
+ "Convert the input first (e.g. format=rgba) or select another "
+ "algorithm.\n",
+ ctx->algorithm == AMF_HQ_SCALER_ALGORITHM_POINT ? "point"
+ : "sr1-1
(VideoSR1.1)",
av_get_pix_fmt_name(in_format));
return AVERROR(EINVAL);
}
--
2.52.0
>From c88c39fcfedee2d96a498b9bad11bb1eff1d3489 Mon Sep 17 00:00:00 2001
From: Julius Bairaktaris <[email protected]>
Date: Fri, 31 Jul 2026 13:01:57 +0200
Subject: [PATCH 03/19] avutil/hwcontext_amf: allow X2BGR10 and RGBAF16 frames
contexts
Both formats are already in format_map[] and map to AMF_SURFACE_R10G10B10A2
and AMF_SURFACE_RGBA_F16, but they were missing from supported_formats[]
and supported_transfer_formats[]. An AMF frames context could therefore
not use them as sw_format, and hwupload/hwdownload rejected them, which
in turn kept the AMF filters from offering the two formats AMF's HQ
Scaler needs for HDR.
Both are single-plane packed formats, so the existing transfer paths
handle them unchanged: amf_transfer_data_to/from derive plane count from
the surface and copy with av_image_copy2().
Verified on Windows 11, RX 9070 XT, driver 32.0.31035.1003:
ffmpeg -init_hw_device amf -f lavfi -i testsrc2=size=640x360 \
-vf "format=x2bgr10le,hwupload,hwdownload,format=x2bgr10le" -f null -
passes where it previously failed with "Invalid output format x2bgr10le
for hwframe download", and a rawvideo rgbaf16le round-trip through
hwupload,hwdownload comes back bit-exact.
Assisted-by: Claude Opus 5
Signed-off-by: Julius Bairaktaris <[email protected]>
---
libavutil/hwcontext_amf.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/libavutil/hwcontext_amf.c b/libavutil/hwcontext_amf.c
index 505424af74..d67193a3c5 100644
--- a/libavutil/hwcontext_amf.c
+++ b/libavutil/hwcontext_amf.c
@@ -317,6 +317,8 @@ static const enum AVPixelFormat supported_formats[] = {
AV_PIX_FMT_RGBA,
AV_PIX_FMT_BGR0,
AV_PIX_FMT_P010,
+ AV_PIX_FMT_X2BGR10,
+ AV_PIX_FMT_RGBAF16,
#if CONFIG_D3D11VA
AV_PIX_FMT_D3D11,
#endif
@@ -334,6 +336,8 @@ static const enum AVPixelFormat
supported_transfer_formats[] = {
AV_PIX_FMT_BGRA,
AV_PIX_FMT_RGBA,
AV_PIX_FMT_P010,
+ AV_PIX_FMT_X2BGR10,
+ AV_PIX_FMT_RGBAF16,
AV_PIX_FMT_NONE,
};
--
2.52.0
>From cf5b0dc39ceab22b8ed742fb8c901dad9d99b432 Mon Sep 17 00:00:00 2001
From: Julius Bairaktaris <[email protected]>
Date: Fri, 31 Jul 2026 13:01:57 +0200
Subject: [PATCH 04/19] avfilter/vf_sr_amf: offer 10-bit and f16 packed RGB
AMF's HQ Scaler accepts R10G10B10A2 and RGBA_F16 for the algorithms that
require packed RGB, but the filter only offered RGBA and BGRA because an
AMF frames context could not carry the other two. Now that
hwcontext_amf supports them, add them to the negotiated list so a 10-bit
or half-float source is no longer forced down to 8-bit RGB.
Tested on Windows 11, RX 9070 XT, driver 32.0.31035.1003: sr1-1, point,
sr1-0 and bilinear all produce correct output for x2bgr10le and
rgbaf16le input at 1280x720 -> 2560x1440.
Assisted-by: Claude Opus 5
Signed-off-by: Julius Bairaktaris <[email protected]>
---
doc/filters.texi | 5 +++--
libavfilter/vf_sr_amf.c | 2 ++
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/doc/filters.texi b/doc/filters.texi
index 4b8e9ac32d..9cd24b2c82 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -23271,14 +23271,15 @@ This is a default value
Point
AMF returns an unwritten surface for this algorithm when the format is
@code{nv12} or @code{p010}, so inputs in those formats are converted to a
packed
-RGB format (@code{rgba} or @code{bgra}) automatically.
+RGB format (@code{rgba}, @code{bgra}, @code{x2bgr10le} or @code{rgbaf16le})
+automatically.
@item sr1-1
Video SR1.1
This algorithm only supports packed RGB formats and requires a DirectX 11 or
DirectX 12 device, so it is available on Windows only. Inputs in other formats
such as @code{nv12} or @code{p010} are converted to a packed RGB format
-(@code{rgba} or @code{bgra}) automatically.
+(@code{rgba}, @code{bgra}, @code{x2bgr10le} or @code{rgbaf16le}) automatically.
Note that the VideoSR algorithms (@code{sr1-0} and @code{sr1-1}) do not
preserve the alpha channel (it is left zeroed); append e.g. @code{format=rgb24}
downstream if an opaque result is required.
diff --git a/libavfilter/vf_sr_amf.c b/libavfilter/vf_sr_amf.c
index e4d56c24a1..1758c94ea9 100644
--- a/libavfilter/vf_sr_amf.c
+++ b/libavfilter/vf_sr_amf.c
@@ -86,6 +86,8 @@ static int amf_filter_query_formats(AVFilterContext *avctx)
static const enum AVPixelFormat pix_fmts_packed_rgb[] = {
AV_PIX_FMT_RGBA,
AV_PIX_FMT_BGRA,
+ AV_PIX_FMT_X2BGR10,
+ AV_PIX_FMT_RGBAF16,
AV_PIX_FMT_AMF_SURFACE,
AV_PIX_FMT_NONE,
};
--
2.52.0
>From 3983a87b251eb2e835b7886c62b13c266abae653 Mon Sep 17 00:00:00 2001
From: Julius Bairaktaris <[email protected]>
Date: Fri, 31 Jul 2026 13:25:20 +0200
Subject: [PATCH 05/19] avfilter/vf_sr_amf: convert YUV input on the GPU for
sr1-1 and point
sr1-1 and point need a packed RGB surface. Software input was handled by
restricting the negotiated formats so libavfilter inserts a converter,
but a hardware surface backed by NV12 or P010 could only be rejected,
which made both algorithms unusable with any AMF or D3D11VA producer
upstream - including every player that hands libavfilter its decoder
frames.
Chain an AMFVideoConverter ahead of the scaler in that case instead. The
conversion stays on the GPU, so no download is introduced, and the
component is driven from the shared filter_frame path through a new
pre_converter field.
The HQ scaler has no output format property and emits the format it was
initialised with, so one packed RGB target drives the converter output
format, AMFHQScaler::Init() and the output frames context alike. P010
converts to X2BGR10 and NV12 to RGBA by default; an explicit format= is
kept when it names a packed RGB format and rejected otherwise. Where no
conversion is needed the scaler cannot change the format at all, so an
explicit format= differing from the input is rejected and the target is
pinned to the input format, rather than letting the output link negotiate
a frames context that disagrees with the surface the scaler produces.
AMD's Video Converter programming guide has the caller poll for output and
stop submitting while the component reports AMF_INPUT_FULL, so both calls
are retried instead of turning a valid asynchronous state into a dropped
frame or an error. The component runs synchronously on the tested device
and the retry never triggers there.
The converter is asked for full range output, and the converted frame is
tagged AVCOL_SPC_RGB with AVCOL_RANGE_JPEG rather than inheriting the
input's YUV matrix.
The output frames context follows the converted format, so the filter
emits packed RGB where it previously emitted NV12; with format=same this
is only reachable for the two algorithms that cannot produce YUV anyway.
Tested on Windows 11, RX 9070 XT, driver 32.0.31041.1004, with
-hwaccel amf -hwaccel_output_format amf: point and sr1-1 measure
YMIN=YAVG=YMAX=0 before this change and match the plain decode to within
0.6/255 after, while bilinear, bicubic and sr1-0 keep their NV12 output.
point at 2x scores 36.5 dB PSNR against a software neighbour reference,
the converted frame is tagged pc/gbr, P010 reaches X2BGR10, the accepted
and rejected format= values behave as described, and 300 frames run
through the converter for both algorithms.
Assisted-by: Claude Opus 5
Signed-off-by: Julius Bairaktaris <[email protected]>
---
doc/filters.texi | 13 +++++--
libavfilter/vf_amf_common.c | 35 ++++++++++++++++++
libavfilter/vf_amf_common.h | 1 +
libavfilter/vf_sr_amf.c | 73 +++++++++++++++++++++++++++++++------
4 files changed, 107 insertions(+), 15 deletions(-)
diff --git a/doc/filters.texi b/doc/filters.texi
index 9cd24b2c82..ed54f1e4c6 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -23271,15 +23271,16 @@ This is a default value
Point
AMF returns an unwritten surface for this algorithm when the format is
@code{nv12} or @code{p010}, so inputs in those formats are converted to a
packed
-RGB format (@code{rgba}, @code{bgra}, @code{x2bgr10le} or @code{rgbaf16le})
-automatically.
+RGB format automatically. This is a driver bug rather than a documented
+restriction; the conversion can be dropped once a driver that scales
+@code{nv12} and @code{p010} correctly with @code{point} is in wide use.
@item sr1-1
Video SR1.1
This algorithm only supports packed RGB formats and requires a DirectX 11 or
DirectX 12 device, so it is available on Windows only. Inputs in other formats
such as @code{nv12} or @code{p010} are converted to a packed RGB format
-(@code{rgba}, @code{bgra}, @code{x2bgr10le} or @code{rgbaf16le}) automatically.
+automatically.
Note that the VideoSR algorithms (@code{sr1-0} and @code{sr1-1}) do not
preserve the alpha channel (it is left zeroed); append e.g. @code{format=rgb24}
downstream if an opaque result is required.
@@ -23293,6 +23294,12 @@ Control hq scaler sharpening. The value is a float in
the range of [0.0, 2.0]
Controls the output pixel format. By default, or if none is specified, the
input
pixel format is used.
+The @code{point} and @code{sr1-1} algorithms emit the packed RGB format the
+scaler receives, so for them this option must name one of @code{rgba},
+@code{bgra}, @code{x2bgr10le} or @code{rgbaf16le}. When the input is converted
+automatically, @code{x2bgr10le} is selected for @code{p010} input and
+@code{rgba} otherwise, and the result is tagged as full range RGB.
+
@item keep-ratio
Force the scaler to keep the aspect ratio of the input image when the output
size has a different aspect ratio.
Default value is false.
diff --git a/libavfilter/vf_amf_common.c b/libavfilter/vf_amf_common.c
index 40c6fbceea..f7cd680cbe 100644
--- a/libavfilter/vf_amf_common.c
+++ b/libavfilter/vf_amf_common.c
@@ -25,6 +25,7 @@
#include "libavutil/mem.h"
#include "libavutil/imgutils.h"
#include "libavutil/pixdesc.h"
+#include "libavutil/time.h"
#include "AMF/components/VideoDecoderUVD.h"
#include "libavutil/hwcontext_amf.h"
@@ -66,6 +67,12 @@ void amf_filter_uninit(AVFilterContext *avctx)
ctx->component = NULL;
}
+ if (ctx->pre_converter) {
+ ctx->pre_converter->pVtbl->Terminate(ctx->pre_converter);
+ ctx->pre_converter->pVtbl->Release(ctx->pre_converter);
+ ctx->pre_converter = NULL;
+ }
+
if (ctx->master_display)
av_freep(&ctx->master_display);
@@ -100,6 +107,32 @@ int amf_filter_filter_frame(AVFilterLink *inlink, AVFrame
*in)
if (ret < 0)
goto fail;
+ if (ctx->pre_converter) {
+ AMFGuid guid = IID_AMFSurface();
+ AMFData *data_conv = NULL;
+ AMFSurface *surface_conv = NULL;
+
+ do {
+ res = ctx->pre_converter->pVtbl->SubmitInput(ctx->pre_converter,
(AMFData*)surface_in);
+ if (res == AMF_INPUT_FULL)
+ av_usleep(100);
+ } while (res == AMF_INPUT_FULL);
+ surface_in->pVtbl->Release(surface_in);
+ AMF_GOTO_FAIL_IF_FALSE(avctx, res == AMF_OK, AVERROR_UNKNOWN,
"Converter SubmitInput() failed with error %d\n", res);
+
+ do {
+ res = ctx->pre_converter->pVtbl->QueryOutput(ctx->pre_converter,
&data_conv);
+ if (res == AMF_REPEAT && !data_conv)
+ av_usleep(100);
+ } while (res == AMF_REPEAT && !data_conv);
+ AMF_GOTO_FAIL_IF_FALSE(avctx, res == AMF_OK && data_conv,
AVERROR_UNKNOWN, "Converter QueryOutput() failed with error %d\n", res);
+
+ res = data_conv->pVtbl->QueryInterface(data_conv, &guid,
(void**)&surface_conv);
+ data_conv->pVtbl->Release(data_conv);
+ AMF_GOTO_FAIL_IF_FALSE(avctx, res == AMF_OK, AVERROR_UNKNOWN,
"Converter QueryInterface(IID_AMFSurface) failed with error %d\n", res);
+ surface_in = surface_conv;
+ }
+
res = ctx->component->pVtbl->SubmitInput(ctx->component,
(AMFData*)surface_in);
surface_in->pVtbl->Release(surface_in); // release surface after use
AMF_GOTO_FAIL_IF_FALSE(avctx, res == AMF_OK, AVERROR_UNKNOWN,
"SubmitInput() failed with error %d\n", res);
@@ -158,6 +191,8 @@ int amf_filter_filter_frame(AVFilterLink *inlink, AVFrame
*in)
if (ctx->out_trc != AMF_COLOR_TRANSFER_CHARACTERISTIC_UNDEFINED)
out->color_trc = ctx->out_trc;
+ if (ctx->pre_converter)
+ out->colorspace = AVCOL_SPC_RGB;
if (ret < 0)
goto fail;
diff --git a/libavfilter/vf_amf_common.h b/libavfilter/vf_amf_common.h
index 0290a52c45..7879403d06 100644
--- a/libavfilter/vf_amf_common.h
+++ b/libavfilter/vf_amf_common.h
@@ -58,6 +58,7 @@ typedef struct AMFFilterContext {
int reset_sar;
AMFComponent *component;
+ AMFComponent *pre_converter;
AVBufferRef *amf_device_ref;
AVBufferRef *hwframes_in_ref;
diff --git a/libavfilter/vf_sr_amf.c b/libavfilter/vf_sr_amf.c
index 1758c94ea9..deb020809d 100644
--- a/libavfilter/vf_sr_amf.c
+++ b/libavfilter/vf_sr_amf.c
@@ -35,6 +35,7 @@
#include "libavutil/hwcontext_amf_internal.h"
#include "AMF/components/HQScaler.h"
+#include "AMF/components/VideoConverter.h"
#include "AMF/components/ColorSpace.h"
#include "vf_amf_common.h"
@@ -52,12 +53,27 @@
#endif
+static enum AVPixelFormat amf_inlink_sw_format(AVFilterLink *inlink)
+{
+ FilterLink *inl = ff_filter_link(inlink);
+
+ if (inl->hw_frames_ctx)
+ return ((AVHWFramesContext*)inl->hw_frames_ctx->data)->sw_format;
+ return inlink->format;
+}
+
static int amf_hq_scaler_needs_packed_rgb(int algorithm)
{
return algorithm == AMF_HQ_SCALER_ALGORITHM_VIDEOSR1_1 ||
algorithm == AMF_HQ_SCALER_ALGORITHM_POINT;
}
+static int amf_is_packed_rgb(enum AVPixelFormat format)
+{
+ return format == AV_PIX_FMT_RGBA || format == AV_PIX_FMT_BGRA ||
+ format == AV_PIX_FMT_X2BGR10 || format == AV_PIX_FMT_RGBAF16;
+}
+
static int amf_filter_query_formats(AVFilterContext *avctx)
{
AMFFilterContext *ctx = avctx->priv;
@@ -82,7 +98,7 @@ static int amf_filter_query_formats(AVFilterContext *avctx)
AV_PIX_FMT_RGBAF16,
AV_PIX_FMT_NONE,
};
- // sr1-1 and point produce a blank surface on YUV input
+ // sr1-1 and point need packed RGB; YUV is converted on the GPU
static const enum AVPixelFormat pix_fmts_packed_rgb[] = {
AV_PIX_FMT_RGBA,
AV_PIX_FMT_BGRA,
@@ -113,21 +129,54 @@ static int amf_filter_config_output(AVFilterLink *outlink)
AMF_RESULT res;
enum AVPixelFormat in_format;
enum AMF_MEMORY_TYPE mem_type = AMF_MEMORY_UNKNOWN;
+ enum AVPixelFormat in_sw_format;
+ int needs_conversion;
+
+ in_sw_format = amf_inlink_sw_format(inlink);
+ needs_conversion = amf_hq_scaler_needs_packed_rgb(ctx->algorithm) &&
+ (in_sw_format == AV_PIX_FMT_NV12 || in_sw_format ==
AV_PIX_FMT_P010);
+
+ if (amf_hq_scaler_needs_packed_rgb(ctx->algorithm)) {
+ if (!needs_conversion) {
+ if (ctx->format != AV_PIX_FMT_NONE && ctx->format != in_sw_format)
{
+ av_log(avctx, AV_LOG_ERROR, "The HQ scaler does not convert
formats, format must be same or %s.\n",
+ av_get_pix_fmt_name(in_sw_format));
+ return AVERROR(EINVAL);
+ }
+ ctx->format = in_sw_format;
+ } else if (ctx->format == AV_PIX_FMT_NONE) {
+ ctx->format = in_sw_format == AV_PIX_FMT_P010 ? AV_PIX_FMT_X2BGR10
: AV_PIX_FMT_RGBA;
+ } else if (!amf_is_packed_rgb(ctx->format)) {
+ av_log(avctx, AV_LOG_ERROR, "This algorithm only outputs packed
RGB, format=%s is not supported.\n",
+ av_get_pix_fmt_name(ctx->format));
+ return AVERROR(EINVAL);
+ }
+ }
err = amf_init_filter_config(outlink, &in_format);
if (err < 0)
return err;
- if (amf_hq_scaler_needs_packed_rgb(ctx->algorithm) &&
- (in_format == AV_PIX_FMT_NV12 || in_format == AV_PIX_FMT_P010)) {
- av_log(avctx, AV_LOG_ERROR,
- "%s requires a packed RGB format (rgba); %s is not supported. "
- "Convert the input first (e.g. format=rgba) or select another "
- "algorithm.\n",
- ctx->algorithm == AMF_HQ_SCALER_ALGORITHM_POINT ? "point"
- : "sr1-1
(VideoSR1.1)",
- av_get_pix_fmt_name(in_format));
- return AVERROR(EINVAL);
+ if (needs_conversion) {
+ AMFSize in_size = { inlink->w, inlink->h };
+
+ res =
ctx->amf_device_ctx->factory->pVtbl->CreateComponent(ctx->amf_device_ctx->factory,
ctx->amf_device_ctx->context, AMFVideoConverter, &ctx->pre_converter);
+ AMF_RETURN_IF_FALSE(ctx, res == AMF_OK, AVERROR_FILTER_NOT_FOUND,
"CreateComponent(%ls) failed with error %d\n", AMFVideoConverter, res);
+
+ AMF_ASSIGN_PROPERTY_INT64(res, ctx->pre_converter,
AMF_VIDEO_CONVERTER_OUTPUT_FORMAT, (amf_int32)av_av_to_amf_format(ctx->format));
+ AMF_RETURN_IF_FALSE(avctx, res == AMF_OK, AVERROR_UNKNOWN,
"AMFConverter-SetProperty() failed with error %d\n", res);
+ AMF_ASSIGN_PROPERTY_SIZE(res, ctx->pre_converter,
AMF_VIDEO_CONVERTER_OUTPUT_SIZE, in_size);
+ AMF_RETURN_IF_FALSE(avctx, res == AMF_OK, AVERROR_UNKNOWN,
"AMFConverter-SetProperty() failed with error %d\n", res);
+ AMF_ASSIGN_PROPERTY_INT64(res, ctx->pre_converter,
AMF_VIDEO_CONVERTER_OUTPUT_COLOR_RANGE, AMF_COLOR_RANGE_FULL);
+ AMF_RETURN_IF_FALSE(avctx, res == AMF_OK, AVERROR_UNKNOWN,
"AMFConverter-SetProperty() failed with error %d\n", res);
+
+ res = ctx->pre_converter->pVtbl->Init(ctx->pre_converter,
av_av_to_amf_format(in_format), inlink->w, inlink->h);
+ AMF_RETURN_IF_FALSE(avctx, res == AMF_OK, AVERROR_UNKNOWN,
+ "AMFConverter-Init() failed with error %d, %s to
%s is not supported by this device%s\n",
+ res, av_get_pix_fmt_name(in_format),
av_get_pix_fmt_name(ctx->format),
+ ctx->format == AV_PIX_FMT_X2BGR10 ? ", try
format=rgba" : "");
+
+ in_format = ctx->format;
}
// HQ scaler should be used for upscaling only
@@ -161,7 +210,7 @@ static int amf_filter_config_output(AVFilterLink *outlink)
ctx->in_primaries = AMF_COLOR_PRIMARIES_UNDEFINED;
ctx->in_trc = AMF_COLOR_TRANSFER_CHARACTERISTIC_UNDEFINED;
ctx->color_profile = AMF_VIDEO_CONVERTER_COLOR_PROFILE_UNKNOWN;
- ctx->out_color_range = AMF_COLOR_RANGE_UNDEFINED;
+ ctx->out_color_range = needs_conversion ? AMF_COLOR_RANGE_FULL :
AMF_COLOR_RANGE_UNDEFINED;
ctx->out_primaries = AMF_COLOR_PRIMARIES_UNDEFINED;
ctx->out_trc = AMF_COLOR_TRANSFER_CHARACTERISTIC_UNDEFINED;
--
2.52.0
>From f78bfe8193053a3792234f5bbc607f0025252769 Mon Sep 17 00:00:00 2001
From: Julius Bairaktaris <[email protected]>
Date: Sun, 30 Aug 2026 15:15:28 +0200
Subject: [PATCH 06/19] avfilter/amf: fix leaks and a NULL dereference in the
filter frame path
amf_filter_filter_frame() overwrote out->hw_frames_ctx with a second
reference to the output frames context. av_hwframe_get_buffer(), called
from amf_amfsurface_to_avframe() for every output, has already attached
one, so each frame leaked a reference and the frames context never
reached zero. Its AMF surface pool therefore survived filter teardown,
which matters to anything that rebuilds a filter graph. vf_frc_amf.c had
the same line.
The function also takes ownership of the input frame but returned without
freeing it on two paths, the QueryInterface() failure after QueryOutput()
and the case with no output, and it passed the result of
amf_amfsurface_to_avframe() to av_frame_copy_props() unchecked, so an
allocation failure or an unsupported surface memory type dereferenced
NULL. Route those through the existing fail label.
amf_amfsurface_to_avframe() only takes ownership of the surface once it
is attached to the frame, so its fail path and its frame allocation
failure leaked the surface for both callers; release it in both, and
check the av_buffer_create() that performs the attach.
amf_setup_input_output_formats() leaked the input list when allocating
the output list failed, and the output list when referencing the input
list failed.
Assisted-by: Claude Opus 5
Signed-off-by: Julius Bairaktaris <[email protected]>
---
libavfilter/vf_amf_common.c | 28 +++++++++++++++-------------
libavfilter/vf_frc_amf.c | 8 +-------
2 files changed, 16 insertions(+), 20 deletions(-)
diff --git a/libavfilter/vf_amf_common.c b/libavfilter/vf_amf_common.c
index f7cd680cbe..5700c6e2a4 100644
--- a/libavfilter/vf_amf_common.c
+++ b/libavfilter/vf_amf_common.c
@@ -143,12 +143,14 @@ int amf_filter_filter_frame(AVFilterLink *inlink, AVFrame
*in)
AMFGuid guid = IID_AMFSurface();
res = data_out->pVtbl->QueryInterface(data_out, &guid,
(void**)&surface_out); // query for buffer interface
data_out->pVtbl->Release(data_out);
- AMF_RETURN_IF_FALSE(avctx, res == AMF_OK, AVERROR_UNKNOWN,
"QueryInterface(IID_AMFSurface) failed with error %d\n", res);
+ AMF_GOTO_FAIL_IF_FALSE(avctx, res == AMF_OK, AVERROR_UNKNOWN,
"QueryInterface(IID_AMFSurface) failed with error %d\n", res);
} else {
- return AVERROR(EAGAIN);
+ ret = AVERROR(EAGAIN);
+ goto fail;
}
out = amf_amfsurface_to_avframe(avctx, surface_out);
+ AMF_GOTO_FAIL_IF_FALSE(avctx, out != NULL, AVERROR(ENOMEM), "Failed to
convert AMFSurface to AVFrame\n");
ret = av_frame_copy_props(out, in);
av_frame_unref(in);
@@ -197,12 +199,6 @@ int amf_filter_filter_frame(AVFilterLink *inlink, AVFrame
*in)
if (ret < 0)
goto fail;
- out->hw_frames_ctx = av_buffer_ref(ctx->hwframes_out_ref);
- if (!out->hw_frames_ctx) {
- ret = AVERROR(ENOMEM);
- goto fail;
- }
-
av_frame_free(&in);
return ff_filter_frame(outlink, out);
fail:
@@ -266,15 +262,16 @@ int amf_setup_input_output_formats(AVFilterContext *avctx,
}
output_formats = ff_make_pixel_format_list(output_pix_fmts);
if (!output_formats) {
+ ff_formats_unref(&input_formats);
return AVERROR(ENOMEM);
}
- if ((err = ff_formats_ref(input_formats,
&avctx->inputs[0]->outcfg.formats)) < 0)
+ if ((err = ff_formats_ref(input_formats,
&avctx->inputs[0]->outcfg.formats)) < 0) {
+ ff_formats_unref(&output_formats);
return err;
+ }
- if ((err = ff_formats_ref(output_formats,
&avctx->outputs[0]->incfg.formats)) < 0)
- return err;
- return 0;
+ return ff_formats_ref(output_formats, &avctx->outputs[0]->incfg.formats);
}
int amf_copy_surface(AVFilterContext *avctx, const AVFrame *frame,
@@ -427,8 +424,10 @@ AVFrame *amf_amfsurface_to_avframe(AVFilterContext *avctx,
AMFSurface* pSurface)
AVFrame *frame = av_frame_alloc();
AMFFilterContext *ctx = avctx->priv;
- if (!frame)
+ if (!frame) {
+ pSurface->pVtbl->Release(pSurface);
return NULL;
+ }
if (ctx->hwframes_out_ref) {
AVHWFramesContext *hwframes_out = (AVHWFramesContext
*)ctx->hwframes_out_ref->data;
@@ -443,6 +442,8 @@ AVFrame *amf_amfsurface_to_avframe(AVFilterContext *avctx,
AMFSurface* pSurface)
amf_free_amfsurface,
(void*)avctx,
AV_BUFFER_FLAG_READONLY);
+ if (!frame->buf[1])
+ goto fail;
} else { // FIXME: add processing of other hw formats
av_log(ctx, AV_LOG_ERROR, "Unknown pixel format\n");
goto fail;
@@ -491,6 +492,7 @@ AVFrame *amf_amfsurface_to_avframe(AVFilterContext *avctx,
AMFSurface* pSurface)
return frame;
fail:
+ pSurface->pVtbl->Release(pSurface);
av_frame_free(&frame);
return NULL;
}
diff --git a/libavfilter/vf_frc_amf.c b/libavfilter/vf_frc_amf.c
index f5ea93619b..1b914908a0 100644
--- a/libavfilter/vf_frc_amf.c
+++ b/libavfilter/vf_frc_amf.c
@@ -227,7 +227,7 @@ static int amf_frc_filter_avframe(AVFilterLink *inlink,
AVFrame *in)
res = AMF_IFACE_CALL(data_out, QueryInterface, &guid,
(void**)&surface_out);
AMF_IFACE_CALL(data_out, Release);
data_out = NULL;
- AMF_RETURN_IF_FALSE(avctx, res == AMF_OK, AVERROR_UNKNOWN,
"QueryInterface(IID_AMFSurface) failed with error %d\n", res);
+ AMF_GOTO_FAIL_IF_FALSE(avctx, res == AMF_OK, AVERROR_UNKNOWN,
"QueryInterface(IID_AMFSurface) failed with error %d\n", res);
out = amf_amfsurface_to_avframe(avctx, surface_out);
AMF_GOTO_FAIL_IF_FALSE(avctx, out != NULL, AVERROR(ENOMEM), "Failed to
convert AMFSurface to AVFrame\n");
@@ -240,12 +240,6 @@ static int amf_frc_filter_avframe(AVFilterLink *inlink,
AVFrame *in)
if (frc_ctx->enable)
out->duration /= 2;
- out->hw_frames_ctx = av_buffer_ref(amf_ctx->hwframes_out_ref);
- if (!out->hw_frames_ctx) {
- ret = AVERROR(ENOMEM);
- goto fail;
- }
-
ret = ff_filter_frame(outlink, out);
out = NULL;
if (ret < 0)
--
2.52.0
>From 284f4084c357c4766be29231dce5c17bfa7943c9 Mon Sep 17 00:00:00 2001
From: Julius Bairaktaris <[email protected]>
Date: Sun, 30 Aug 2026 15:30:45 +0200
Subject: [PATCH 07/19] avfilter/amf: make the input format lists describe what
AMF accepts
amf_setup_input_output_formats() builds both negotiated lists from
output_pix_fmts and ignores its input_pix_fmts argument, so the input
lists have never been used and have drifted from what the filters handle.
None of them lists AV_PIX_FMT_D3D11 or AV_PIX_FMT_DXVA2_VLD even though
amf_avframe_to_amfsurface() wraps both. sr_amf and vpp_amf carry the two
in their output lists, so they accept such input today only because the
input list is dead; vqe_amf and frc_amf carry them nowhere and gain the
input here. vpp_amf meanwhile lists three formats AMF will not take:
YUV420P10 and 0RGB have no format_map[] entry at all, and the video
converter rejects GRAY8 with AMF_NOT_SUPPORTED. Those three only appear
to work today because the dead input list leaves libavfilter converting
them in software first.
Correct the lists in both directions so they describe reality before the
helper is made to use them. No functional change on its own.
Assisted-by: Claude Opus 5
Signed-off-by: Julius Bairaktaris <[email protected]>
---
libavfilter/vf_frc_amf.c | 2 ++
libavfilter/vf_sr_amf.c | 4 ++++
libavfilter/vf_vpp_amf.c | 5 ++---
libavfilter/vf_vqe_amf.c | 2 ++
4 files changed, 10 insertions(+), 3 deletions(-)
diff --git a/libavfilter/vf_frc_amf.c b/libavfilter/vf_frc_amf.c
index 1b914908a0..5b93f65ad1 100644
--- a/libavfilter/vf_frc_amf.c
+++ b/libavfilter/vf_frc_amf.c
@@ -70,6 +70,8 @@ static int amf_filter_query_formats(AVFilterContext *avctx)
const enum AVPixelFormat *output_pix_fmts;
static const enum AVPixelFormat input_pix_fmts[] = {
AV_PIX_FMT_AMF_SURFACE,
+ AV_PIX_FMT_D3D11,
+ AV_PIX_FMT_DXVA2_VLD,
AV_PIX_FMT_NV12,
AV_PIX_FMT_P010,
AV_PIX_FMT_BGRA,
diff --git a/libavfilter/vf_sr_amf.c b/libavfilter/vf_sr_amf.c
index deb020809d..d9ed1dcd8a 100644
--- a/libavfilter/vf_sr_amf.c
+++ b/libavfilter/vf_sr_amf.c
@@ -84,6 +84,8 @@ static int amf_filter_query_formats(AVFilterContext *avctx)
AV_PIX_FMT_BGRA,
AV_PIX_FMT_RGBA,
AV_PIX_FMT_AMF_SURFACE,
+ AV_PIX_FMT_D3D11,
+ AV_PIX_FMT_DXVA2_VLD,
AV_PIX_FMT_RGBAF16,
AV_PIX_FMT_NONE,
};
@@ -105,6 +107,8 @@ static int amf_filter_query_formats(AVFilterContext *avctx)
AV_PIX_FMT_X2BGR10,
AV_PIX_FMT_RGBAF16,
AV_PIX_FMT_AMF_SURFACE,
+ AV_PIX_FMT_D3D11,
+ AV_PIX_FMT_DXVA2_VLD,
AV_PIX_FMT_NONE,
};
diff --git a/libavfilter/vf_vpp_amf.c b/libavfilter/vf_vpp_amf.c
index 839a075ea7..43277224ad 100644
--- a/libavfilter/vf_vpp_amf.c
+++ b/libavfilter/vf_vpp_amf.c
@@ -49,16 +49,15 @@ static int amf_filter_query_formats(AVFilterContext *avctx)
const enum AVPixelFormat *output_pix_fmts;
static const enum AVPixelFormat input_pix_fmts[] = {
AV_PIX_FMT_AMF_SURFACE,
+ AV_PIX_FMT_D3D11,
+ AV_PIX_FMT_DXVA2_VLD,
AV_PIX_FMT_NV12,
AV_PIX_FMT_P010,
- AV_PIX_FMT_0RGB,
AV_PIX_FMT_BGR0,
AV_PIX_FMT_BGRA,
AV_PIX_FMT_RGB0,
AV_PIX_FMT_RGBA,
- AV_PIX_FMT_GRAY8,
AV_PIX_FMT_YUV420P,
- AV_PIX_FMT_YUV420P10,
AV_PIX_FMT_YUYV422,
AV_PIX_FMT_NONE,
};
diff --git a/libavfilter/vf_vqe_amf.c b/libavfilter/vf_vqe_amf.c
index 70b533e475..d53703ee58 100644
--- a/libavfilter/vf_vqe_amf.c
+++ b/libavfilter/vf_vqe_amf.c
@@ -63,6 +63,8 @@ static int amf_filter_query_formats(AVFilterContext *avctx)
const enum AVPixelFormat *output_pix_fmts;
static const enum AVPixelFormat input_pix_fmts[] = {
AV_PIX_FMT_AMF_SURFACE,
+ AV_PIX_FMT_D3D11,
+ AV_PIX_FMT_DXVA2_VLD,
AV_PIX_FMT_NV12,
AV_PIX_FMT_P010,
AV_PIX_FMT_BGRA,
--
2.52.0
>From b0f19d35273c86d716b4ec1cd808c37b968b9762 Mon Sep 17 00:00:00 2001
From: Julius Bairaktaris <[email protected]>
Date: Sun, 30 Aug 2026 15:32:47 +0200
Subject: [PATCH 08/19] avfilter/amf: negotiate the input link from the input
format list
amf_setup_input_output_formats() built both negotiated lists from
output_pix_fmts, so every filter's input_pix_fmts argument was dead and
the input link only ever offered what the filter could output.
For vpp_amf, whose input list is deliberately wider than its output list,
libavfilter therefore inserted a software conversion in front of the
filter for any input format missing from the output list. P010 is the
damaging case: 10-bit software input was silently reduced to 8-bit
YUV420P before being uploaded, and the filter was then asked to convert
that back to P010.
Build input_formats from input_pix_fmts, and apply the D3D11VA and DXVA2
device overrides to both lists, which the old code got for free by
sharing one list.
Fixes the FFmpeg side of #21620. With
-f lavfi -i "smptehdbars=d=1,format=p010le" -vf "vpp_amf=format=p010le"
signalstats YAVG moves from 432.141 to 413.631, matching the untouched
P010 source exactly, and the p010le -> yuv420p conversion no longer
appears in the filter graph. The 8-bit-to-P010 half of that report is an
AMF limitation and is unaffected.
Tested on Windows 11, RX 9070 XT, driver 32.0.31041.1004: vpp_amf,
vqe_amf, frc_amf and every sr_amf algorithm, with AMF hardware surfaces
and with NV12, YUV420P, YUV420P10, YUYV422, GRAY8, 0RGB, RGB0, BGR0,
P010, RGBA and BGRA software input. No case that worked before stopped
working; software P010 and RGBA into vpp_amf now work where they
previously failed.
Assisted-by: Claude Opus 5
Signed-off-by: Julius Bairaktaris <[email protected]>
---
libavfilter/vf_amf_common.c | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/libavfilter/vf_amf_common.c b/libavfilter/vf_amf_common.c
index 5700c6e2a4..0fbefbfbc0 100644
--- a/libavfilter/vf_amf_common.c
+++ b/libavfilter/vf_amf_common.c
@@ -226,22 +226,24 @@ int amf_setup_input_output_formats(AVFilterContext *avctx,
#if CONFIG_D3D11VA
case AV_HWDEVICE_TYPE_D3D11VA:
{
- static const enum AVPixelFormat output_pix_fmts_d3d11[] = {
+ static const enum AVPixelFormat pix_fmts_d3d11[] = {
AV_PIX_FMT_D3D11,
AV_PIX_FMT_NONE,
};
- output_pix_fmts = output_pix_fmts_d3d11;
+ input_pix_fmts = pix_fmts_d3d11;
+ output_pix_fmts = pix_fmts_d3d11;
}
break;
#endif
#if CONFIG_DXVA2
case AV_HWDEVICE_TYPE_DXVA2:
{
- static const enum AVPixelFormat output_pix_fmts_dxva2[] = {
+ static const enum AVPixelFormat pix_fmts_dxva2[] = {
AV_PIX_FMT_DXVA2_VLD,
AV_PIX_FMT_NONE,
};
- output_pix_fmts = output_pix_fmts_dxva2;
+ input_pix_fmts = pix_fmts_dxva2;
+ output_pix_fmts = pix_fmts_dxva2;
}
break;
#endif
@@ -256,7 +258,7 @@ int amf_setup_input_output_formats(AVFilterContext *avctx,
}
}
- input_formats = ff_make_pixel_format_list(output_pix_fmts);
+ input_formats = ff_make_pixel_format_list(input_pix_fmts);
if (!input_formats) {
return AVERROR(ENOMEM);
}
--
2.52.0
>From 5fb1e2462bd705308a859ec83fdd3d900a532752 Mon Sep 17 00:00:00 2001
From: Julius Bairaktaris <[email protected]>
Date: Sun, 30 Aug 2026 15:34:30 +0200
Subject: [PATCH 09/19] avfilter/amf: pin the output format for components that
cannot convert
amf_init_filter_config() falls back to outlink->format for the output
frames context when no format is requested. That is right for vpp_amf,
whose converter is initialised from hwframes_out->sw_format and therefore
makes the frames context true. The HQ scaler, the VQ enhancer and the
frame rate converter have no output format property at all and emit the
format they were initialised with, so whatever the output link happened
to negotiate was attached to a surface that never had that layout.
ffmpeg -init_hw_device amf -f lavfi -i testsrc2 \
-vf "format=nv12,hwupload,sr_amf=w=2560:h=1440,format=rgba"
is enough to reach it: the link negotiates RGBA, the scaler emits NV12,
and hwdownload then reads a two-plane surface as one packed plane.
Take the input software format as the output format for those three
filters. sr_amf keeps its explicit format= handling: a packed RGB target
is still selectable for the algorithms that convert, and anything the
scaler cannot emit is rejected rather than silently mislabelled.
amf_inlink_sw_format() moves to vf_amf_common.c so all three can use it.
sr_amf compares against the format the user asked for rather than the one
a previous configuration resolved, so reconfiguring the graph with a
different input format does not reject itself.
Tested on Windows 11, RX 9070 XT, driver 32.0.31041.1004: for every
sr_amf algorithm and for vqe_amf and frc_amf, with NV12, P010 and RGBA
input, hwdownload now accepts exactly the format the component emits and
rejects every other, where before the frames context could claim any
format the output link negotiated.
Assisted-by: Claude Opus 5
Signed-off-by: Julius Bairaktaris <[email protected]>
---
libavfilter/vf_amf_common.c | 10 ++++++++++
libavfilter/vf_amf_common.h | 2 ++
libavfilter/vf_frc_amf.c | 2 ++
libavfilter/vf_sr_amf.c | 30 +++++++++++-------------------
libavfilter/vf_vqe_amf.c | 2 ++
5 files changed, 27 insertions(+), 19 deletions(-)
diff --git a/libavfilter/vf_amf_common.c b/libavfilter/vf_amf_common.c
index 0fbefbfbc0..9b80a7cd1b 100644
--- a/libavfilter/vf_amf_common.c
+++ b/libavfilter/vf_amf_common.c
@@ -53,6 +53,7 @@ int amf_filter_init(AVFilterContext *avctx)
return AVERROR(EINVAL);
}
}
+ ctx->format_opt = ctx->format;
return 0;
}
@@ -300,6 +301,15 @@ int amf_copy_surface(AVFilterContext *avctx, const AVFrame
*frame,
return 0;
}
+enum AVPixelFormat amf_inlink_sw_format(AVFilterLink *inlink)
+{
+ FilterLink *inl = ff_filter_link(inlink);
+
+ if (inl->hw_frames_ctx)
+ return ((AVHWFramesContext*)inl->hw_frames_ctx->data)->sw_format;
+ return inlink->format;
+}
+
int amf_init_filter_config(AVFilterLink *outlink, enum AVPixelFormat
*in_format)
{
int err;
diff --git a/libavfilter/vf_amf_common.h b/libavfilter/vf_amf_common.h
index 7879403d06..0d323dd480 100644
--- a/libavfilter/vf_amf_common.h
+++ b/libavfilter/vf_amf_common.h
@@ -31,6 +31,7 @@ typedef struct AMFFilterContext {
int width, height;
enum AVPixelFormat format;
+ enum AVPixelFormat format_opt;
int scale_type;
int in_color_range;
int in_primaries;
@@ -71,6 +72,7 @@ typedef struct AMFFilterContext {
int amf_filter_init(AVFilterContext *avctx);
void amf_filter_uninit(AVFilterContext *avctx);
+enum AVPixelFormat amf_inlink_sw_format(AVFilterLink *inlink);
int amf_init_filter_config(AVFilterLink *outlink, enum AVPixelFormat
*in_format);
int amf_copy_surface(AVFilterContext *avctx, const AVFrame *frame, AMFSurface*
surface);
void amf_free_amfsurface(void *opaque, uint8_t *data);
diff --git a/libavfilter/vf_frc_amf.c b/libavfilter/vf_frc_amf.c
index 5b93f65ad1..1a84a2ee4f 100644
--- a/libavfilter/vf_frc_amf.c
+++ b/libavfilter/vf_frc_amf.c
@@ -110,6 +110,8 @@ static int amf_frc_filter_config_output(AVFilterLink
*outlink)
AMF_RESULT res;
enum AVPixelFormat in_format;
+ amf_ctx->format = amf_inlink_sw_format(inlink);
+
err = amf_init_filter_config(outlink, &in_format);
if (err < 0)
return err;
diff --git a/libavfilter/vf_sr_amf.c b/libavfilter/vf_sr_amf.c
index d9ed1dcd8a..fcaf2c5d2a 100644
--- a/libavfilter/vf_sr_amf.c
+++ b/libavfilter/vf_sr_amf.c
@@ -53,15 +53,6 @@
#endif
-static enum AVPixelFormat amf_inlink_sw_format(AVFilterLink *inlink)
-{
- FilterLink *inl = ff_filter_link(inlink);
-
- if (inl->hw_frames_ctx)
- return ((AVHWFramesContext*)inl->hw_frames_ctx->data)->sw_format;
- return inlink->format;
-}
-
static int amf_hq_scaler_needs_packed_rgb(int algorithm)
{
return algorithm == AMF_HQ_SCALER_ALGORITHM_VIDEOSR1_1 ||
@@ -140,21 +131,22 @@ static int amf_filter_config_output(AVFilterLink *outlink)
needs_conversion = amf_hq_scaler_needs_packed_rgb(ctx->algorithm) &&
(in_sw_format == AV_PIX_FMT_NV12 || in_sw_format ==
AV_PIX_FMT_P010);
- if (amf_hq_scaler_needs_packed_rgb(ctx->algorithm)) {
- if (!needs_conversion) {
- if (ctx->format != AV_PIX_FMT_NONE && ctx->format != in_sw_format)
{
- av_log(avctx, AV_LOG_ERROR, "The HQ scaler does not convert
formats, format must be same or %s.\n",
- av_get_pix_fmt_name(in_sw_format));
- return AVERROR(EINVAL);
- }
- ctx->format = in_sw_format;
- } else if (ctx->format == AV_PIX_FMT_NONE) {
+ ctx->format = ctx->format_opt;
+
+ if (needs_conversion) {
+ if (ctx->format == AV_PIX_FMT_NONE)
ctx->format = in_sw_format == AV_PIX_FMT_P010 ? AV_PIX_FMT_X2BGR10
: AV_PIX_FMT_RGBA;
- } else if (!amf_is_packed_rgb(ctx->format)) {
+ else if (!amf_is_packed_rgb(ctx->format)) {
av_log(avctx, AV_LOG_ERROR, "This algorithm only outputs packed
RGB, format=%s is not supported.\n",
av_get_pix_fmt_name(ctx->format));
return AVERROR(EINVAL);
}
+ } else if (ctx->format != AV_PIX_FMT_NONE && ctx->format != in_sw_format) {
+ av_log(avctx, AV_LOG_ERROR, "The HQ scaler does not convert formats,
format must be same or %s.\n",
+ av_get_pix_fmt_name(in_sw_format));
+ return AVERROR(EINVAL);
+ } else {
+ ctx->format = in_sw_format;
}
err = amf_init_filter_config(outlink, &in_format);
diff --git a/libavfilter/vf_vqe_amf.c b/libavfilter/vf_vqe_amf.c
index d53703ee58..901cfbdb5e 100644
--- a/libavfilter/vf_vqe_amf.c
+++ b/libavfilter/vf_vqe_amf.c
@@ -102,6 +102,8 @@ static int amf_vqe_filter_config_output(AVFilterLink
*outlink)
AMF_RESULT res;
enum AVPixelFormat in_format;
+ amf_ctx->format = amf_inlink_sw_format(inlink);
+
err = amf_init_filter_config(outlink, &in_format);
if (err < 0)
return err;
--
2.52.0
>From c3c2749b718392def802cf5784bd06acc79c9ee0 Mon Sep 17 00:00:00 2001
From: Julius Bairaktaris <[email protected]>
Date: Sun, 30 Aug 2026 15:53:03 +0200
Subject: [PATCH 10/19] avfilter/vf_amf_common: drain the component and
tolerate AMF_REPEAT
amf_filter_filter_frame() read the component output exactly once and
treated anything but AMF_OK as fatal, so a component that had no output
ready yet, which AMD's Video Converter programming guide documents as
AMF_REPEAT, produced an error instead of simply consuming the frame.
Poll until the component stops returning data, and accept AMF_REPEAT.
A component that produces one output per input, which is what the scaler,
the enhancer and the converter do today, behaves as before: one iteration
with data, one without. Timestamps now come from the output surface, as
vf_frc_amf.c already does, so a second output of the same submission does
not inherit the first one's.
Returning AVERROR(EAGAIN) from a filter_frame callback was wrong and is
dropped. filter_frame_to_filter() latches a negative return into the
link's status_out, which stalls a pull driven graph and stops EOF from
crossing the filter; a frame that yields no output is simply consumed.
AMF_INPUT_FULL on submission stays fatal. It means the frame was not
accepted, so tolerating it would silently drop it, and the queue is
already drained after every submission, so it cannot be reached by
feeding the component faster than it produces.
The input frame is kept until the loop ends, since its properties are
copied onto every output, and freed once at the end.
Tested on Windows 11, RX 9070 XT, driver 32.0.31041.1004: identical
output and timestamps for every sr_amf algorithm, vpp_amf, vqe_amf and
frc_amf, with software, AMF and D3D11VA input.
Assisted-by: Claude Opus 5
Signed-off-by: Julius Bairaktaris <[email protected]>
---
libavfilter/vf_amf_common.c | 112 +++++++++++++++++++-----------------
1 file changed, 60 insertions(+), 52 deletions(-)
diff --git a/libavfilter/vf_amf_common.c b/libavfilter/vf_amf_common.c
index 9b80a7cd1b..0d2a2f6257 100644
--- a/libavfilter/vf_amf_common.c
+++ b/libavfilter/vf_amf_common.c
@@ -99,6 +99,7 @@ int amf_filter_filter_frame(AVFilterLink *inlink, AVFrame *in)
enum AVColorRange out_color_range;
AVFrame *out = NULL;
+ int got_frame = 0;
int ret = 0;
if (!ctx->component)
@@ -137,71 +138,78 @@ int amf_filter_filter_frame(AVFilterLink *inlink, AVFrame
*in)
res = ctx->component->pVtbl->SubmitInput(ctx->component,
(AMFData*)surface_in);
surface_in->pVtbl->Release(surface_in); // release surface after use
AMF_GOTO_FAIL_IF_FALSE(avctx, res == AMF_OK, AVERROR_UNKNOWN,
"SubmitInput() failed with error %d\n", res);
- res = ctx->component->pVtbl->QueryOutput(ctx->component, &data_out);
- AMF_GOTO_FAIL_IF_FALSE(avctx, res == AMF_OK, AVERROR_UNKNOWN,
"QueryOutput() failed with error %d\n", res);
- if (data_out) {
+ while (1) {
AMFGuid guid = IID_AMFSurface();
+
+ res = ctx->component->pVtbl->QueryOutput(ctx->component, &data_out);
+ AMF_GOTO_FAIL_IF_FALSE(avctx, res == AMF_OK || res == AMF_REPEAT,
AVERROR_UNKNOWN, "QueryOutput() failed with error %d\n", res);
+ if (!data_out)
+ break;
+
res = data_out->pVtbl->QueryInterface(data_out, &guid,
(void**)&surface_out); // query for buffer interface
data_out->pVtbl->Release(data_out);
+ data_out = NULL;
AMF_GOTO_FAIL_IF_FALSE(avctx, res == AMF_OK, AVERROR_UNKNOWN,
"QueryInterface(IID_AMFSurface) failed with error %d\n", res);
- } else {
- ret = AVERROR(EAGAIN);
- goto fail;
- }
- out = amf_amfsurface_to_avframe(avctx, surface_out);
- AMF_GOTO_FAIL_IF_FALSE(avctx, out != NULL, AVERROR(ENOMEM), "Failed to
convert AMFSurface to AVFrame\n");
+ out = amf_amfsurface_to_avframe(avctx, surface_out);
+ AMF_GOTO_FAIL_IF_FALSE(avctx, out != NULL, AVERROR(ENOMEM), "Failed to
convert AMFSurface to AVFrame\n");
- ret = av_frame_copy_props(out, in);
- av_frame_unref(in);
+ ret = av_frame_copy_props(out, in);
+ if (ret < 0)
+ goto fail;
+ out->pts = surface_out->pVtbl->GetPts(surface_out);
- out_colorspace = AVCOL_SPC_UNSPECIFIED;
+ out_colorspace = AVCOL_SPC_UNSPECIFIED;
- if (ctx->color_profile != AMF_VIDEO_CONVERTER_COLOR_PROFILE_UNKNOWN) {
- switch(ctx->color_profile) {
- case AMF_VIDEO_CONVERTER_COLOR_PROFILE_601:
- out_colorspace = AVCOL_SPC_SMPTE170M;
- break;
- case AMF_VIDEO_CONVERTER_COLOR_PROFILE_709:
- out_colorspace = AVCOL_SPC_BT709;
- break;
- case AMF_VIDEO_CONVERTER_COLOR_PROFILE_2020:
- out_colorspace = AVCOL_SPC_BT2020_NCL;
- break;
- case AMF_VIDEO_CONVERTER_COLOR_PROFILE_JPEG:
- out_colorspace = AVCOL_SPC_RGB;
- break;
- default:
- out_colorspace = AVCOL_SPC_UNSPECIFIED;
- break;
+ if (ctx->color_profile != AMF_VIDEO_CONVERTER_COLOR_PROFILE_UNKNOWN) {
+ switch(ctx->color_profile) {
+ case AMF_VIDEO_CONVERTER_COLOR_PROFILE_601:
+ out_colorspace = AVCOL_SPC_SMPTE170M;
+ break;
+ case AMF_VIDEO_CONVERTER_COLOR_PROFILE_709:
+ out_colorspace = AVCOL_SPC_BT709;
+ break;
+ case AMF_VIDEO_CONVERTER_COLOR_PROFILE_2020:
+ out_colorspace = AVCOL_SPC_BT2020_NCL;
+ break;
+ case AMF_VIDEO_CONVERTER_COLOR_PROFILE_JPEG:
+ out_colorspace = AVCOL_SPC_RGB;
+ break;
+ default:
+ out_colorspace = AVCOL_SPC_UNSPECIFIED;
+ break;
+ }
+ out->colorspace = out_colorspace;
}
- out->colorspace = out_colorspace;
+
+ out_color_range = AVCOL_RANGE_UNSPECIFIED;
+ if (ctx->out_color_range == AMF_COLOR_RANGE_FULL)
+ out_color_range = AVCOL_RANGE_JPEG;
+ else if (ctx->out_color_range == AMF_COLOR_RANGE_STUDIO)
+ out_color_range = AVCOL_RANGE_MPEG;
+
+ if (ctx->out_color_range != AMF_COLOR_RANGE_UNDEFINED)
+ out->color_range = out_color_range;
+
+ if (ctx->out_primaries != AMF_COLOR_PRIMARIES_UNDEFINED)
+ out->color_primaries = ctx->out_primaries;
+
+ if (ctx->out_trc != AMF_COLOR_TRANSFER_CHARACTERISTIC_UNDEFINED)
+ out->color_trc = ctx->out_trc;
+
+ if (ctx->pre_converter)
+ out->colorspace = AVCOL_SPC_RGB;
+
+ ret = ff_filter_frame(outlink, out);
+ out = NULL;
+ if (ret < 0)
+ goto fail;
+ got_frame = 1;
}
- out_color_range = AVCOL_RANGE_UNSPECIFIED;
- if (ctx->out_color_range == AMF_COLOR_RANGE_FULL)
- out_color_range = AVCOL_RANGE_JPEG;
- else if (ctx->out_color_range == AMF_COLOR_RANGE_STUDIO)
- out_color_range = AVCOL_RANGE_MPEG;
-
- if (ctx->out_color_range != AMF_COLOR_RANGE_UNDEFINED)
- out->color_range = out_color_range;
-
- if (ctx->out_primaries != AMF_COLOR_PRIMARIES_UNDEFINED)
- out->color_primaries = ctx->out_primaries;
-
- if (ctx->out_trc != AMF_COLOR_TRANSFER_CHARACTERISTIC_UNDEFINED)
- out->color_trc = ctx->out_trc;
-
- if (ctx->pre_converter)
- out->colorspace = AVCOL_SPC_RGB;
-
- if (ret < 0)
- goto fail;
-
av_frame_free(&in);
- return ff_filter_frame(outlink, out);
+ return got_frame ? ret : 0;
fail:
av_frame_free(&in);
av_frame_free(&out);
--
2.52.0
>From 72b963bc51b4461e95a5be52c8494955acfbdde1 Mon Sep 17 00:00:00 2001
From: Julius Bairaktaris <[email protected]>
Date: Sun, 30 Aug 2026 15:53:03 +0200
Subject: [PATCH 11/19] avfilter/vf_vqe_amf: only offer the formats the VQ
enhancer accepts
vqe_amf advertised BGRA, RGBA, RGBAF16 and X2BGR10 alongside NV12 and
P010, but AMFVQEnhancer::Init() rejects all four with AMF_INVALID_ARG.
Now that the input list is used for negotiation, a packed RGB source
reaches the filter and fails at configuration time instead of being
converted to a format the component takes.
Drop the four from both lists. libavfilter then converts such a source to
NV12 in software and the filter works, where before it aborted. A
hardware frames context is not covered by format negotiation, so reject
an unsupported sw_format explicitly rather than leaving the user with
AMF_INVALID_ARG from Init().
The component's own capability query cannot be used to build this list.
AMFVQEnhancer enumerates NV12, BGRA, ARGB, RGBA, P010, RGBA_F16 and
R10G10B10A2 through GetCaps()/GetInputCaps(), each flagged native and
stable across runs, but Init() accepts only NV12 and P010 and rejects the
other five. AMFHQScaler and AMFVideoConverter do not have this problem,
so the list cannot simply follow the query for every component. Reported
as GPUOpen-LibrariesAndSDKs/AMF#610.
Tested on Windows 11, RX 9070 XT, driver 32.0.31041.1004.
Assisted-by: Claude Opus 5
Signed-off-by: Julius Bairaktaris <[email protected]>
---
libavfilter/vf_vqe_amf.c | 15 +++++++--------
1 file changed, 7 insertions(+), 8 deletions(-)
diff --git a/libavfilter/vf_vqe_amf.c b/libavfilter/vf_vqe_amf.c
index 901cfbdb5e..d368770c89 100644
--- a/libavfilter/vf_vqe_amf.c
+++ b/libavfilter/vf_vqe_amf.c
@@ -23,6 +23,7 @@
#include "libavutil/opt.h"
+#include "libavutil/pixdesc.h"
#include "libavutil/hwcontext.h"
#include "libavutil/hwcontext_amf.h"
#include "libavutil/hwcontext_amf_internal.h"
@@ -67,20 +68,12 @@ static int amf_filter_query_formats(AVFilterContext *avctx)
AV_PIX_FMT_DXVA2_VLD,
AV_PIX_FMT_NV12,
AV_PIX_FMT_P010,
- AV_PIX_FMT_BGRA,
- AV_PIX_FMT_RGBA,
- AV_PIX_FMT_RGBAF16,
- AV_PIX_FMT_X2BGR10,
AV_PIX_FMT_NONE,
};
static const enum AVPixelFormat output_pix_fmts_default[] = {
AV_PIX_FMT_AMF_SURFACE,
AV_PIX_FMT_NV12,
AV_PIX_FMT_P010,
- AV_PIX_FMT_BGRA,
- AV_PIX_FMT_RGBA,
- AV_PIX_FMT_RGBAF16,
- AV_PIX_FMT_X2BGR10,
AV_PIX_FMT_NONE,
};
output_pix_fmts = output_pix_fmts_default;
@@ -108,6 +101,12 @@ static int amf_vqe_filter_config_output(AVFilterLink
*outlink)
if (err < 0)
return err;
+ if (in_format != AV_PIX_FMT_NV12 && in_format != AV_PIX_FMT_P010) {
+ av_log(avctx, AV_LOG_ERROR, "The VQ enhancer only accepts nv12 and
p010, got %s.\n",
+ av_get_pix_fmt_name(in_format));
+ return AVERROR(EINVAL);
+ }
+
device_ctx = amf_ctx->amf_device_ctx;
res = AMF_IFACE_CALL(device_ctx->factory, CreateComponent,
device_ctx->context, AMFVQEnhancer, &amf_ctx->component);
--
2.52.0
>From c62f33c51f02137c239fa2eda49d69fcc0e6b735 Mon Sep 17 00:00:00 2001
From: Julius Bairaktaris <[email protected]>
Date: Sun, 30 Aug 2026 15:53:03 +0200
Subject: [PATCH 12/19] avfilter/amf: log CreateComponent failures on the
filter context
These four call sites pass the filter's private context to av_log() while
every other call around them passes the AVFilterContext. Both happen to
start with an AVClass, so nothing misbehaves, but the private class has
no parent set, so the message loses the "[Parsed_sr_amf_0 @ ...]" prefix
that identifies which filter instance failed.
Assisted-by: Claude Opus 5
Signed-off-by: Julius Bairaktaris <[email protected]>
---
libavfilter/vf_sr_amf.c | 4 ++--
libavfilter/vf_vpp_amf.c | 2 +-
libavfilter/vsrc_amf.c | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/libavfilter/vf_sr_amf.c b/libavfilter/vf_sr_amf.c
index fcaf2c5d2a..cfde4886b1 100644
--- a/libavfilter/vf_sr_amf.c
+++ b/libavfilter/vf_sr_amf.c
@@ -157,7 +157,7 @@ static int amf_filter_config_output(AVFilterLink *outlink)
AMFSize in_size = { inlink->w, inlink->h };
res =
ctx->amf_device_ctx->factory->pVtbl->CreateComponent(ctx->amf_device_ctx->factory,
ctx->amf_device_ctx->context, AMFVideoConverter, &ctx->pre_converter);
- AMF_RETURN_IF_FALSE(ctx, res == AMF_OK, AVERROR_FILTER_NOT_FOUND,
"CreateComponent(%ls) failed with error %d\n", AMFVideoConverter, res);
+ AMF_RETURN_IF_FALSE(avctx, res == AMF_OK, AVERROR_FILTER_NOT_FOUND,
"CreateComponent(%ls) failed with error %d\n", AMFVideoConverter, res);
AMF_ASSIGN_PROPERTY_INT64(res, ctx->pre_converter,
AMF_VIDEO_CONVERTER_OUTPUT_FORMAT, (amf_int32)av_av_to_amf_format(ctx->format));
AMF_RETURN_IF_FALSE(avctx, res == AMF_OK, AVERROR_UNKNOWN,
"AMFConverter-SetProperty() failed with error %d\n", res);
@@ -182,7 +182,7 @@ static int amf_filter_config_output(AVFilterLink *outlink)
}
// FIXME: add checks whether we have HW context
res =
ctx->amf_device_ctx->factory->pVtbl->CreateComponent(ctx->amf_device_ctx->factory,
ctx->amf_device_ctx->context, AMFHQScaler, &ctx->component);
- AMF_RETURN_IF_FALSE(ctx, res == AMF_OK, AVERROR_FILTER_NOT_FOUND,
"CreateComponent(%ls) failed with error %d\n", AMFHQScaler, res);
+ AMF_RETURN_IF_FALSE(avctx, res == AMF_OK, AVERROR_FILTER_NOT_FOUND,
"CreateComponent(%ls) failed with error %d\n", AMFHQScaler, res);
mem_type = av_amf_get_memory_type(ctx->amf_device_ctx);
if (mem_type != AMF_MEMORY_UNKNOWN)
diff --git a/libavfilter/vf_vpp_amf.c b/libavfilter/vf_vpp_amf.c
index 43277224ad..1cd7e81128 100644
--- a/libavfilter/vf_vpp_amf.c
+++ b/libavfilter/vf_vpp_amf.c
@@ -97,7 +97,7 @@ static int amf_filter_config_output(AVFilterLink *outlink)
// FIXME: add checks whether we have HW context
hwframes_out = (AVHWFramesContext*)ctx->hwframes_out_ref->data;
res =
ctx->amf_device_ctx->factory->pVtbl->CreateComponent(ctx->amf_device_ctx->factory,
ctx->amf_device_ctx->context, AMFVideoConverter, &ctx->component);
- AMF_RETURN_IF_FALSE(ctx, res == AMF_OK, AVERROR_FILTER_NOT_FOUND,
"CreateComponent(%ls) failed with error %d\n", AMFVideoConverter, res);
+ AMF_RETURN_IF_FALSE(avctx, res == AMF_OK, AVERROR_FILTER_NOT_FOUND,
"CreateComponent(%ls) failed with error %d\n", AMFVideoConverter, res);
mem_type = av_amf_get_memory_type(ctx->amf_device_ctx);
if (mem_type != AMF_MEMORY_UNKNOWN)
diff --git a/libavfilter/vsrc_amf.c b/libavfilter/vsrc_amf.c
index 2a9811ae4a..e3fb8b9d38 100644
--- a/libavfilter/vsrc_amf.c
+++ b/libavfilter/vsrc_amf.c
@@ -159,7 +159,7 @@ static int amf_init_vsrc(AVFilterLink *outlink)
amf_device_ctx->context,
AMFDisplayCapture,
&ctx->capture);
- AMF_RETURN_IF_FALSE(ctx, res == AMF_OK, AVERROR_FILTER_NOT_FOUND,
"CreateComponent(%ls) failed with error %d\n", AMFDisplayCapture, res);
+ AMF_RETURN_IF_FALSE(avctx, res == AMF_OK, AVERROR_FILTER_NOT_FOUND,
"CreateComponent(%ls) failed with error %d\n", AMFDisplayCapture, res);
AMF_ASSIGN_PROPERTY_INT64(res, ctx->capture,
AMF_DISPLAYCAPTURE_MONITOR_INDEX, ctx->monitor_index);
if (res != AMF_OK) {
--
2.52.0
>From c6857b4db37c3b99f13c56923f46ec8675650851 Mon Sep 17 00:00:00 2001
From: Julius Bairaktaris <[email protected]>
Date: Sun, 30 Aug 2026 16:04:10 +0200
Subject: [PATCH 13/19] avfilter/amf: copy D3D11 decoder textures the
components can read
The AMF filter components read their input with a shader and reject a
texture created without D3D11_BIND_SHADER_RESOURCE, which is what a
D3D11VA decoder pool provides. Every AMF filter therefore fails on
ffmpeg -hwaccel d3d11va -hwaccel_output_format d3d11 \
-i in.mp4 -vf vpp_amf ...
with AMF_DIRECTX_FAILED from the first QueryOutput(), while frc_amf and
the AMF encoders consume the same surfaces. The only way out was the
device option SHADER=1, which the usual command lines do not set.
Copy the slice into an AMF allocated surface, which carries the flags the
components need. CopySubresourceRegion() runs on the copy engine, so it
can read a decoder texture that a shader cannot. AMFSurface::Duplicate(),
suggested upstream for this, returns AMF_OK but yields a surface that
fails in exactly the same way, for AMF_MEMORY_DX11 and AMF_MEMORY_HOST
alike.
Only a frame whose texture lacks the flag is copied, so a pool created
with SHADER=1 keeps the zero copy path, and only the components that need
a shader readable input ask for the copy. The frame rate converter reads
the decoder texture as it is, so it is left on the zero copy path through
a flag on the shared filter context.
Tested on Windows 11, RX 9070 XT, driver 32.0.31041.1004, with
-hwaccel d3d11va -hwaccel_output_format d3d11: vpp_amf, vqe_amf and every
sr_amf algorithm produce output matching the plain decode where they
previously failed, and frc_amf is unchanged.
Refs https://github.com/GPUOpen-LibrariesAndSDKs/AMF/issues/605
Assisted-by: Claude Opus 5
Signed-off-by: Julius Bairaktaris <[email protected]>
---
libavfilter/vf_amf_common.c | 66 +++++++++++++++++++++++++++++++++++++
libavfilter/vf_amf_common.h | 1 +
libavfilter/vf_vqe_amf.c | 1 +
3 files changed, 68 insertions(+)
diff --git a/libavfilter/vf_amf_common.c b/libavfilter/vf_amf_common.c
index 0d2a2f6257..2d70d6d954 100644
--- a/libavfilter/vf_amf_common.c
+++ b/libavfilter/vf_amf_common.c
@@ -38,6 +38,7 @@
#if CONFIG_D3D11VA
#include <d3d11.h>
+#include "libavutil/hwcontext_d3d11va.h"
#endif
int amf_filter_init(AVFilterContext *avctx)
@@ -54,6 +55,7 @@ int amf_filter_init(AVFilterContext *avctx)
}
}
ctx->format_opt = ctx->format;
+ ctx->shader_input = 1;
return 0;
}
@@ -517,6 +519,58 @@ fail:
return NULL;
}
+#if CONFIG_D3D11VA
+/* The AMF filter components read their input with a shader, so they reject a
+ * texture created without D3D11_BIND_SHADER_RESOURCE, which is what a D3D11VA
+ * decoder pool gives us. Copy the slice into an AMF allocated surface, which
+ * carries the flags the components need. CopySubresourceRegion() uses the copy
+ * engine, so it can read the decoder texture that a shader cannot. */
+static int amf_copy_d3d11_texture(AVFilterContext *avctx, const AVFrame *frame,
+ int index, AMFSurface **ppSurface)
+{
+ AMFFilterContext *ctx = avctx->priv;
+ AVHWFramesContext *frames =
(AVHWFramesContext*)frame->hw_frames_ctx->data;
+ AVD3D11VADeviceContext *hwctx = frames->device_ctx->hwctx;
+ ID3D11Texture2D *texture = (ID3D11Texture2D*)frame->data[0];
+ AMFSurface *surface = NULL;
+ AMFPlane *plane;
+ D3D11_TEXTURE2D_DESC desc;
+ D3D11_BOX box;
+ AMF_RESULT res;
+
+ res =
ctx->amf_device_ctx->context->pVtbl->AllocSurface(ctx->amf_device_ctx->context,
+ AMF_MEMORY_DX11, av_av_to_amf_format(frames->sw_format),
+ frame->width, frame->height, &surface);
+ AMF_RETURN_IF_FALSE(avctx, res == AMF_OK, AVERROR(ENOMEM), "AllocSurface()
failed with error %d\n", res);
+
+ plane = surface->pVtbl->GetPlaneAt(surface, 0);
+ if (!plane) {
+ surface->pVtbl->Release(surface);
+ return AVERROR(ENOMEM);
+ }
+
+ // The decoder pool is allocated with aligned dimensions, so copy the coded
+ // area rather than the whole source subresource. D3D11 wants even bounds
+ // for a planar format, and the source is at least that large.
+ 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;
+
+ hwctx->lock(hwctx->lock_ctx);
+ hwctx->device_context->lpVtbl->CopySubresourceRegion(hwctx->device_context,
+ (ID3D11Resource*)plane->pVtbl->GetNative(plane), 0, 0, 0, 0,
+ (ID3D11Resource*)texture, index, &box);
+ hwctx->unlock(hwctx->lock_ctx);
+
+ *ppSurface = surface;
+ return 0;
+}
+#endif
+
int amf_avframe_to_amfsurface(AVFilterContext *avctx, const AVFrame *frame,
AMFSurface** ppSurface)
{
AMFVariantStruct var = { 0 };
@@ -533,6 +587,18 @@ int amf_avframe_to_amfsurface(AVFilterContext *avctx,
const AVFrame *frame, AMFS
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
+ D3D11_TEXTURE2D_DESC desc;
+ int ret;
+
+ texture->lpVtbl->GetDesc(texture, &desc);
+ if (ctx->shader_input && !(desc.BindFlags &
D3D11_BIND_SHADER_RESOURCE) && frame->hw_frames_ctx) {
+ ret = amf_copy_d3d11_texture(avctx, frame, index, &surface);
+ if (ret < 0)
+ return ret;
+ hw_surface = 1;
+ break;
+ }
+
texture->lpVtbl->SetPrivateData(texture,
&AMFTextureArrayIndexGUID, sizeof(index), &index);
res =
ctx->amf_device_ctx->context->pVtbl->CreateSurfaceFromDX11Native(ctx->amf_device_ctx->context,
texture, &surface, NULL); // wrap to AMF surface
diff --git a/libavfilter/vf_amf_common.h b/libavfilter/vf_amf_common.h
index 0d323dd480..eedaef7ba6 100644
--- a/libavfilter/vf_amf_common.h
+++ b/libavfilter/vf_amf_common.h
@@ -68,6 +68,7 @@ typedef struct AMFFilterContext {
AVAMFDeviceContext *amf_device_ctx;
int local_context;
+ int shader_input;
} AMFFilterContext;
int amf_filter_init(AVFilterContext *avctx);
diff --git a/libavfilter/vf_vqe_amf.c b/libavfilter/vf_vqe_amf.c
index d368770c89..63febf5c15 100644
--- a/libavfilter/vf_vqe_amf.c
+++ b/libavfilter/vf_vqe_amf.c
@@ -55,6 +55,7 @@ static int amf_vqe_init(AVFilterContext *avctx) {
AMFVQEFilterContext *ctx = avctx->priv;
ctx->common.format = AV_PIX_FMT_NONE;
+ ctx->common.shader_input = 1;
return 0;
}
--
2.52.0
>From b4e5d510262d4bb02a33ebe33c4bf3d22ff7d904 Mon Sep 17 00:00:00 2001
From: Julius Bairaktaris <[email protected]>
Date: Mon, 31 Aug 2026 16:07:36 +0200
Subject: [PATCH 14/19] avfilter/vf_sr_amf: let format= choose the packed
format the scaler runs on
For point and sr1-1 the input link offered all four packed RGB formats
regardless of format=, so a software producer was converted to whichever
one libavfilter picked, RGBA for NV12 and X2BGR10 for P010. The input was
then already packed, no GPU conversion was set up, and a different
explicit target was rejected as a format the scaler cannot produce:
ffmpeg -f lavfi -i testsrc2,format=nv12 \
-vf sr_amf=w=2560:h=1440:algorithm=point:format=bgra ...
failed with "The HQ scaler does not convert formats" for a format the
documentation advertises.
Offer only the requested format on the input link when format= names one
the filter accepts, next to the hardware formats, so libavfilter converts
software input to that format directly. The hardware path reaches the
same target through the GPU converter, which now also runs when a
hardware surface is packed RGB but not the requested one, instead of
being limited to NV12 and P010 sources.
Tested on Windows 11, RX 9070 XT, driver 32.0.31041.1004: software and
AMF surface NV12 and P010 input crossed with format=rgba, bgra and
x2bgr10le, and hardware input for rgbaf16le, all produce a frame matching
the source. rgbaf16le from software input stays unreachable because
libswscale has no conversion to it, from any format.
Assisted-by: Claude Opus 5
Signed-off-by: Julius Bairaktaris <[email protected]>
---
doc/filters.texi | 9 ++++++---
libavfilter/vf_sr_amf.c | 35 ++++++++++++++++++++++++++++-------
2 files changed, 34 insertions(+), 10 deletions(-)
diff --git a/doc/filters.texi b/doc/filters.texi
index ed54f1e4c6..9feaf151e7 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -23296,9 +23296,12 @@ pixel format is used.
The @code{point} and @code{sr1-1} algorithms emit the packed RGB format the
scaler receives, so for them this option must name one of @code{rgba},
-@code{bgra}, @code{x2bgr10le} or @code{rgbaf16le}. When the input is converted
-automatically, @code{x2bgr10le} is selected for @code{p010} input and
-@code{rgba} otherwise, and the result is tagged as full range RGB.
+@code{bgra}, @code{x2bgr10le} or @code{rgbaf16le}. A named format also decides
+what software input is converted to, and what a hardware surface in another
+format is converted to on the GPU. Without it, @code{x2bgr10le} is selected for
+@code{p010} input and @code{rgba} for other non-RGB input, and the result is
+tagged as full range RGB. There is no software conversion to
+@code{rgbaf16le}, so that one is reachable from a hardware surface only.
@item keep-ratio
Force the scaler to keep the aspect ratio of the input image when the output
size has a different aspect ratio.
diff --git a/libavfilter/vf_sr_amf.c b/libavfilter/vf_sr_amf.c
index cfde4886b1..8ecfcbb1fe 100644
--- a/libavfilter/vf_sr_amf.c
+++ b/libavfilter/vf_sr_amf.c
@@ -102,6 +102,14 @@ static int amf_filter_query_formats(AVFilterContext *avctx)
AV_PIX_FMT_DXVA2_VLD,
AV_PIX_FMT_NONE,
};
+ enum AVPixelFormat pix_fmts_requested[] = {
+ AV_PIX_FMT_NONE,
+ AV_PIX_FMT_AMF_SURFACE,
+ AV_PIX_FMT_D3D11,
+ AV_PIX_FMT_DXVA2_VLD,
+ AV_PIX_FMT_NONE,
+ };
+ int i;
if (amf_hq_scaler_needs_packed_rgb(ctx->algorithm)) {
input_pix_fmts = pix_fmts_packed_rgb;
@@ -111,6 +119,17 @@ static int amf_filter_query_formats(AVFilterContext *avctx)
output_pix_fmts = output_pix_fmts_default;
}
+ if (ctx->format_opt != AV_PIX_FMT_NONE) {
+ for (i = 0; input_pix_fmts[i] != AV_PIX_FMT_NONE; i++) {
+ if (input_pix_fmts[i] == ctx->format_opt) {
+ pix_fmts_requested[0] = ctx->format_opt;
+ input_pix_fmts = pix_fmts_requested;
+ output_pix_fmts = pix_fmts_requested;
+ break;
+ }
+ }
+ }
+
return amf_setup_input_output_formats(avctx, input_pix_fmts,
output_pix_fmts);
}
@@ -128,25 +147,27 @@ static int amf_filter_config_output(AVFilterLink *outlink)
int needs_conversion;
in_sw_format = amf_inlink_sw_format(inlink);
- needs_conversion = amf_hq_scaler_needs_packed_rgb(ctx->algorithm) &&
- (in_sw_format == AV_PIX_FMT_NV12 || in_sw_format ==
AV_PIX_FMT_P010);
-
ctx->format = ctx->format_opt;
- if (needs_conversion) {
- if (ctx->format == AV_PIX_FMT_NONE)
- ctx->format = in_sw_format == AV_PIX_FMT_P010 ? AV_PIX_FMT_X2BGR10
: AV_PIX_FMT_RGBA;
- else if (!amf_is_packed_rgb(ctx->format)) {
+ if (amf_hq_scaler_needs_packed_rgb(ctx->algorithm)) {
+ if (ctx->format == AV_PIX_FMT_NONE) {
+ if (amf_is_packed_rgb(in_sw_format))
+ ctx->format = in_sw_format;
+ else
+ ctx->format = in_sw_format == AV_PIX_FMT_P010 ?
AV_PIX_FMT_X2BGR10 : AV_PIX_FMT_RGBA;
+ } else if (!amf_is_packed_rgb(ctx->format)) {
av_log(avctx, AV_LOG_ERROR, "This algorithm only outputs packed
RGB, format=%s is not supported.\n",
av_get_pix_fmt_name(ctx->format));
return AVERROR(EINVAL);
}
+ needs_conversion = ctx->format != in_sw_format;
} else if (ctx->format != AV_PIX_FMT_NONE && ctx->format != in_sw_format) {
av_log(avctx, AV_LOG_ERROR, "The HQ scaler does not convert formats,
format must be same or %s.\n",
av_get_pix_fmt_name(in_sw_format));
return AVERROR(EINVAL);
} else {
ctx->format = in_sw_format;
+ needs_conversion = 0;
}
err = amf_init_filter_config(outlink, &in_format);
--
2.52.0
>From 989b0ff4b0be0b0bcc88ad7a137cd749d3e8862e Mon Sep 17 00:00:00 2001
From: Julius Bairaktaris <[email protected]>
Date: Mon, 31 Aug 2026 16:08:25 +0200
Subject: [PATCH 15/19] avfilter/amf: advertise only AMF surfaces on the output
link
amf_init_filter_config() always creates an AV_PIX_FMT_AMF_SURFACE frames
context and amf_amfsurface_to_avframe() allocates every output frame from
it, so an AMF filter returns an AMF surface no matter what the output link
negotiated. The output format lists offered software formats and, for a
D3D11VA or DXVA2 device, the foreign hardware format, so a downstream
filter that takes one of them made the link agree on a format the filter
never produces:
ffmpeg -f lavfi -i testsrc2,format=nv12 \
-vf vpp_amf=w=320:h=180:format=nv12,signalstats -f null -
crashes, because signalstats reads an AMFSurface pointer as an NV12 plane.
The frame carries the right format, so it is only the link that lies, but
nothing downstream looks at that.
Offer AV_PIX_FMT_AMF_SURFACE alone, which is what the hardware filters in
the tree do. A software consumer now needs an explicit hwdownload, which
was already the only way to get a correct software frame out of these
filters, and negotiation fails at configuration time instead of handing
out a mislabelled surface. The device type still selects the input format,
where D3D11 and DXVA2 surfaces really are what the filter is handed.
Tested on Windows 11, RX 9070 XT, driver 32.0.31041.1004: the case above
now fails to configure instead of crashing, and vpp_amf, vqe_amf, frc_amf
and sr_amf keep working with software, AMF, D3D11VA and DXVA2 input, into
hwdownload and into h264_amf and hevc_amf.
Assisted-by: Claude Opus 5
Signed-off-by: Julius Bairaktaris <[email protected]>
---
doc/filters.texi | 10 +++++++---
libavfilter/vf_amf_common.c | 9 +++++----
libavfilter/vf_amf_common.h | 2 +-
libavfilter/vf_frc_amf.c | 14 +-------------
libavfilter/vf_sr_amf.c | 29 +++++++----------------------
libavfilter/vf_vpp_amf.c | 13 +------------
libavfilter/vf_vqe_amf.c | 10 +---------
7 files changed, 23 insertions(+), 64 deletions(-)
diff --git a/doc/filters.texi b/doc/filters.texi
index 9feaf151e7..84228502b0 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -23317,9 +23317,11 @@ which does not fill the entire output surface should
be filled with a solid colo
@itemize
@item
-Scale input to 720p, keeping aspect ratio and ensuring the output is yuv420p.
+Upscale to 720p, keeping aspect ratio, and read the result back into system
+memory. The filter always outputs AMF surfaces, so a @ref{hwdownload} is needed
+to feed a software encoder.
@example
-sr_amf=-2:720:format=yuv420p
+sr_amf=-2:720,hwdownload,format=nv12
@end example
@item
@@ -26223,8 +26225,10 @@ JEDEC P22 phosphors
@itemize
@item
Scale input to 720p, keeping aspect ratio and ensuring the output is yuv420p.
+The filter always outputs AMF surfaces, so a @ref{hwdownload} is needed to
feed a
+software encoder.
@example
-vpp_amf=-2:720:format=yuv420p
+vpp_amf=-2:720:format=yuv420p,hwdownload,format=yuv420p
@end example
@item
diff --git a/libavfilter/vf_amf_common.c b/libavfilter/vf_amf_common.c
index 2d70d6d954..b3a18ed9b1 100644
--- a/libavfilter/vf_amf_common.c
+++ b/libavfilter/vf_amf_common.c
@@ -221,12 +221,15 @@ fail:
int amf_setup_input_output_formats(AVFilterContext *avctx,
- const enum AVPixelFormat *input_pix_fmts,
- const enum AVPixelFormat *output_pix_fmts)
+ const enum AVPixelFormat *input_pix_fmts)
{
int err;
AVFilterFormats *input_formats;
AVFilterFormats *output_formats;
+ static const enum AVPixelFormat output_pix_fmts[] = {
+ AV_PIX_FMT_AMF_SURFACE,
+ AV_PIX_FMT_NONE,
+ };
//in case if hw_device_ctx is set to DXVA2 we change order of pixel
formats to set DXVA2 be chosen by default
//The order is ignored if hw_frames_ctx is not NULL on the config_output
stage
@@ -242,7 +245,6 @@ int amf_setup_input_output_formats(AVFilterContext *avctx,
AV_PIX_FMT_NONE,
};
input_pix_fmts = pix_fmts_d3d11;
- output_pix_fmts = pix_fmts_d3d11;
}
break;
#endif
@@ -254,7 +256,6 @@ int amf_setup_input_output_formats(AVFilterContext *avctx,
AV_PIX_FMT_NONE,
};
input_pix_fmts = pix_fmts_dxva2;
- output_pix_fmts = pix_fmts_dxva2;
}
break;
#endif
diff --git a/libavfilter/vf_amf_common.h b/libavfilter/vf_amf_common.h
index eedaef7ba6..4910b23e9b 100644
--- a/libavfilter/vf_amf_common.h
+++ b/libavfilter/vf_amf_common.h
@@ -79,7 +79,7 @@ int amf_copy_surface(AVFilterContext *avctx, const AVFrame
*frame, AMFSurface* s
void amf_free_amfsurface(void *opaque, uint8_t *data);
AVFrame *amf_amfsurface_to_avframe(AVFilterContext *avctx, AMFSurface*
pSurface);
int amf_avframe_to_amfsurface(AVFilterContext *avctx, const AVFrame *frame,
AMFSurface** ppSurface);
-int amf_setup_input_output_formats(AVFilterContext *avctx, const enum
AVPixelFormat *input_pix_fmts, const enum AVPixelFormat *output_pix_fmts);
+int amf_setup_input_output_formats(AVFilterContext *avctx, const enum
AVPixelFormat *input_pix_fmts);
int amf_filter_filter_frame(AVFilterLink *inlink, AVFrame *in);
#endif /* AVFILTER_AMF_COMMON_H */
diff --git a/libavfilter/vf_frc_amf.c b/libavfilter/vf_frc_amf.c
index 1a84a2ee4f..c05ab818e8 100644
--- a/libavfilter/vf_frc_amf.c
+++ b/libavfilter/vf_frc_amf.c
@@ -67,7 +67,6 @@ static int amf_frc_init(AVFilterContext *avctx) {
static int amf_filter_query_formats(AVFilterContext *avctx)
{
- const enum AVPixelFormat *output_pix_fmts;
static const enum AVPixelFormat input_pix_fmts[] = {
AV_PIX_FMT_AMF_SURFACE,
AV_PIX_FMT_D3D11,
@@ -80,19 +79,8 @@ static int amf_filter_query_formats(AVFilterContext *avctx)
AV_PIX_FMT_X2BGR10,
AV_PIX_FMT_NONE,
};
- static const enum AVPixelFormat output_pix_fmts_default[] = {
- AV_PIX_FMT_AMF_SURFACE,
- AV_PIX_FMT_NV12,
- AV_PIX_FMT_P010,
- AV_PIX_FMT_BGRA,
- AV_PIX_FMT_RGBA,
- AV_PIX_FMT_RGBAF16,
- AV_PIX_FMT_X2BGR10,
- AV_PIX_FMT_NONE,
- };
- output_pix_fmts = output_pix_fmts_default;
- return amf_setup_input_output_formats(avctx, input_pix_fmts,
output_pix_fmts);
+ return amf_setup_input_output_formats(avctx, input_pix_fmts);
}
static int amf_frc_filter_config_output(AVFilterLink *outlink)
diff --git a/libavfilter/vf_sr_amf.c b/libavfilter/vf_sr_amf.c
index 8ecfcbb1fe..0d8ba40b31 100644
--- a/libavfilter/vf_sr_amf.c
+++ b/libavfilter/vf_sr_amf.c
@@ -68,7 +68,7 @@ static int amf_is_packed_rgb(enum AVPixelFormat format)
static int amf_filter_query_formats(AVFilterContext *avctx)
{
AMFFilterContext *ctx = avctx->priv;
- const enum AVPixelFormat *input_pix_fmts, *output_pix_fmts;
+ const enum AVPixelFormat *input_pix_fmts;
static const enum AVPixelFormat input_pix_fmts_default[] = {
AV_PIX_FMT_NV12,
AV_PIX_FMT_P010,
@@ -80,17 +80,6 @@ static int amf_filter_query_formats(AVFilterContext *avctx)
AV_PIX_FMT_RGBAF16,
AV_PIX_FMT_NONE,
};
- static const enum AVPixelFormat output_pix_fmts_default[] = {
- AV_PIX_FMT_NV12,
- AV_PIX_FMT_P010,
- AV_PIX_FMT_BGRA,
- AV_PIX_FMT_RGBA,
- AV_PIX_FMT_AMF_SURFACE,
- AV_PIX_FMT_D3D11,
- AV_PIX_FMT_DXVA2_VLD,
- AV_PIX_FMT_RGBAF16,
- AV_PIX_FMT_NONE,
- };
// sr1-1 and point need packed RGB; YUV is converted on the GPU
static const enum AVPixelFormat pix_fmts_packed_rgb[] = {
AV_PIX_FMT_RGBA,
@@ -111,26 +100,22 @@ static int amf_filter_query_formats(AVFilterContext
*avctx)
};
int i;
- if (amf_hq_scaler_needs_packed_rgb(ctx->algorithm)) {
- input_pix_fmts = pix_fmts_packed_rgb;
- output_pix_fmts = pix_fmts_packed_rgb;
- } else {
- input_pix_fmts = input_pix_fmts_default;
- output_pix_fmts = output_pix_fmts_default;
- }
+ if (amf_hq_scaler_needs_packed_rgb(ctx->algorithm))
+ input_pix_fmts = pix_fmts_packed_rgb;
+ else
+ input_pix_fmts = input_pix_fmts_default;
if (ctx->format_opt != AV_PIX_FMT_NONE) {
for (i = 0; input_pix_fmts[i] != AV_PIX_FMT_NONE; i++) {
if (input_pix_fmts[i] == ctx->format_opt) {
pix_fmts_requested[0] = ctx->format_opt;
- input_pix_fmts = pix_fmts_requested;
- output_pix_fmts = pix_fmts_requested;
+ input_pix_fmts = pix_fmts_requested;
break;
}
}
}
- return amf_setup_input_output_formats(avctx, input_pix_fmts,
output_pix_fmts);
+ return amf_setup_input_output_formats(avctx, input_pix_fmts);
}
static int amf_filter_config_output(AVFilterLink *outlink)
diff --git a/libavfilter/vf_vpp_amf.c b/libavfilter/vf_vpp_amf.c
index 1cd7e81128..f992795cf6 100644
--- a/libavfilter/vf_vpp_amf.c
+++ b/libavfilter/vf_vpp_amf.c
@@ -46,7 +46,6 @@
static int amf_filter_query_formats(AVFilterContext *avctx)
{
- const enum AVPixelFormat *output_pix_fmts;
static const enum AVPixelFormat input_pix_fmts[] = {
AV_PIX_FMT_AMF_SURFACE,
AV_PIX_FMT_D3D11,
@@ -61,18 +60,8 @@ static int amf_filter_query_formats(AVFilterContext *avctx)
AV_PIX_FMT_YUYV422,
AV_PIX_FMT_NONE,
};
- static const enum AVPixelFormat output_pix_fmts_default[] = {
- AV_PIX_FMT_AMF_SURFACE,
- AV_PIX_FMT_D3D11,
- AV_PIX_FMT_DXVA2_VLD,
- AV_PIX_FMT_NV12,
- AV_PIX_FMT_BGRA,
- AV_PIX_FMT_YUV420P,
- AV_PIX_FMT_NONE,
- };
- output_pix_fmts = output_pix_fmts_default;
- return amf_setup_input_output_formats(avctx, input_pix_fmts,
output_pix_fmts);
+ return amf_setup_input_output_formats(avctx, input_pix_fmts);
}
static int amf_filter_config_output(AVFilterLink *outlink)
diff --git a/libavfilter/vf_vqe_amf.c b/libavfilter/vf_vqe_amf.c
index 63febf5c15..e36492645d 100644
--- a/libavfilter/vf_vqe_amf.c
+++ b/libavfilter/vf_vqe_amf.c
@@ -62,7 +62,6 @@ static int amf_vqe_init(AVFilterContext *avctx) {
static int amf_filter_query_formats(AVFilterContext *avctx)
{
- const enum AVPixelFormat *output_pix_fmts;
static const enum AVPixelFormat input_pix_fmts[] = {
AV_PIX_FMT_AMF_SURFACE,
AV_PIX_FMT_D3D11,
@@ -71,15 +70,8 @@ static int amf_filter_query_formats(AVFilterContext *avctx)
AV_PIX_FMT_P010,
AV_PIX_FMT_NONE,
};
- static const enum AVPixelFormat output_pix_fmts_default[] = {
- AV_PIX_FMT_AMF_SURFACE,
- AV_PIX_FMT_NV12,
- AV_PIX_FMT_P010,
- AV_PIX_FMT_NONE,
- };
- output_pix_fmts = output_pix_fmts_default;
- return amf_setup_input_output_formats(avctx, input_pix_fmts,
output_pix_fmts);
+ return amf_setup_input_output_formats(avctx, input_pix_fmts);
}
static int amf_vqe_filter_config_output(AVFilterLink *outlink)
--
2.52.0
>From a4955d00489ee10c3ad9f43cefa98341b14a0b49 Mon Sep 17 00:00:00 2001
From: Julius Bairaktaris <[email protected]>
Date: Sun, 30 Aug 2026 16:53:59 +0200
Subject: [PATCH 16/19] avfilter/amf: flush the component at EOF with an
activate callback
The AMF filters ran off a filter_frame callback, which has no way to tell
a component that no more input is coming. Anything a component still held
when the input link ended was lost, and there was nowhere to call
AMFComponent::Drain(). The scaler, the enhancer and the converter return
one output per input today, so nothing is dropped in practice, but the
call sequence was only correct as long as that stayed true.
Convert the three filters that share amf_filter_filter_frame() to an
activate callback: consume one input frame at a time, acknowledge the
input status, and on EOF drain the pre-converter and the component before
forwarding the status downstream. The output side of filter_frame moves
into amf_deliver_output(), which activate reuses for the frames the drain
produces, with the timestamp taken from the surface since there is no
input frame to copy properties from.
vf_frc_amf.c gets the same shape. It keeps its own submission, since it
sets the FRC mode per frame and halves the output duration, but its
output side moves to amf_frc_deliver_output() and it drains at EOF too.
Tested on Windows 11, RX 9070 XT, driver 32.0.31041.1004: sr_amf, vpp_amf
and vqe_amf return 60 frames for a 60 frame clip, bit identical to master
by framecrc, over AMF and D3D11VA surfaces, and a truncated read with
-frames:v still terminates. frc_amf returns the same 2N-1 frames with the
same timestamps and durations as before, which is every frame the x2 mode
can produce from N inputs; its pixel output is not bit comparable because
the component is not deterministic between runs on the same build.
Assisted-by: Claude Opus 5
Signed-off-by: Julius Bairaktaris <[email protected]>
---
libavfilter/vf_amf_common.c | 159 ++++++++++++++++++++++++------------
libavfilter/vf_amf_common.h | 5 ++
libavfilter/vf_frc_amf.c | 133 +++++++++++++++++++++---------
libavfilter/vf_sr_amf.c | 2 +-
libavfilter/vf_vpp_amf.c | 2 +-
libavfilter/vf_vqe_amf.c | 2 +-
6 files changed, 209 insertions(+), 94 deletions(-)
diff --git a/libavfilter/vf_amf_common.c b/libavfilter/vf_amf_common.c
index b3a18ed9b1..c1b47040b7 100644
--- a/libavfilter/vf_amf_common.c
+++ b/libavfilter/vf_amf_common.c
@@ -21,6 +21,7 @@
#include "libavutil/avassert.h"
#include "avfilter.h"
#include "avfilter_internal.h"
+#include "filters.h"
#include "formats.h"
#include "libavutil/mem.h"
#include "libavutil/imgutils.h"
@@ -88,64 +89,24 @@ void amf_filter_uninit(AVFilterContext *avctx)
av_buffer_unref(&ctx->hwframes_out_ref);
}
-int amf_filter_filter_frame(AVFilterLink *inlink, AVFrame *in)
+static int amf_deliver_output(AVFilterContext *avctx, const AVFrame *in, int
*got_frame)
{
- AVFilterContext *avctx = inlink->dst;
AMFFilterContext *ctx = avctx->priv;
- AVFilterLink *outlink = avctx->outputs[0];
- AMF_RESULT res;
- AMFSurface *surface_in;
+ AVFilterLink *outlink = avctx->outputs[0];
AMFSurface *surface_out;
AMFData *data_out = NULL;
enum AVColorSpace out_colorspace;
enum AVColorRange out_color_range;
-
AVFrame *out = NULL;
- int got_frame = 0;
+ AMF_RESULT res;
int ret = 0;
- if (!ctx->component)
- return AVERROR(EINVAL);
-
- ret = amf_avframe_to_amfsurface(avctx, in, &surface_in);
- if (ret < 0)
- goto fail;
-
- if (ctx->pre_converter) {
- AMFGuid guid = IID_AMFSurface();
- AMFData *data_conv = NULL;
- AMFSurface *surface_conv = NULL;
-
- do {
- res = ctx->pre_converter->pVtbl->SubmitInput(ctx->pre_converter,
(AMFData*)surface_in);
- if (res == AMF_INPUT_FULL)
- av_usleep(100);
- } while (res == AMF_INPUT_FULL);
- surface_in->pVtbl->Release(surface_in);
- AMF_GOTO_FAIL_IF_FALSE(avctx, res == AMF_OK, AVERROR_UNKNOWN,
"Converter SubmitInput() failed with error %d\n", res);
-
- do {
- res = ctx->pre_converter->pVtbl->QueryOutput(ctx->pre_converter,
&data_conv);
- if (res == AMF_REPEAT && !data_conv)
- av_usleep(100);
- } while (res == AMF_REPEAT && !data_conv);
- AMF_GOTO_FAIL_IF_FALSE(avctx, res == AMF_OK && data_conv,
AVERROR_UNKNOWN, "Converter QueryOutput() failed with error %d\n", res);
-
- res = data_conv->pVtbl->QueryInterface(data_conv, &guid,
(void**)&surface_conv);
- data_conv->pVtbl->Release(data_conv);
- AMF_GOTO_FAIL_IF_FALSE(avctx, res == AMF_OK, AVERROR_UNKNOWN,
"Converter QueryInterface(IID_AMFSurface) failed with error %d\n", res);
- surface_in = surface_conv;
- }
-
- res = ctx->component->pVtbl->SubmitInput(ctx->component,
(AMFData*)surface_in);
- surface_in->pVtbl->Release(surface_in); // release surface after use
- AMF_GOTO_FAIL_IF_FALSE(avctx, res == AMF_OK, AVERROR_UNKNOWN,
"SubmitInput() failed with error %d\n", res);
-
while (1) {
AMFGuid guid = IID_AMFSurface();
res = ctx->component->pVtbl->QueryOutput(ctx->component, &data_out);
- AMF_GOTO_FAIL_IF_FALSE(avctx, res == AMF_OK || res == AMF_REPEAT,
AVERROR_UNKNOWN, "QueryOutput() failed with error %d\n", res);
+ AMF_GOTO_FAIL_IF_FALSE(avctx, res == AMF_OK || res == AMF_REPEAT ||
res == AMF_EOF,
+ AVERROR_UNKNOWN, "QueryOutput() failed with
error %d\n", res);
if (!data_out)
break;
@@ -157,9 +118,11 @@ int amf_filter_filter_frame(AVFilterLink *inlink, AVFrame
*in)
out = amf_amfsurface_to_avframe(avctx, surface_out);
AMF_GOTO_FAIL_IF_FALSE(avctx, out != NULL, AVERROR(ENOMEM), "Failed to
convert AMFSurface to AVFrame\n");
- ret = av_frame_copy_props(out, in);
- if (ret < 0)
- goto fail;
+ if (in) {
+ ret = av_frame_copy_props(out, in);
+ if (ret < 0)
+ goto fail;
+ }
out->pts = surface_out->pVtbl->GetPts(surface_out);
out_colorspace = AVCOL_SPC_UNSPECIFIED;
@@ -207,17 +170,109 @@ int amf_filter_filter_frame(AVFilterLink *inlink,
AVFrame *in)
out = NULL;
if (ret < 0)
goto fail;
- got_frame = 1;
+ *got_frame = 1;
}
- av_frame_free(&in);
- return got_frame ? ret : 0;
+ return 0;
fail:
- av_frame_free(&in);
av_frame_free(&out);
return ret;
}
+int amf_filter_filter_frame(AVFilterLink *inlink, AVFrame *in)
+{
+ AVFilterContext *avctx = inlink->dst;
+ AMFFilterContext *ctx = avctx->priv;
+ AMF_RESULT res;
+ AMFSurface *surface_in;
+ int got_frame = 0;
+ int ret = 0;
+
+ if (!ctx->component)
+ return AVERROR(EINVAL);
+
+ ret = amf_avframe_to_amfsurface(avctx, in, &surface_in);
+ if (ret < 0)
+ goto fail;
+
+ if (ctx->pre_converter) {
+ AMFGuid guid = IID_AMFSurface();
+ AMFData *data_conv = NULL;
+ AMFSurface *surface_conv = NULL;
+
+ do {
+ res = ctx->pre_converter->pVtbl->SubmitInput(ctx->pre_converter,
(AMFData*)surface_in);
+ if (res == AMF_INPUT_FULL)
+ av_usleep(100);
+ } while (res == AMF_INPUT_FULL);
+ surface_in->pVtbl->Release(surface_in);
+ AMF_GOTO_FAIL_IF_FALSE(avctx, res == AMF_OK, AVERROR_UNKNOWN,
"Converter SubmitInput() failed with error %d\n", res);
+
+ do {
+ res = ctx->pre_converter->pVtbl->QueryOutput(ctx->pre_converter,
&data_conv);
+ if (res == AMF_REPEAT && !data_conv)
+ av_usleep(100);
+ } while (res == AMF_REPEAT && !data_conv);
+ AMF_GOTO_FAIL_IF_FALSE(avctx, res == AMF_OK && data_conv,
AVERROR_UNKNOWN, "Converter QueryOutput() failed with error %d\n", res);
+
+ res = data_conv->pVtbl->QueryInterface(data_conv, &guid,
(void**)&surface_conv);
+ data_conv->pVtbl->Release(data_conv);
+ AMF_GOTO_FAIL_IF_FALSE(avctx, res == AMF_OK, AVERROR_UNKNOWN,
"Converter QueryInterface(IID_AMFSurface) failed with error %d\n", res);
+ surface_in = surface_conv;
+ }
+
+ res = ctx->component->pVtbl->SubmitInput(ctx->component,
(AMFData*)surface_in);
+ surface_in->pVtbl->Release(surface_in); // release surface after use
+ AMF_GOTO_FAIL_IF_FALSE(avctx, res == AMF_OK, AVERROR_UNKNOWN,
"SubmitInput() failed with error %d\n", res);
+
+ ret = amf_deliver_output(avctx, in, &got_frame);
+fail:
+ av_frame_free(&in);
+ return ret;
+}
+
+int amf_filter_activate(AVFilterContext *avctx)
+{
+ AMFFilterContext *ctx = avctx->priv;
+ AVFilterLink *inlink = avctx->inputs[0];
+ AVFilterLink *outlink = avctx->outputs[0];
+ AVFrame *in = NULL;
+ int got_frame = 0;
+ int ret;
+
+ FF_FILTER_FORWARD_STATUS_BACK(outlink, inlink);
+
+ if (!ctx->eof) {
+ ret = ff_inlink_consume_frame(inlink, &in);
+ if (ret < 0)
+ return ret;
+ if (in)
+ return amf_filter_filter_frame(inlink, in);
+
+ if (ff_inlink_acknowledge_status(inlink, &ctx->status,
&ctx->status_pts))
+ ctx->eof = 1;
+ }
+
+ if (ctx->eof) {
+ // let the component hand back anything it is still holding
+ if (ctx->component && !ctx->drained) {
+ ctx->drained = 1;
+ if (ctx->pre_converter)
+ ctx->pre_converter->pVtbl->Drain(ctx->pre_converter);
+ ctx->component->pVtbl->Drain(ctx->component);
+ ret = amf_deliver_output(avctx, NULL, &got_frame);
+ if (ret < 0)
+ return ret;
+ }
+ ff_outlink_set_status(outlink, ctx->status, ctx->status_pts);
+ return 0;
+ }
+
+ FF_FILTER_FORWARD_WANTED(outlink, inlink);
+
+ return FFERROR_NOT_READY;
+}
+
int amf_setup_input_output_formats(AVFilterContext *avctx,
diff --git a/libavfilter/vf_amf_common.h b/libavfilter/vf_amf_common.h
index 4910b23e9b..362af88fbc 100644
--- a/libavfilter/vf_amf_common.h
+++ b/libavfilter/vf_amf_common.h
@@ -32,6 +32,10 @@ typedef struct AMFFilterContext {
int width, height;
enum AVPixelFormat format;
enum AVPixelFormat format_opt;
+ int eof;
+ int drained;
+ int status;
+ int64_t status_pts;
int scale_type;
int in_color_range;
int in_primaries;
@@ -81,5 +85,6 @@ AVFrame *amf_amfsurface_to_avframe(AVFilterContext *avctx,
AMFSurface* pSurface)
int amf_avframe_to_amfsurface(AVFilterContext *avctx, const AVFrame *frame,
AMFSurface** ppSurface);
int amf_setup_input_output_formats(AVFilterContext *avctx, const enum
AVPixelFormat *input_pix_fmts);
int amf_filter_filter_frame(AVFilterLink *inlink, AVFrame *in);
+int amf_filter_activate(AVFilterContext *avctx);
#endif /* AVFILTER_AMF_COMMON_H */
diff --git a/libavfilter/vf_frc_amf.c b/libavfilter/vf_frc_amf.c
index c05ab818e8..53f59b890f 100644
--- a/libavfilter/vf_frc_amf.c
+++ b/libavfilter/vf_frc_amf.c
@@ -171,20 +171,67 @@ static const AVOption frc_amf_options[] = {
AVFILTER_DEFINE_CLASS(frc_amf);
+static int amf_frc_deliver_output(AVFilterContext *avctx, const AVFrame *in)
+{
+ AMFFRCFilterContext *frc_ctx = avctx->priv;
+ AMFComponent *amf_filter = frc_ctx->common.component;
+ AVFilterLink *outlink = avctx->outputs[0];
+ AMFSurface *surface_out = NULL;
+ AMFData *data_out = NULL;
+ AVFrame *out = NULL;
+ AMF_RESULT res = AMF_FAIL;
+ int ret = 0;
+
+ while (true) {
+ AMFGuid guid = IID_AMFSurface();
+
+ res = AMF_IFACE_CALL(amf_filter, QueryOutput, &data_out);
+
+ AMF_GOTO_FAIL_IF_FALSE(avctx, (res == AMF_OK || res == AMF_REPEAT ||
res == AMF_EOF),
+ AVERROR_UNKNOWN, "QueryOutput() failed with
error %d\n", res);
+ if (data_out == NULL)
+ break;
+
+ res = AMF_IFACE_CALL(data_out, QueryInterface, &guid,
(void**)&surface_out);
+ AMF_IFACE_CALL(data_out, Release);
+ data_out = NULL;
+ AMF_GOTO_FAIL_IF_FALSE(avctx, res == AMF_OK, AVERROR_UNKNOWN,
"QueryInterface(IID_AMFSurface) failed with error %d\n", res);
+
+ out = amf_amfsurface_to_avframe(avctx, surface_out);
+ AMF_GOTO_FAIL_IF_FALSE(avctx, out != NULL, AVERROR(ENOMEM), "Failed to
convert AMFSurface to AVFrame\n");
+
+ if (in) {
+ ret = av_frame_copy_props(out, in);
+ AMF_GOTO_FAIL_IF_FALSE(avctx, ret >= 0, AVERROR(ENOMEM), "Failed
to copy frame properties\n");
+ }
+
+ out->pts = AMF_IFACE_CALL(surface_out, GetPts);
+
+ if (frc_ctx->enable)
+ out->duration /= 2;
+
+ ret = ff_filter_frame(outlink, out);
+ out = NULL;
+ if (ret < 0)
+ goto fail;
+ }
+
+ return 0;
+fail:
+ av_frame_free(&out);
+ return ret;
+}
+
static int amf_frc_filter_avframe(AVFilterLink *inlink, AVFrame *in)
{
AVFilterContext *avctx = inlink->dst;
AMFFRCFilterContext *frc_ctx = avctx->priv;
- AMFFilterContext *amf_ctx = &frc_ctx->common;
- AMFComponent *amf_filter = amf_ctx->component;
+ AMFComponent *amf_filter = frc_ctx->common.component;
AVFilterLink *outlink = avctx->outputs[0];
- AMFSurface *surface_out = NULL;
AMFSurface *surface_in = NULL;
FilterLink *il = ff_filter_link(inlink);
FilterLink *ol = ff_filter_link(outlink);
AMF_RESULT res = AMF_FAIL;
- AMFData *data_out = NULL;
- AVFrame *out = NULL;
int ret = 0;
if (!amf_filter)
@@ -208,50 +255,57 @@ static int amf_frc_filter_avframe(AVFilterLink *inlink,
AVFrame *in)
surface_in = NULL;
AMF_GOTO_FAIL_IF_FALSE(avctx, (res == AMF_OK || res == AMF_INPUT_FULL),
AVERROR_UNKNOWN, "SubmitInput() failed with error %d\n", res);
- while (true) {
- res = AMF_IFACE_CALL(amf_filter, QueryOutput, &data_out);
-
- AMF_GOTO_FAIL_IF_FALSE(avctx, (res == AMF_OK || res == AMF_REPEAT),
AVERROR_UNKNOWN, "QueryOutput() failed with error %d\n", res);
- if (data_out == NULL)
- break;
-
- AMFGuid guid = IID_AMFSurface();
- res = AMF_IFACE_CALL(data_out, QueryInterface, &guid,
(void**)&surface_out);
- AMF_IFACE_CALL(data_out, Release);
- data_out = NULL;
- AMF_GOTO_FAIL_IF_FALSE(avctx, res == AMF_OK, AVERROR_UNKNOWN,
"QueryInterface(IID_AMFSurface) failed with error %d\n", res);
-
- out = amf_amfsurface_to_avframe(avctx, surface_out);
- AMF_GOTO_FAIL_IF_FALSE(avctx, out != NULL, AVERROR(ENOMEM), "Failed to
convert AMFSurface to AVFrame\n");
-
- ret = av_frame_copy_props(out, in);
- AMF_GOTO_FAIL_IF_FALSE(avctx, ret >= 0, AVERROR(ENOMEM), "Failed to
copy frame properties\n");
-
- out->pts = AMF_IFACE_CALL(surface_out, GetPts);
-
- if (frc_ctx->enable)
- out->duration /= 2;
-
- ret = ff_filter_frame(outlink, out);
- out = NULL;
- if (ret < 0)
- goto fail;
- }
-
+ ret = amf_frc_deliver_output(avctx, in);
fail:
- av_frame_unref(in);
av_frame_free(&in);
- if (out != NULL)
- av_frame_free(&out);
return ret;
}
+static int amf_frc_activate(AVFilterContext *avctx)
+{
+ AMFFRCFilterContext *frc_ctx = avctx->priv;
+ AMFFilterContext *ctx = &frc_ctx->common;
+ AVFilterLink *inlink = avctx->inputs[0];
+ AVFilterLink *outlink = avctx->outputs[0];
+ AVFrame *in = NULL;
+ int ret;
+
+ FF_FILTER_FORWARD_STATUS_BACK(outlink, inlink);
+
+ if (!ctx->eof) {
+ ret = ff_inlink_consume_frame(inlink, &in);
+ if (ret < 0)
+ return ret;
+ if (in)
+ return amf_frc_filter_avframe(inlink, in);
+
+ if (ff_inlink_acknowledge_status(inlink, &ctx->status,
&ctx->status_pts))
+ ctx->eof = 1;
+ }
+
+ if (ctx->eof) {
+ // the converter holds a frame back when it interpolates from the
future one
+ if (ctx->component && !ctx->drained) {
+ ctx->drained = 1;
+ AMF_IFACE_CALL(ctx->component, Drain);
+ ret = amf_frc_deliver_output(avctx, NULL);
+ if (ret < 0)
+ return ret;
+ }
+ ff_outlink_set_status(outlink, ctx->status, ctx->status_pts);
+ return 0;
+ }
+
+ FF_FILTER_FORWARD_WANTED(outlink, inlink);
+
+ return FFERROR_NOT_READY;
+}
+
static const AVFilterPad amf_filter_inputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_VIDEO,
- .filter_frame = amf_frc_filter_avframe,
}
};
@@ -272,6 +326,7 @@ FFFilter ff_vf_frc_amf = {
.priv_size = sizeof(AMFFRCFilterContext),
.init = amf_frc_init,
.uninit = amf_filter_uninit,
+ .activate = amf_frc_activate,
FILTER_INPUTS(amf_filter_inputs),
FILTER_OUTPUTS(amf_filter_outputs),
FILTER_QUERY_FUNC(amf_filter_query_formats),
diff --git a/libavfilter/vf_sr_amf.c b/libavfilter/vf_sr_amf.c
index 0d8ba40b31..d589fa9970 100644
--- a/libavfilter/vf_sr_amf.c
+++ b/libavfilter/vf_sr_amf.c
@@ -250,7 +250,6 @@ static const AVFilterPad amf_filter_inputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_VIDEO,
- .filter_frame = amf_filter_filter_frame,
}
};
@@ -271,6 +270,7 @@ FFFilter ff_vf_sr_amf = {
.init = amf_filter_init,
.uninit = amf_filter_uninit,
+ .activate = amf_filter_activate,
FILTER_INPUTS(amf_filter_inputs),
FILTER_OUTPUTS(amf_filter_outputs),
FILTER_QUERY_FUNC(&amf_filter_query_formats),
diff --git a/libavfilter/vf_vpp_amf.c b/libavfilter/vf_vpp_amf.c
index f992795cf6..8368f2b168 100644
--- a/libavfilter/vf_vpp_amf.c
+++ b/libavfilter/vf_vpp_amf.c
@@ -269,7 +269,6 @@ static const AVFilterPad amf_filter_inputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_VIDEO,
- .filter_frame = amf_filter_filter_frame,
}
};
@@ -289,6 +288,7 @@ FFFilter ff_vf_vpp_amf = {
.priv_size = sizeof(AMFFilterContext),
.init = amf_filter_init,
.uninit = amf_filter_uninit,
+ .activate = amf_filter_activate,
FILTER_INPUTS(amf_filter_inputs),
FILTER_OUTPUTS(amf_filter_outputs),
FILTER_QUERY_FUNC(amf_filter_query_formats),
diff --git a/libavfilter/vf_vqe_amf.c b/libavfilter/vf_vqe_amf.c
index e36492645d..d54661fafb 100644
--- a/libavfilter/vf_vqe_amf.c
+++ b/libavfilter/vf_vqe_amf.c
@@ -141,7 +141,6 @@ static const AVFilterPad amf_filter_inputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_VIDEO,
- .filter_frame = amf_filter_filter_frame,
}
};
@@ -161,6 +160,7 @@ FFFilter ff_vf_vqe_amf = {
.priv_size = sizeof(AMFVQEFilterContext),
.init = amf_vqe_init,
.uninit = amf_filter_uninit,
+ .activate = amf_filter_activate,
FILTER_INPUTS(amf_filter_inputs),
FILTER_OUTPUTS(amf_filter_outputs),
FILTER_QUERY_FUNC(&amf_filter_query_formats),
--
2.52.0
>From 602adfc545f4fb67e2b861bf9a1d5ae51a5a28b0 Mon Sep 17 00:00:00 2001
From: Julius Bairaktaris <[email protected]>
Date: Fri, 4 Sep 2026 11:35:19 +0200
Subject: [PATCH 17/19] avutil/hwcontext_amf: report only sw_format as
transferable
amf_transfer_data_to() and amf_transfer_data_from() reject any frame
whose format differs from the frames context's sw_format, but
amf_transfer_get_formats() returned a static list of seven formats
instead. The list had drifted from supported_formats[]: BGR0 can be a
sw_format but was not in it, so a BGR0 surface could never be
downloaded. Conversely hwdownload accepted any of the seven when the
graph was configured and then failed on every frame with EINVAL once
the format did not match the surface, so "hwdownload,format=rgba" on a
BGR0 surface printed "Failed to download frame: -22" instead of an
error naming the format. The first entry of that list is also what
av_hwframe_transfer_data() picks when the caller leaves the destination
format unset, which asked for NV12 whatever the surface held.
Return the frames context's sw_format alone, as the other hardware
contexts do, and drop the list. hwdownload now rejects a mismatched
output format at configuration time with "Invalid output format %s for
hwframe download", and every sw_format a frames context can have is
downloadable.
Tested on Windows 11, RX 9070 XT, driver 32.0.31041.1004: hwupload,
hwdownload round trips for nv12, yuv420p, bgra, rgba, bgr0, p010le and
x2bgr10le are bit-exact with the source by framecrc; bgr0 failed
before.
Assisted-by: Claude Fable 5.1
Signed-off-by: Julius Bairaktaris <[email protected]>
---
libavutil/hwcontext_amf.c | 19 ++++---------------
1 file changed, 4 insertions(+), 15 deletions(-)
diff --git a/libavutil/hwcontext_amf.c b/libavutil/hwcontext_amf.c
index d67193a3c5..81840160d8 100644
--- a/libavutil/hwcontext_amf.c
+++ b/libavutil/hwcontext_amf.c
@@ -330,17 +330,6 @@ static const enum AVPixelFormat supported_formats[] = {
#endif
};
-static const enum AVPixelFormat supported_transfer_formats[] = {
- AV_PIX_FMT_NV12,
- AV_PIX_FMT_YUV420P,
- AV_PIX_FMT_BGRA,
- AV_PIX_FMT_RGBA,
- AV_PIX_FMT_P010,
- AV_PIX_FMT_X2BGR10,
- AV_PIX_FMT_RGBAF16,
- AV_PIX_FMT_NONE,
-};
-
static int amf_frames_get_constraints(AVHWDeviceContext *ctx,
const void *hwconfig,
AVHWFramesConstraints *constraints)
@@ -424,13 +413,13 @@ static int amf_transfer_get_formats(AVHWFramesContext
*ctx,
enum AVPixelFormat **formats)
{
enum AVPixelFormat *fmts;
- int i;
- fmts = av_malloc_array(FF_ARRAY_ELEMS(supported_transfer_formats),
sizeof(*fmts));
+ fmts = av_malloc_array(2, sizeof(*fmts));
if (!fmts)
return AVERROR(ENOMEM);
- for (i = 0; i < FF_ARRAY_ELEMS(supported_transfer_formats); i++)
- fmts[i] = supported_transfer_formats[i];
+
+ fmts[0] = ctx->sw_format;
+ fmts[1] = AV_PIX_FMT_NONE;
*formats = fmts;
--
2.52.0
>From d7224ceccc37e5e5dbf9501fa329f6a69177e10b Mon Sep 17 00:00:00 2001
From: Julius Bairaktaris <[email protected]>
Date: Fri, 4 Sep 2026 11:35:19 +0200
Subject: [PATCH 18/19] avutil/hwcontext_amf: allow YUYV422 frames contexts
AMF_SURFACE_YUY2 is in format_map[] and the video converter accepts it
as input and output, but YUYV422 was missing from supported_formats[],
so it could not be the sw_format of an AMF frames context: hwupload
refused it, and vpp_amf on yuyv422 software input without an explicit
output format failed in amf_frames_init() with "Pixel format 'yuyv422'
is not supported". It is a single packed plane, so the transfer paths
need no change.
Tested on Windows 11, RX 9070 XT, driver 32.0.31041.1004: a yuyv422
hwupload,hwdownload round trip is bit-exact with the source by
framecrc, and "format=yuyv422,vpp_amf,hwdownload,format=yuyv422"
configures and runs where it failed before.
Assisted-by: Claude Fable 5.1
Signed-off-by: Julius Bairaktaris <[email protected]>
---
libavutil/hwcontext_amf.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/libavutil/hwcontext_amf.c b/libavutil/hwcontext_amf.c
index 81840160d8..4c0821d12a 100644
--- a/libavutil/hwcontext_amf.c
+++ b/libavutil/hwcontext_amf.c
@@ -313,6 +313,7 @@ int av_amf_attach_hdr_metadata(AVFrame *frame, const
AMFHDRMetadata *hdrmeta) {
static const enum AVPixelFormat supported_formats[] = {
AV_PIX_FMT_NV12,
AV_PIX_FMT_YUV420P,
+ AV_PIX_FMT_YUYV422,
AV_PIX_FMT_BGRA,
AV_PIX_FMT_RGBA,
AV_PIX_FMT_BGR0,
--
2.52.0
>From dcd5ffc552d17ffc183690abade576b9b45034a6 Mon Sep 17 00:00:00 2001
From: Julius Bairaktaris <[email protected]>
Date: Fri, 4 Sep 2026 11:35:19 +0200
Subject: [PATCH 19/19] avfilter/amf: build the input format list from the
component's capabilities
Every AMF filter carried a hand-written input format list, and
vf_sr_amf.c a second one for the algorithms that need packed RGB. The
lists were copied from each component's programming guide and then
corrected by trial: vpp_amf listed formats the converter rejects,
vqe_amf listed four that AMFVQEnhancer::Init() rejects, and none of
them can follow a driver that gains or loses a format.
Ask the component instead. amf_setup_input_output_formats() takes the
component id, creates a throwaway instance on the filter's device, or
on a standalone AMF device when there is none yet, and enumerates
AMFCaps::GetInputCaps(). Each reported surface format is offered under
every pixel format that maps to it, restricted to the sw_formats an AMF
frames context can hold, since a filter that cannot convert emits the
format it receives. A filter can pass a predicate to narrow the list:
sr_amf keeps packed RGB only for point and sr1-1, and pins the list to
an explicit format= as before. The D3D11VA and DXVA2 device overrides
are unchanged, and the output side stays AV_PIX_FMT_AMF_SURFACE.
The explicit NV12/P010 check in vqe_amf goes with its list: the driver
reports what the enhancer takes and Init() rejects the rest.
This depends on the driver reporting caps that match Init(). On driver
32.0.31041.1004 AMFVQEnhancer advertises five packed RGB formats that
Init() then rejects, reported as GPUOpen-LibrariesAndSDKs/AMF#610, so
packed RGB software input to vqe_amf now fails at configuration where
the hand-written list had it converted to NV12. AMD has fixed the caps
for a future AMF release.
The wider lists change what libavfilter picks for software input that
needs converting: 8-bit input to point, sr1-1 and frc_amf now arrives
as bgr0 rather than rgba, so a downstream hwdownload has to name bgr0;
the scaled pixels are identical. vpp_amf gains yuyv422 and x2bgr10le
as pass-through formats and loses rgb0, which is not a valid frames
context sw_format and which libavfilter picked for rgb24 input, so
"format=rgb24,vpp_amf,hwdownload,format=bgr0" configures where it
failed in amf_frames_init() before.
Tested on Windows 11, RX 9070 XT, driver 32.0.31041.1004, against the
parent branch: vpp_amf, vqe_amf, frc_amf and every sr_amf algorithm
with software nv12, p010, rgba, bgra, yuv420p, yuyv422 and x2bgr10le
input, with AMF, D3D11VA and DXVA2 surfaces, with -hwaccel amf
decoding, and with no device given at all. Every case that worked
before still works except the vqe_amf packed RGB one above, and
vpp_amf, vqe_amf and sr_amf give byte-identical framecrc output for
every negotiation that did not change. sr1-1 and point on software
nv12 produce the same rgb24 pixels through bgr0 as they did through
rgba, and signalstats confirms a written frame. Configuring a graph
takes the same 204 ms as before. The touched files also build
warning-free on Linux with --enable-amf, where CONFIG_D3D11VA is 0.
Assisted-by: Claude Fable 5.1
Signed-off-by: Julius Bairaktaris <[email protected]>
---
doc/filters.texi | 17 +++---
libavfilter/vf_amf_common.c | 100 +++++++++++++++++++++++++++++++++---
libavfilter/vf_amf_common.h | 3 +-
libavfilter/vf_frc_amf.c | 15 +-----
libavfilter/vf_sr_amf.c | 67 ++++++------------------
libavfilter/vf_vpp_amf.c | 17 +-----
libavfilter/vf_vqe_amf.c | 17 +-----
7 files changed, 125 insertions(+), 111 deletions(-)
diff --git a/doc/filters.texi b/doc/filters.texi
index 84228502b0..204fc11880 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -23295,13 +23295,16 @@ Controls the output pixel format. By default, or if
none is specified, the input
pixel format is used.
The @code{point} and @code{sr1-1} algorithms emit the packed RGB format the
-scaler receives, so for them this option must name one of @code{rgba},
-@code{bgra}, @code{x2bgr10le} or @code{rgbaf16le}. A named format also decides
-what software input is converted to, and what a hardware surface in another
-format is converted to on the GPU. Without it, @code{x2bgr10le} is selected for
-@code{p010} input and @code{rgba} for other non-RGB input, and the result is
-tagged as full range RGB. There is no software conversion to
-@code{rgbaf16le}, so that one is reachable from a hardware surface only.
+scaler receives, so for them this option must name a packed RGB format the
+scaler accepts, such as @code{rgba}, @code{bgra}, @code{bgr0},
+@code{x2bgr10le} or @code{rgbaf16le}. A named format also decides what
+software input is converted to, and what a hardware surface in another format
+is converted to on the GPU. Without it, a hardware surface in @code{p010} is
+converted to @code{x2bgr10le} and one in any other non-RGB format to
+@code{rgba}, while software input is converted by libavfilter to the closest
+packed RGB format the scaler accepts; the result is tagged as full range RGB.
+There is no software conversion to @code{rgbaf16le}, so that one is reachable
+from a hardware surface only.
@item keep-ratio
Force the scaler to keep the aspect ratio of the input image when the output
size has a different aspect ratio.
diff --git a/libavfilter/vf_amf_common.c b/libavfilter/vf_amf_common.c
index c1b47040b7..686286d0e6 100644
--- a/libavfilter/vf_amf_common.c
+++ b/libavfilter/vf_amf_common.c
@@ -275,12 +275,95 @@ int amf_filter_activate(AVFilterContext *avctx)
-int amf_setup_input_output_formats(AVFilterContext *avctx,
- const enum AVPixelFormat *input_pix_fmts)
+static int amf_component_input_formats(AVFilterContext *avctx, const wchar_t
*component_id,
+ int (*accept)(AVFilterContext *avctx,
enum AVPixelFormat fmt),
+ AVFilterFormats **formats)
+{
+ static const enum AVPixelFormat hw_pix_fmts[] = {
+ AV_PIX_FMT_AMF_SURFACE,
+ AV_PIX_FMT_D3D11,
+ AV_PIX_FMT_DXVA2_VLD,
+ };
+ AVBufferRef *device_ref = NULL;
+ AVHWFramesConstraints *constraints = NULL;
+ AVAMFDeviceContext *amf_ctx;
+ AMFComponent *component = NULL;
+ AMFCaps *caps = NULL;
+ AMFIOCaps *io_caps = NULL;
+ AMF_RESULT res;
+ int nb_amf_formats, i, ret;
+
+ if (avctx->hw_device_ctx)
+ ret = av_hwdevice_ctx_create_derived(&device_ref,
AV_HWDEVICE_TYPE_AMF, avctx->hw_device_ctx, 0);
+ else
+ ret = av_hwdevice_ctx_create(&device_ref, AV_HWDEVICE_TYPE_AMF, NULL,
NULL, 0);
+ if (ret < 0)
+ return ret;
+
+ constraints = av_hwdevice_get_hwframe_constraints(device_ref, NULL);
+ if (!constraints) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+
+ amf_ctx = ((AVHWDeviceContext*)device_ref->data)->hwctx;
+ res = amf_ctx->factory->pVtbl->CreateComponent(amf_ctx->factory,
amf_ctx->context, component_id, &component);
+ AMF_GOTO_FAIL_IF_FALSE(avctx, res == AMF_OK, AVERROR_FILTER_NOT_FOUND,
"CreateComponent(%ls) failed with error %d\n", component_id, res);
+ res = component->pVtbl->GetCaps(component, &caps);
+ AMF_GOTO_FAIL_IF_FALSE(avctx, res == AMF_OK, AVERROR_UNKNOWN,
"GetCaps(%ls) failed with error %d\n", component_id, res);
+ res = caps->pVtbl->GetInputCaps(caps, &io_caps);
+ AMF_GOTO_FAIL_IF_FALSE(avctx, res == AMF_OK, AVERROR_UNKNOWN,
"GetInputCaps(%ls) failed with error %d\n", component_id, res);
+ nb_amf_formats = io_caps->pVtbl->GetNumOfFormats(io_caps);
+
+ for (i = 0; i < FF_ARRAY_ELEMS(hw_pix_fmts); i++) {
+ ret = ff_add_format(formats, hw_pix_fmts[i]);
+ if (ret < 0)
+ goto fail;
+ }
+
+ for (i = 0; constraints->valid_sw_formats[i] != AV_PIX_FMT_NONE; i++) {
+ enum AVPixelFormat fmt = constraints->valid_sw_formats[i];
+ enum AMF_SURFACE_FORMAT amf_fmt = av_av_to_amf_format(fmt);
+ int j;
+
+ if (amf_fmt == AMF_SURFACE_UNKNOWN || (accept && !accept(avctx, fmt)))
+ continue;
+ for (j = 0; j < nb_amf_formats; j++) {
+ enum AMF_SURFACE_FORMAT caps_fmt;
+ amf_bool native;
+
+ if (io_caps->pVtbl->GetFormatAt(io_caps, j, &caps_fmt, &native) ==
AMF_OK && caps_fmt == amf_fmt)
+ break;
+ }
+ if (j == nb_amf_formats)
+ continue;
+ ret = ff_add_format(formats, fmt);
+ if (ret < 0)
+ goto fail;
+ }
+ ret = 0;
+
+fail:
+ if (ret < 0)
+ ff_formats_unref(formats);
+ if (io_caps)
+ io_caps->pVtbl->Release(io_caps);
+ if (caps)
+ caps->pVtbl->Release(caps);
+ if (component)
+ component->pVtbl->Release(component);
+ av_hwframe_constraints_free(&constraints);
+ av_buffer_unref(&device_ref);
+ return ret;
+}
+
+int amf_setup_input_output_formats(AVFilterContext *avctx, const wchar_t
*component_id,
+ int (*accept)(AVFilterContext *avctx, enum
AVPixelFormat fmt))
{
int err;
- AVFilterFormats *input_formats;
+ AVFilterFormats *input_formats = NULL;
AVFilterFormats *output_formats;
+ const enum AVPixelFormat *input_pix_fmts = NULL;
static const enum AVPixelFormat output_pix_fmts[] = {
AV_PIX_FMT_AMF_SURFACE,
AV_PIX_FMT_NONE,
@@ -325,9 +408,14 @@ int amf_setup_input_output_formats(AVFilterContext *avctx,
}
}
- input_formats = ff_make_pixel_format_list(input_pix_fmts);
- if (!input_formats) {
- return AVERROR(ENOMEM);
+ if (input_pix_fmts) {
+ input_formats = ff_make_pixel_format_list(input_pix_fmts);
+ if (!input_formats)
+ return AVERROR(ENOMEM);
+ } else {
+ err = amf_component_input_formats(avctx, component_id, accept,
&input_formats);
+ if (err < 0)
+ return err;
}
output_formats = ff_make_pixel_format_list(output_pix_fmts);
if (!output_formats) {
diff --git a/libavfilter/vf_amf_common.h b/libavfilter/vf_amf_common.h
index 362af88fbc..8ec95057e4 100644
--- a/libavfilter/vf_amf_common.h
+++ b/libavfilter/vf_amf_common.h
@@ -83,7 +83,8 @@ int amf_copy_surface(AVFilterContext *avctx, const AVFrame
*frame, AMFSurface* s
void amf_free_amfsurface(void *opaque, uint8_t *data);
AVFrame *amf_amfsurface_to_avframe(AVFilterContext *avctx, AMFSurface*
pSurface);
int amf_avframe_to_amfsurface(AVFilterContext *avctx, const AVFrame *frame,
AMFSurface** ppSurface);
-int amf_setup_input_output_formats(AVFilterContext *avctx, const enum
AVPixelFormat *input_pix_fmts);
+int amf_setup_input_output_formats(AVFilterContext *avctx, const wchar_t
*component_id,
+ int (*accept)(AVFilterContext *avctx, enum
AVPixelFormat fmt));
int amf_filter_filter_frame(AVFilterLink *inlink, AVFrame *in);
int amf_filter_activate(AVFilterContext *avctx);
diff --git a/libavfilter/vf_frc_amf.c b/libavfilter/vf_frc_amf.c
index 53f59b890f..11ed17cea6 100644
--- a/libavfilter/vf_frc_amf.c
+++ b/libavfilter/vf_frc_amf.c
@@ -67,20 +67,7 @@ static int amf_frc_init(AVFilterContext *avctx) {
static int amf_filter_query_formats(AVFilterContext *avctx)
{
- static const enum AVPixelFormat input_pix_fmts[] = {
- AV_PIX_FMT_AMF_SURFACE,
- AV_PIX_FMT_D3D11,
- AV_PIX_FMT_DXVA2_VLD,
- AV_PIX_FMT_NV12,
- AV_PIX_FMT_P010,
- AV_PIX_FMT_BGRA,
- AV_PIX_FMT_RGBA,
- AV_PIX_FMT_RGBAF16,
- AV_PIX_FMT_X2BGR10,
- AV_PIX_FMT_NONE,
- };
-
- return amf_setup_input_output_formats(avctx, input_pix_fmts);
+ return amf_setup_input_output_formats(avctx, AMFFRC, NULL);
}
static int amf_frc_filter_config_output(AVFilterLink *outlink)
diff --git a/libavfilter/vf_sr_amf.c b/libavfilter/vf_sr_amf.c
index d589fa9970..f4c6aaddcf 100644
--- a/libavfilter/vf_sr_amf.c
+++ b/libavfilter/vf_sr_amf.c
@@ -61,61 +61,26 @@ static int amf_hq_scaler_needs_packed_rgb(int algorithm)
static int amf_is_packed_rgb(enum AVPixelFormat format)
{
- return format == AV_PIX_FMT_RGBA || format == AV_PIX_FMT_BGRA ||
- format == AV_PIX_FMT_X2BGR10 || format == AV_PIX_FMT_RGBAF16;
+ const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(format);
+
+ return desc && (desc->flags & AV_PIX_FMT_FLAG_RGB);
+}
+
+static int amf_hq_scaler_accept(AVFilterContext *avctx, enum AVPixelFormat fmt)
+{
+ AMFFilterContext *ctx = avctx->priv;
+ int rgb_only = amf_hq_scaler_needs_packed_rgb(ctx->algorithm);
+
+ if (rgb_only && !amf_is_packed_rgb(fmt))
+ return 0;
+ if (ctx->format_opt == AV_PIX_FMT_NONE || (rgb_only &&
!amf_is_packed_rgb(ctx->format_opt)))
+ return 1;
+ return fmt == ctx->format_opt;
}
static int amf_filter_query_formats(AVFilterContext *avctx)
{
- AMFFilterContext *ctx = avctx->priv;
- const enum AVPixelFormat *input_pix_fmts;
- static const enum AVPixelFormat input_pix_fmts_default[] = {
- AV_PIX_FMT_NV12,
- AV_PIX_FMT_P010,
- AV_PIX_FMT_BGRA,
- AV_PIX_FMT_RGBA,
- AV_PIX_FMT_AMF_SURFACE,
- AV_PIX_FMT_D3D11,
- AV_PIX_FMT_DXVA2_VLD,
- AV_PIX_FMT_RGBAF16,
- AV_PIX_FMT_NONE,
- };
- // sr1-1 and point need packed RGB; YUV is converted on the GPU
- static const enum AVPixelFormat pix_fmts_packed_rgb[] = {
- AV_PIX_FMT_RGBA,
- AV_PIX_FMT_BGRA,
- AV_PIX_FMT_X2BGR10,
- AV_PIX_FMT_RGBAF16,
- AV_PIX_FMT_AMF_SURFACE,
- AV_PIX_FMT_D3D11,
- AV_PIX_FMT_DXVA2_VLD,
- AV_PIX_FMT_NONE,
- };
- enum AVPixelFormat pix_fmts_requested[] = {
- AV_PIX_FMT_NONE,
- AV_PIX_FMT_AMF_SURFACE,
- AV_PIX_FMT_D3D11,
- AV_PIX_FMT_DXVA2_VLD,
- AV_PIX_FMT_NONE,
- };
- int i;
-
- if (amf_hq_scaler_needs_packed_rgb(ctx->algorithm))
- input_pix_fmts = pix_fmts_packed_rgb;
- else
- input_pix_fmts = input_pix_fmts_default;
-
- if (ctx->format_opt != AV_PIX_FMT_NONE) {
- for (i = 0; input_pix_fmts[i] != AV_PIX_FMT_NONE; i++) {
- if (input_pix_fmts[i] == ctx->format_opt) {
- pix_fmts_requested[0] = ctx->format_opt;
- input_pix_fmts = pix_fmts_requested;
- break;
- }
- }
- }
-
- return amf_setup_input_output_formats(avctx, input_pix_fmts);
+ return amf_setup_input_output_formats(avctx, AMFHQScaler,
amf_hq_scaler_accept);
}
static int amf_filter_config_output(AVFilterLink *outlink)
diff --git a/libavfilter/vf_vpp_amf.c b/libavfilter/vf_vpp_amf.c
index 8368f2b168..ef1a8f573d 100644
--- a/libavfilter/vf_vpp_amf.c
+++ b/libavfilter/vf_vpp_amf.c
@@ -46,22 +46,7 @@
static int amf_filter_query_formats(AVFilterContext *avctx)
{
- static const enum AVPixelFormat input_pix_fmts[] = {
- AV_PIX_FMT_AMF_SURFACE,
- AV_PIX_FMT_D3D11,
- AV_PIX_FMT_DXVA2_VLD,
- AV_PIX_FMT_NV12,
- AV_PIX_FMT_P010,
- AV_PIX_FMT_BGR0,
- AV_PIX_FMT_BGRA,
- AV_PIX_FMT_RGB0,
- AV_PIX_FMT_RGBA,
- AV_PIX_FMT_YUV420P,
- AV_PIX_FMT_YUYV422,
- AV_PIX_FMT_NONE,
- };
-
- return amf_setup_input_output_formats(avctx, input_pix_fmts);
+ return amf_setup_input_output_formats(avctx, AMFVideoConverter, NULL);
}
static int amf_filter_config_output(AVFilterLink *outlink)
diff --git a/libavfilter/vf_vqe_amf.c b/libavfilter/vf_vqe_amf.c
index d54661fafb..d6ac576edd 100644
--- a/libavfilter/vf_vqe_amf.c
+++ b/libavfilter/vf_vqe_amf.c
@@ -62,16 +62,7 @@ static int amf_vqe_init(AVFilterContext *avctx) {
static int amf_filter_query_formats(AVFilterContext *avctx)
{
- static const enum AVPixelFormat input_pix_fmts[] = {
- AV_PIX_FMT_AMF_SURFACE,
- AV_PIX_FMT_D3D11,
- AV_PIX_FMT_DXVA2_VLD,
- AV_PIX_FMT_NV12,
- AV_PIX_FMT_P010,
- AV_PIX_FMT_NONE,
- };
-
- return amf_setup_input_output_formats(avctx, input_pix_fmts);
+ return amf_setup_input_output_formats(avctx, AMFVQEnhancer, NULL);
}
static int amf_vqe_filter_config_output(AVFilterLink *outlink)
@@ -94,12 +85,6 @@ static int amf_vqe_filter_config_output(AVFilterLink
*outlink)
if (err < 0)
return err;
- if (in_format != AV_PIX_FMT_NV12 && in_format != AV_PIX_FMT_P010) {
- av_log(avctx, AV_LOG_ERROR, "The VQ enhancer only accepts nv12 and
p010, got %s.\n",
- av_get_pix_fmt_name(in_format));
- return AVERROR(EINVAL);
- }
-
device_ctx = amf_ctx->amf_device_ctx;
res = AMF_IFACE_CALL(device_ctx->factory, CreateComponent,
device_ctx->context, AMFVQEnhancer, &amf_ctx->component);
--
2.52.0
_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]