PR #25458 opened by JuliusBairaktaris URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25458 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25458.patch
Depends on #25149, whose 3 commits this is stacked on; only the last 3 commits belong to this pull request. An AMF device derived from a D3D11VA or D3D12VA device runs AMF on that device, but locks a private mutex, so nothing keeps AMF apart from other users of the device. On D3D11 they share the immediate context. In mpv, which renders on the device it hands to `--vf=lavfi=[frc_amf]`, HDR playback makes libplacebo's peak detection compute run between AMFFRC's calls: AMF fails with AMF_DIRECTX_FAILED and the GPU engine times out. - `avutil/hwcontext_amf: free the default lock`: the default mutex was never freed (the check compared lock_ctx with the lock function). - `avutil/hwcontext_amf: use the parent device's lock`: a derived device uses the parent's lock callbacks; download and CPU mapping hold the lock; the lock fields are documented (lavu 61.20.102, APIchanges). - `avfilter/amf: hold the device lock around AMF calls`: around Init, SubmitInput, QueryOutput, Drain, Terminate and surface wrapping/duplication, never across ff_filter_frame(). AMF does all of its D3D11 work synchronously inside its API calls. I checked this by hooking the immediate context while FRC, VQEnhancer, HQScaler and VideoConverter run: every context call happens on the calling thread, inside an AMF call. So a lock around the calls covers it. AMFContext::LockDX11() cannot serve here: it is a mutex of the AMFContext, not the device's lock, so an application cannot take it for the AMF device that libavfilter creates. framemd5 of vqe_amf, vpp_amf (nv12, p010, download, D3D11 map, CPU map), sr_amf and frc_amf (nv12 and HDR10 p010) is unchanged, and the map API test of #25149 passes. With this and MPVLOCK, mpv plays HDR10 1080p60 through frc_amf at 120 fps without errors, 4 runs; before, it failed within a second. Tested on Windows 11, RX 9070 XT, driver 32.0.32015.7029. Written with Claude Opus 5.5 in Claude Code; `Assisted-by` trailers on the commits. >From 78bacef4935f94838db1d28c96e4505b4c2fb583 Mon Sep 17 00:00:00 2001 From: Julius Bairaktaris <[email protected]> Date: Mon, 28 Sep 2026 11:22:38 +0200 Subject: [PATCH 1/6] avutil/hwcontext_amf: do not map to system memory for a hardware target amf_map_frame() ignores dst->format and always maps to system memory. hwmap to a hardware format therefore passes a system memory frame down a hardware link, and the next filter fails. Return AVERROR(ENOSYS) for a hardware destination format. Mapping to system memory is unchanged. Assisted-by: Claude Opus 5.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 7f47df5b59d..de785db68a5 100644 --- a/libavutil/hwcontext_amf.c +++ b/libavutil/hwcontext_amf.c @@ -931,6 +931,10 @@ static int amf_map_frame(AVHWFramesContext *device_ctx, AVFrame *dst, const AVFr size_t plane_count = 0; int ret = -1; + if (dst->format != AV_PIX_FMT_NONE && + av_pix_fmt_desc_get(dst->format)->flags & AV_PIX_FMT_FLAG_HWACCEL) + return AVERROR(ENOSYS); + AMF_RETURN_IF_FALSE(device_ctx, surface != NULL, AVERROR(EINVAL) , "Source surface is null"); res = AMF_IFACE_CALL(surface, QueryInterface, &guid, (void**)&surface1); -- 2.54.0 >From 190fdb09c4256898c081c0c9e1b2131f53d5f167 Mon Sep 17 00:00:00 2001 From: Julius Bairaktaris <[email protected]> Date: Sat, 24 Oct 2026 12:14:12 +0200 Subject: [PATCH 2/6] avutil/hwcontext_amf: download without converting the source surface amf_transfer_data_from() converted the source surface to host memory in place. The surface is shared by every reference to the frame, so a download through one reference moved the surface of all others to host memory, and the runtime handed its released texture to the next allocation. Copy the surface into a new host surface with Duplicate() instead, and read host surfaces directly. Tested on Windows 11, RX 9070 XT, driver 32.0.32015.2008: after a download through a second reference the surface stays AMF_MEMORY_DX11, and its texture is no longer handed to new surfaces. 1200 frames of vqe_amf,hwdownload take 1.47 s against 1.49 s before (median of 3). Assisted-by: Claude Opus 5.5 Signed-off-by: Julius Bairaktaris <[email protected]> --- libavutil/hwcontext_amf.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/libavutil/hwcontext_amf.c b/libavutil/hwcontext_amf.c index de785db68a5..5ada787ac06 100644 --- a/libavutil/hwcontext_amf.c +++ b/libavutil/hwcontext_amf.c @@ -494,6 +494,8 @@ static int amf_transfer_data_from(AVHWFramesContext *ctx, AVFrame *dst, const AVFrame *src) { AMFSurface* surface = (AMFSurface*)src->data[0]; + const AMFGuid guid = IID_AMFSurface(); + AMFData *data; AMFPlane *plane; uint8_t *src_data[4]; int src_linesize[4]; @@ -506,8 +508,15 @@ static int amf_transfer_data_from(AVHWFramesContext *ctx, AVFrame *dst, if (src->hw_frames_ctx->data != (uint8_t *)ctx || dst->format != ctx->sw_format) return AVERROR(EINVAL); - ret = surface->pVtbl->Convert(surface, AMF_MEMORY_HOST); - AMF_RETURN_IF_FALSE(ctx, ret == AMF_OK, AVERROR_UNKNOWN, "Convert(amf::AMF_MEMORY_HOST) failed with error %d\n", AVERROR_UNKNOWN); + if (surface->pVtbl->GetMemoryType(surface) == AMF_MEMORY_HOST) { + surface->pVtbl->Acquire(surface); + } else { + ret = surface->pVtbl->Duplicate(surface, AMF_MEMORY_HOST, &data); + AMF_RETURN_IF_FALSE(ctx, ret == AMF_OK, AVERROR_UNKNOWN, "Duplicate(AMF_MEMORY_HOST) failed with error %d\n", ret); + ret = data->pVtbl->QueryInterface(data, &guid, (void**)&surface); + data->pVtbl->Release(data); + AMF_RETURN_IF_FALSE(ctx, ret == AMF_OK, AVERROR_UNKNOWN, "QueryInterface(AMFSurface) failed with error %d\n", ret); + } planes = (int)surface->pVtbl->GetPlanesCount(surface); av_assert0(planes < FF_ARRAY_ELEMS(src_data)); @@ -520,6 +529,7 @@ static int amf_transfer_data_from(AVHWFramesContext *ctx, AVFrame *dst, av_image_copy2(dst->data, dst->linesize, src_data, src_linesize, dst->format, w, h); + surface->pVtbl->Release(surface); return 0; } -- 2.54.0 >From 5e637918ca15603ae23b61ad8294ad49f1d03a3a Mon Sep 17 00:00:00 2001 From: Julius Bairaktaris <[email protected]> Date: Mon, 28 Sep 2026 11:23:11 +0200 Subject: [PATCH 3/6] avutil/hwcontext_amf: map AMF surfaces to D3D11 frames AMF surfaces reach a D3D11 consumer only through hwdownload and a new upload, although on an AMF device derived from D3D11VA they already are textures on that device. Export the surface's texture as a D3D11 frame. The mapping keeps a reference to the source frame, so it needs no unmap callback. Only surfaces in AMF_MEMORY_DX11 are mapped, since anything else would need a copy; other surfaces, such as the DX12 frames that frc_amf's DX12 engine returns, get AVERROR(ENOSYS), and hwdownload remains available for them. The mapping also requires the AMF context to run on the target's D3D11 device, compared by IUnknown identity, and a target frames context with the source's sw_format and size. It is refused for surfaces whose planes are separate textures (yuv420p) and for surfaces on a texture array: a wrapped array slice carries no reliable slice index, because the index is private data on the shared texture and the last wrapped slice overwrites it. Surfaces allocated by the AMF runtime are single textures of exactly the frame size, including odd sizes. hwmap does not restrict its output to the derived device, so format=d3d11 must follow it. Tested on Windows 11, RX 9070 XT, driver 32.0.32015.7029: framemd5 over 120 frames of d3d11va input matches the hwdownload path for vqe_amf, sr_amf, vpp_amf (nv12 and p010) and frc_amf with the DX11 engine; frc_amf with the DX12 engine is refused. An API test maps nv12, p010, bgra, rgba, bgr0, x2bgr10le and rgbaf16le surfaces at 1920x1080 and 1918x1078 and reads them back through D3D11 unchanged, also after another reference was downloaded, and checks that host surfaces and mismatched sizes are refused. Assisted-by: Claude Opus 5.5 Signed-off-by: Julius Bairaktaris <[email protected]> --- doc/APIchanges | 3 ++ libavutil/hwcontext_amf.c | 79 +++++++++++++++++++++++++++++++++++++++ libavutil/version.h | 2 +- 3 files changed, 83 insertions(+), 1 deletion(-) diff --git a/doc/APIchanges b/doc/APIchanges index 8ce9c22d68e..39f3dba61c1 100644 --- a/doc/APIchanges +++ b/doc/APIchanges @@ -2,6 +2,9 @@ The last version increases of all libraries were on 2026-06-23. API changes, most recent first: +2026-10-xx - xxxxxxxxxx - lavu 61.20.101 - hwcontext_amf.h + AMF frames can be mapped to D3D11 frames with av_hwframe_map(). + 2026-10-xx - d19f86ff4c4 - lavf 63.14.102 - avformat.h After avformat_seek_file(), the first packet of an audio stream carries AV_PKT_DATA_SKIP_SAMPLES for the larger of the rest of the priming and the diff --git a/libavutil/hwcontext_amf.c b/libavutil/hwcontext_amf.c index 5ada787ac06..0aaba9c260c 100644 --- a/libavutil/hwcontext_amf.c +++ b/libavutil/hwcontext_amf.c @@ -929,6 +929,81 @@ static void amf_unmap_frame(av_unused AVHWFramesContext *unused, HWMapDescriptor AMF_IFACE_CALL(surface1, Release); } +#if CONFIG_D3D11VA +static int amf_same_d3d11_device(ID3D11Device *a, ID3D11Device *b) +{ + static const GUID iid_unknown = { 0x00000000, 0x0000, 0x0000, { 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 } }; + IUnknown *unk_a, *unk_b; + int same; + + if (a == b) + return 1; + if (!a || !b) + return 0; + if (FAILED(a->lpVtbl->QueryInterface(a, &iid_unknown, (void **)&unk_a))) + return 0; + if (FAILED(b->lpVtbl->QueryInterface(b, &iid_unknown, (void **)&unk_b))) { + unk_a->lpVtbl->Release(unk_a); + return 0; + } + same = unk_a == unk_b; + unk_a->lpVtbl->Release(unk_a); + unk_b->lpVtbl->Release(unk_b); + return same; +} + +static int amf_map_to_d3d11(AVHWFramesContext *ctx, AVFrame *dst, const AVFrame *src) +{ + AVAMFDeviceContext *amf_ctx = ctx->device_ctx->hwctx; + AVHWFramesContext *dst_fc; + AVD3D11VADeviceContext *d3d11_ctx; + AMFSurface *surface = (AMFSurface *)src->data[0]; + D3D11_TEXTURE2D_DESC desc; + ID3D11Texture2D *texture; + AMFPlane *plane; + int ret; + + AMF_RETURN_IF_FALSE(ctx, surface, AVERROR(EINVAL), "Source surface is null\n"); + if (!dst->hw_frames_ctx) + return AVERROR(EINVAL); + dst_fc = (AVHWFramesContext *)dst->hw_frames_ctx->data; + d3d11_ctx = dst_fc->device_ctx->hwctx; + if (dst_fc->sw_format != ctx->sw_format || + dst_fc->width != ctx->width || dst_fc->height != ctx->height) + return AVERROR(ENOSYS); + if (!amf_same_d3d11_device(AMF_IFACE_CALL(amf_ctx->context, GetDX11Device, AMF_DX11_0), + d3d11_ctx->device)) { + av_log(ctx, AV_LOG_ERROR, "The AMF context does not use the D3D11 device of the target frames.\n"); + return AVERROR(EINVAL); + } + if (AMF_IFACE_CALL(surface, GetMemoryType) != AMF_MEMORY_DX11) + return AVERROR(ENOSYS); + + plane = AMF_IFACE_CALL(surface, GetPlaneAt, 0); + AMF_RETURN_IF_FALSE(ctx, plane, AVERROR(EINVAL), "Surface has no plane\n"); + texture = plane->pVtbl->GetNative(plane); + AMF_RETURN_IF_FALSE(ctx, texture, AVERROR(EINVAL), "Surface has no D3D11 texture\n"); + for (int i = 1; i < AMF_IFACE_CALL(surface, GetPlanesCount); i++) { + plane = AMF_IFACE_CALL(surface, GetPlaneAt, i); + if (!plane || plane->pVtbl->GetNative(plane) != texture) + return AVERROR(ENOSYS); + } + texture->lpVtbl->GetDesc(texture, &desc); + if (desc.ArraySize > 1) + return AVERROR(ENOSYS); + + ret = ff_hwframe_map_create(dst->hw_frames_ctx, dst, src, NULL, NULL); + if (ret < 0) + return ret; + + dst->data[0] = (uint8_t *)texture; + dst->data[1] = 0; + dst->width = src->width; + dst->height = src->height; + return 0; +} +#endif + static int amf_map_frame(AVHWFramesContext *device_ctx, AVFrame *dst, const AVFrame *src, int flags) { AMFSurface *surface = (AMFSurface *)src->data[0]; @@ -941,6 +1016,10 @@ static int amf_map_frame(AVHWFramesContext *device_ctx, AVFrame *dst, const AVFr size_t plane_count = 0; int ret = -1; +#if CONFIG_D3D11VA + if (dst->format == AV_PIX_FMT_D3D11) + return amf_map_to_d3d11(device_ctx, dst, src); +#endif if (dst->format != AV_PIX_FMT_NONE && av_pix_fmt_desc_get(dst->format)->flags & AV_PIX_FMT_FLAG_HWACCEL) return AVERROR(ENOSYS); diff --git a/libavutil/version.h b/libavutil/version.h index cbdd0ad029e..7e8891ecc6b 100644 --- a/libavutil/version.h +++ b/libavutil/version.h @@ -80,7 +80,7 @@ #define LIBAVUTIL_VERSION_MAJOR 61 #define LIBAVUTIL_VERSION_MINOR 20 -#define LIBAVUTIL_VERSION_MICRO 100 +#define LIBAVUTIL_VERSION_MICRO 101 #define LIBAVUTIL_VERSION_INT AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \ LIBAVUTIL_VERSION_MINOR, \ -- 2.54.0 >From 9acc7f3fef43b18a4ae008d564600311b5eb394d Mon Sep 17 00:00:00 2001 From: Julius Bairaktaris <[email protected]> Date: Sun, 25 Oct 2026 17:14:54 +0100 Subject: [PATCH 4/6] avutil/hwcontext_amf: free the default lock amf_device_uninit() compares lock_ctx with the default lock function, which never matches, so the mutex allocated for the default lock is neither destroyed nor freed. Compare the lock callback instead. Assisted-by: Claude Opus 5.5 Signed-off-by: Julius Bairaktaris <[email protected]> --- libavutil/hwcontext_amf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libavutil/hwcontext_amf.c b/libavutil/hwcontext_amf.c index 0aaba9c260c..668abbbe253 100644 --- a/libavutil/hwcontext_amf.c +++ b/libavutil/hwcontext_amf.c @@ -562,7 +562,7 @@ static void amf_device_uninit(AVHWDeviceContext *device_ctx) amf_writer_free(amf_ctx->trace_writer); } - if (amf_ctx->lock_ctx == amf_lock_default) { + if (amf_ctx->lock == amf_lock_default) { ff_mutex_destroy((AVMutex*)amf_ctx->lock_ctx); av_freep(&amf_ctx->lock_ctx); amf_ctx->lock = NULL; -- 2.54.0 >From ffaa29dbd5dfb165b6bf9f56fe3353c08d87befe Mon Sep 17 00:00:00 2001 From: Julius Bairaktaris <[email protected]> Date: Sun, 25 Oct 2026 17:14:54 +0100 Subject: [PATCH 5/6] avutil/hwcontext_amf: use the parent device's lock An AMF device derived from a D3D11VA or D3D12VA device runs AMF on that device, but its lock callbacks default to a private mutex, so nothing keeps AMF's work apart from other users of the same device. On a D3D11 device the users share the immediate context, and another thread can change the pipeline state between AMF's calls. An application that renders on the device it gave to the AMF filters, such as mpv with HDR content and frc_amf, sees AMF_DIRECTX_FAILED followed by a GPU engine timeout. AMF does all its work on the device synchronously inside its API calls, which I checked by hooking the D3D11 immediate context, so a lock held around those calls covers it. Use the lock callbacks of the D3D11VA or D3D12VA device the AMF device is derived from, so that the application's lock also excludes AMF, and hold the device lock in the download and CPU map paths, which run AMF on the device. Document the lock fields, which were undocumented. Tested on Windows 11, RX 9070 XT, driver 32.0.32015.7029: framemd5 of vqe_amf, vpp_amf (nv12 and p010, download, D3D11 map and CPU map), sr_amf and frc_amf (nv12 and HDR10 p010) is unchanged. With this series and the matching mpv change, mpv plays HDR10 1080p60 through frc_amf at 120 fps without errors, where it failed within a second before. Assisted-by: Claude Opus 5.5 Signed-off-by: Julius Bairaktaris <[email protected]> --- doc/APIchanges | 4 ++++ libavutil/hwcontext_amf.c | 43 ++++++++++++++++++++++++++++++++++----- libavutil/hwcontext_amf.h | 9 ++++++++ libavutil/version.h | 2 +- 4 files changed, 52 insertions(+), 6 deletions(-) diff --git a/doc/APIchanges b/doc/APIchanges index 39f3dba61c1..43c0db77135 100644 --- a/doc/APIchanges +++ b/doc/APIchanges @@ -2,6 +2,10 @@ The last version increases of all libraries were on 2026-06-23. API changes, most recent first: +2026-10-xx - xxxxxxxxxx - lavu 61.20.102 - hwcontext_amf.h + Document AVAMFDeviceContext.lock, .unlock and .lock_ctx. An AMF device + derived from a D3D11VA or D3D12VA device now uses that device's lock. + 2026-10-xx - xxxxxxxxxx - lavu 61.20.101 - hwcontext_amf.h AMF frames can be mapped to D3D11 frames with av_hwframe_map(). diff --git a/libavutil/hwcontext_amf.c b/libavutil/hwcontext_amf.c index 668abbbe253..16de869e69b 100644 --- a/libavutil/hwcontext_amf.c +++ b/libavutil/hwcontext_amf.c @@ -511,7 +511,10 @@ static int amf_transfer_data_from(AVHWFramesContext *ctx, AVFrame *dst, if (surface->pVtbl->GetMemoryType(surface) == AMF_MEMORY_HOST) { surface->pVtbl->Acquire(surface); } else { + AVAMFDeviceContext *amf_ctx = ctx->device_ctx->hwctx; + amf_ctx->lock(amf_ctx->lock_ctx); ret = surface->pVtbl->Duplicate(surface, AMF_MEMORY_HOST, &data); + amf_ctx->unlock(amf_ctx->lock_ctx); AMF_RETURN_IF_FALSE(ctx, ret == AMF_OK, AVERROR_UNKNOWN, "Duplicate(AMF_MEMORY_HOST) failed with error %d\n", ret); ret = data->pVtbl->QueryInterface(data, &guid, (void**)&surface); data->pVtbl->Release(data); @@ -889,6 +892,20 @@ static int amf_init_from_d3d12_device(AVAMFDeviceContext* amf_ctx, AVHWDeviceCon #endif +#if CONFIG_D3D11VA || CONFIG_D3D12VA +static void amf_use_device_lock(AVAMFDeviceContext *amf_ctx, void (*lock)(void *lock_ctx), + void (*unlock)(void *lock_ctx), void *lock_ctx) +{ + if (amf_ctx->lock == amf_lock_default) { + ff_mutex_destroy((AVMutex*)amf_ctx->lock_ctx); + av_freep(&amf_ctx->lock_ctx); + } + amf_ctx->lock = lock; + amf_ctx->unlock = unlock; + amf_ctx->lock_ctx = lock_ctx; +} +#endif + static int amf_device_derive(AVHWDeviceContext *device_ctx, AVHWDeviceContext *child_device_ctx, AVDictionary *opts, int flags) @@ -908,12 +925,22 @@ static int amf_device_derive(AVHWDeviceContext *device_ctx, return amf_init_from_dxva2_device(amf_ctx, child_device_ctx); #endif #if CONFIG_D3D11VA - case AV_HWDEVICE_TYPE_D3D11VA: - return amf_init_from_d3d11_device(amf_ctx, child_device_ctx); + case AV_HWDEVICE_TYPE_D3D11VA: { + AVD3D11VADeviceContext *d3d11_ctx = child_device_ctx->hwctx; + ret = amf_init_from_d3d11_device(amf_ctx, child_device_ctx); + if (ret >= 0) + amf_use_device_lock(amf_ctx, d3d11_ctx->lock, d3d11_ctx->unlock, d3d11_ctx->lock_ctx); + return ret; + } #endif #if CONFIG_D3D12VA - case AV_HWDEVICE_TYPE_D3D12VA: - return amf_init_from_d3d12_device(amf_ctx, child_device_ctx); + case AV_HWDEVICE_TYPE_D3D12VA: { + AVD3D12VADeviceContext *d3d12_ctx = child_device_ctx->hwctx; + ret = amf_init_from_d3d12_device(amf_ctx, child_device_ctx); + if (ret >= 0) + amf_use_device_lock(amf_ctx, d3d12_ctx->lock, d3d12_ctx->unlock, d3d12_ctx->lock_ctx); + return ret; + } #endif default: av_log(child_device_ctx, AV_LOG_ERROR, "AMF initialisation from a %s device is not supported.\n", @@ -922,10 +949,13 @@ static int amf_device_derive(AVHWDeviceContext *device_ctx, } } -static void amf_unmap_frame(av_unused AVHWFramesContext *unused, HWMapDescriptor *hwmap) +static void amf_unmap_frame(AVHWFramesContext *ctx, HWMapDescriptor *hwmap) { + AVAMFDeviceContext *amf_ctx = ctx->device_ctx->hwctx; AMFSurface1 *surface1 = hwmap->priv; + amf_ctx->lock(amf_ctx->lock_ctx); AMF_IFACE_CALL(surface1, Unmap); + amf_ctx->unlock(amf_ctx->lock_ctx); AMF_IFACE_CALL(surface1, Release); } @@ -1006,6 +1036,7 @@ static int amf_map_to_d3d11(AVHWFramesContext *ctx, AVFrame *dst, const AVFrame static int amf_map_frame(AVHWFramesContext *device_ctx, AVFrame *dst, const AVFrame *src, int flags) { + AVAMFDeviceContext *amf_ctx = device_ctx->device_ctx->hwctx; AMFSurface *surface = (AMFSurface *)src->data[0]; AMFSurface1 *surface1 = NULL; size_t *linesizes = NULL; @@ -1044,7 +1075,9 @@ static int amf_map_frame(AVHWFramesContext *device_ctx, AVFrame *dst, const AVFr dst->format = av_amf_to_av_format(AMF_IFACE_CALL(surface, GetFormat)); + amf_ctx->lock(amf_ctx->lock_ctx); res = AMF_IFACE_CALL(surface1, Map, amf_mem_flags, plane_count, linesizes, (void**)map); + amf_ctx->unlock(amf_ctx->lock_ctx); AMF_GOTO_FAIL_IF_FALSE(device_ctx, res == AMF_OK, AVERROR_UNKNOWN, "AMFSurface1->Map() failed with error %d\n", res); ret = ff_hwframe_map_create(src->hw_frames_ctx, dst, src, &amf_unmap_frame, surface1); diff --git a/libavutil/hwcontext_amf.h b/libavutil/hwcontext_amf.h index d2bbb418911..a3039c18e5d 100644 --- a/libavutil/hwcontext_amf.h +++ b/libavutil/hwcontext_amf.h @@ -41,6 +41,15 @@ typedef struct AVAMFDeviceContext { AMFContext *context; AMF_MEMORY_TYPE memory_type; + /** + * Callbacks for locking. They are held around AMF calls that use the + * device, so that other code sharing the device can exclude them. + * + * If unset on init, an internal mutex is used. A device derived from a + * D3D11VA or D3D12VA device uses that device's lock callbacks instead, so + * AMF work on the device is serialized with everything else that holds + * that lock. + */ void (*lock)(void *lock_ctx); void (*unlock)(void *lock_ctx); void *lock_ctx; diff --git a/libavutil/version.h b/libavutil/version.h index 7e8891ecc6b..9ea080edacc 100644 --- a/libavutil/version.h +++ b/libavutil/version.h @@ -80,7 +80,7 @@ #define LIBAVUTIL_VERSION_MAJOR 61 #define LIBAVUTIL_VERSION_MINOR 20 -#define LIBAVUTIL_VERSION_MICRO 101 +#define LIBAVUTIL_VERSION_MICRO 102 #define LIBAVUTIL_VERSION_INT AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \ LIBAVUTIL_VERSION_MINOR, \ -- 2.54.0 >From 338f94f6d794348ccbfc5e08aeb9015f4b2f7f32 Mon Sep 17 00:00:00 2001 From: Julius Bairaktaris <[email protected]> Date: Sun, 25 Oct 2026 17:14:54 +0100 Subject: [PATCH 6/6] avfilter/amf: hold the device lock around AMF calls The AMF filters call Init(), SubmitInput(), QueryOutput(), Drain() and Terminate() and wrap and duplicate surfaces without the device lock, so on a device that other code also uses, those calls can interleave with the other user's work on the same D3D11 context. Hold the AMF device lock around each of them. The lock is not held between calls or across ff_filter_frame(), so a downstream filter on the same device can take it, and the D3D11 copy of decoder slices still takes the D3D11VA lock on its own. Assisted-by: Claude Opus 5.5 Signed-off-by: Julius Bairaktaris <[email protected]> --- libavfilter/vf_amf_common.c | 34 ++++++++++++++++++++++++++++++++-- libavfilter/vf_amf_common.h | 2 ++ libavfilter/vf_frc_amf.c | 2 ++ libavfilter/vf_sr_amf.c | 4 ++++ libavfilter/vf_vpp_amf.c | 2 ++ libavfilter/vf_vqe_amf.c | 2 ++ 6 files changed, 44 insertions(+), 2 deletions(-) diff --git a/libavfilter/vf_amf_common.c b/libavfilter/vf_amf_common.c index e1d0b42ee37..1c148cd4b04 100644 --- a/libavfilter/vf_amf_common.c +++ b/libavfilter/vf_amf_common.c @@ -61,10 +61,23 @@ int amf_filter_init(AVFilterContext *avctx) return 0; } +void amf_lock(AMFFilterContext *ctx) +{ + if (ctx->amf_device_ctx && ctx->amf_device_ctx->lock) + ctx->amf_device_ctx->lock(ctx->amf_device_ctx->lock_ctx); +} + +void amf_unlock(AMFFilterContext *ctx) +{ + if (ctx->amf_device_ctx && ctx->amf_device_ctx->unlock) + ctx->amf_device_ctx->unlock(ctx->amf_device_ctx->lock_ctx); +} + void amf_filter_uninit(AVFilterContext *avctx) { AMFFilterContext *ctx = avctx->priv; + amf_lock(ctx); if (ctx->component) { ctx->component->pVtbl->Terminate(ctx->component); ctx->component->pVtbl->Release(ctx->component); @@ -76,6 +89,7 @@ void amf_filter_uninit(AVFilterContext *avctx) ctx->pre_converter->pVtbl->Release(ctx->pre_converter); ctx->pre_converter = NULL; } + amf_unlock(ctx); if (ctx->pending) { AVFrame *props; @@ -121,7 +135,9 @@ static int amf_receive_surface(AVFilterContext *avctx, AMFComponent *component, AMF_RESULT res; *surface = NULL; + amf_lock(avctx->priv); res = component->pVtbl->QueryOutput(component, &data); + amf_unlock(avctx->priv); AMF_RETURN_IF_FALSE(avctx, res == AMF_OK || res == AMF_REPEAT || res == AMF_EOF, AVERROR_UNKNOWN, "QueryOutput() failed with error %d\n", res); if (!data) @@ -232,7 +248,12 @@ static int amf_submit_surface(AVFilterContext *avctx, AMFComponent *component, A AMF_RESULT res; int ret = 0; - while ((res = component->pVtbl->SubmitInput(component, (AMFData*)surface)) == AMF_INPUT_FULL) { + for (;;) { + amf_lock(avctx->priv); + res = component->pVtbl->SubmitInput(component, (AMFData*)surface); + amf_unlock(avctx->priv); + if (res != AMF_INPUT_FULL) + break; ret = deliver(avctx); if (ret < 0) break; @@ -268,7 +289,12 @@ static int amf_drain_component(AVFilterContext *avctx, AMFComponent *component, AMF_RESULT res; int ret; - while ((res = component->pVtbl->Drain(component)) == AMF_INPUT_FULL) { + for (;;) { + amf_lock(avctx->priv); + res = component->pVtbl->Drain(component); + amf_unlock(avctx->priv); + if (res != AMF_INPUT_FULL) + break; ret = deliver(avctx); if (ret < 0) return ret; @@ -808,7 +834,9 @@ int amf_avframe_to_amfsurface(AVFilterContext *avctx, const AVFrame *frame, AMFS ret = amf_hold_frame(frame, &observer); if (ret < 0) return ret; + amf_lock(ctx); res = ctx->amf_device_ctx->context->pVtbl->CreateSurfaceFromDX11Native(ctx->amf_device_ctx->context, texture, &surface, observer); // wrap to AMF surface + amf_unlock(ctx); if (res != AMF_OK) amf_release_held_frame(observer, NULL); AMF_RETURN_IF_FALSE(avctx, res == AMF_OK, AVERROR(ENOMEM), "CreateSurfaceFromDX11Native() failed with error %d\n", res); @@ -927,7 +955,9 @@ int amf_avframe_to_amfsurface(AVFilterContext *avctx, const AVFrame *frame, AMFS if (crop_x || crop_y) { if (crop_w == outlink->w && crop_h == outlink->h) { AMFData *cropped_buffer = NULL; + amf_lock(ctx); res = surface->pVtbl->Duplicate(surface, surface->pVtbl->GetMemoryType(surface), &cropped_buffer); + amf_unlock(ctx); AMF_RETURN_IF_FALSE(avctx, res == AMF_OK, AVERROR(ENOMEM), "Duplicate() failed with error %d\n", res); surface->pVtbl->Release(surface); surface = (AMFSurface*)cropped_buffer; diff --git a/libavfilter/vf_amf_common.h b/libavfilter/vf_amf_common.h index 9af3d2ddda9..4c38ae5874c 100644 --- a/libavfilter/vf_amf_common.h +++ b/libavfilter/vf_amf_common.h @@ -79,6 +79,8 @@ typedef struct AMFFilterContext { } AMFFilterContext; int amf_filter_init(AVFilterContext *avctx); +void amf_lock(AMFFilterContext *ctx); +void amf_unlock(AMFFilterContext *ctx); 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); diff --git a/libavfilter/vf_frc_amf.c b/libavfilter/vf_frc_amf.c index c7ef58b4294..8075b1bc551 100644 --- a/libavfilter/vf_frc_amf.c +++ b/libavfilter/vf_frc_amf.c @@ -135,7 +135,9 @@ static int amf_frc_filter_config_output(AVFilterLink *outlink) AMF_FRC_ASSIGN_PROPERTY_INT64_CHECK(avctx, amf_filter, AMF_FRC_USE_FUTURE_FRAME, frc_ctx->use_future_frame); + amf_lock(amf_ctx); res = AMF_IFACE_CALL(amf_filter, Init, av_av_to_amf_format(in_format), inlink->w, inlink->h); + amf_unlock(amf_ctx); AMF_RETURN_IF_FALSE(avctx, res == AMF_OK, AVERROR_UNKNOWN, "AMFFRC->Init() failed with error %d\n", res); AMF_ASSIGN_PROPERTY_INT64(res, amf_filter, AMF_FRC_MODE, frc_ctx->enable ? FRC_x2_PRESENT : FRC_OFF); diff --git a/libavfilter/vf_sr_amf.c b/libavfilter/vf_sr_amf.c index d589fa99703..6c009c04158 100644 --- a/libavfilter/vf_sr_amf.c +++ b/libavfilter/vf_sr_amf.c @@ -172,7 +172,9 @@ static int amf_filter_config_output(AVFilterLink *outlink) 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); + amf_lock(ctx); res = ctx->pre_converter->pVtbl->Init(ctx->pre_converter, av_av_to_amf_format(in_format), inlink->w, inlink->h); + amf_unlock(ctx); 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), @@ -216,7 +218,9 @@ static int amf_filter_config_output(AVFilterLink *outlink) ctx->out_primaries = AMF_COLOR_PRIMARIES_UNDEFINED; ctx->out_trc = AMF_COLOR_TRANSFER_CHARACTERISTIC_UNDEFINED; + amf_lock(ctx); res = ctx->component->pVtbl->Init(ctx->component, av_av_to_amf_format(in_format), inlink->w, inlink->h); + amf_unlock(ctx); AMF_RETURN_IF_FALSE(avctx, res == AMF_OK, AVERROR_UNKNOWN, "AMFHQScaler-Init() failed with error %d\n", res); return 0; diff --git a/libavfilter/vf_vpp_amf.c b/libavfilter/vf_vpp_amf.c index 1369c400124..41369776687 100644 --- a/libavfilter/vf_vpp_amf.c +++ b/libavfilter/vf_vpp_amf.c @@ -189,7 +189,9 @@ static int amf_filter_config_output(AVFilterLink *outlink) AMF_ASSIGN_PROPERTY_INT64(res, ctx->component, AMF_VIDEO_CONVERTER_OUTPUT_TRANSFER_CHARACTERISTIC, ctx->out_trc); } + amf_lock(ctx); res = ctx->component->pVtbl->Init(ctx->component, av_av_to_amf_format(in_format), inlink->w, inlink->h); + amf_unlock(ctx); AMF_RETURN_IF_FALSE(avctx, res == AMF_OK, AVERROR_UNKNOWN, "AMFConverter-Init() failed with error %d\n", res); return 0; diff --git a/libavfilter/vf_vqe_amf.c b/libavfilter/vf_vqe_amf.c index 0e5a55ada51..84d5bfed1c6 100644 --- a/libavfilter/vf_vqe_amf.c +++ b/libavfilter/vf_vqe_amf.c @@ -117,7 +117,9 @@ static int amf_vqe_filter_config_output(AVFilterLink *outlink) AMF_ASSIGN_PROPERTY_DOUBLE(res, amf_filter, AMF_VE_FCR_ATTENUATION, vqe_ctx->attenuation); AMF_RETURN_IF_FALSE(avctx, res == AMF_OK, AVERROR_UNKNOWN, "Failed to set VQ enhancer attenuation: %d\n", res); + amf_lock(amf_ctx); res = AMF_IFACE_CALL(amf_filter, Init, av_av_to_amf_format(in_format), inlink->w, inlink->h); + amf_unlock(amf_ctx); AMF_RETURN_IF_FALSE(avctx, res == AMF_OK, AVERROR_UNKNOWN, "AMFVQEnhancer-Init() failed with error %d\n", res); return 0; -- 2.54.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
