From cb1b6a54f2d533d4e70b9a2a61685e26d5fbf832 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= Date: Mon, 15 Dec 2025 06:09:23 +0100 Subject: [PATCH] video/filter/vf_amf: add AMD Frame Rate Conversion filter D3D11 only for now. Documentation says that only D3D11 and D3D12 is supported, but it looks like Vulkan is possible too. But I realized this after implementing this version. Vulan may be added later. Fixes: #16613 --- DOCS/interface-changes/vf-amd-frc.txt | 1 + DOCS/man/vf.rst | 51 ++ filters/user_filters.c | 3 + filters/user_filters.h | 1 + meson.build | 7 + meson.options | 1 + video/filter/vf_amf.c | 757 ++++++++++++++++++++++++++ 7 files changed, 821 insertions(+) create mode 100644 DOCS/interface-changes/vf-amd-frc.txt create mode 100644 video/filter/vf_amf.c diff --git a/DOCS/interface-changes/vf-amd-frc.txt b/DOCS/interface-changes/vf-amd-frc.txt new file mode 100644 index 0000000000..c156b930e0 --- /dev/null +++ b/DOCS/interface-changes/vf-amd-frc.txt @@ -0,0 +1 @@ +add `--vf=amf_frc` diff --git a/DOCS/man/vf.rst b/DOCS/man/vf.rst index 4eb00e3085..d9d3dfbe59 100644 --- a/DOCS/man/vf.rst +++ b/DOCS/man/vf.rst @@ -750,6 +750,57 @@ Available mpv-only filters are: ``nvidia-true-hdr`` Enable NVIDIA RTX Video HDR processing. +``amf_frc`` + AMD Frame Rate Conversion filter. Requires AMD hardware and drivers + supporting AMF FRC. + + AMF FRC is a technique for achieving high-end video frame rate conversion + results from lower frame rate video inputs. The filter doubles the input + frame rate by generating intermediate frames. + + AMF FRC only supports D3D11 input currently. Use `--hwdec=d3d11va` or + `--vf-pre=format=d3d11` to upload the data. `--d3d11va-zero-copy` is also + recommended for better performance. + + ``profile=`` + Selects the quality profile for frame rate conversion. + + ``auto`` (default) + Select profile based on input resolution. Uses ``high`` for + resolutions up to 1440p, and ``super`` for 1440p and higher. + ``low`` + Less levels of hierarchical motion search. Only recommended for + extremely low resolutions. + ``high`` + Recommended for any resolution up to 1440p. + ``super`` + More levels of hierarchical motion search. Recommended for + resolutions 1440p or higher. + + ``mv-search=`` + Selects the motion vector search mode. + + ``native`` (default) + Conduct motion search on the full resolution of source images. + ``performance`` + Conduct motion search on the down scaled source images. + Recommended for APU or low end GPU for better performance. + + ``fallback`` + In case confidence is too low to do interpolation, fall back to simple + frame blending. If not set, the filter will repeat the last frame instead. + (default: no) + + ``indicator`` + Specifies whether or not the FRC indicator square is shown in the top + left corner of the video. (default: no) + + ``future-frame`` + When enabled, the information contained in the next frame in the + sequence will be used in FRC interpolation calculations, in addition to + the current pair of frames. This will introduce one extra frame time of + latency. (default: yes) + ``fingerprint=...`` Compute video frame fingerprints and provide them as metadata. Actually, it currently barely deserved to be called ``fingerprint``, because it does not diff --git a/filters/user_filters.c b/filters/user_filters.c index 68c7016371..7bdda69653 100644 --- a/filters/user_filters.c +++ b/filters/user_filters.c @@ -100,6 +100,9 @@ const struct mp_user_filter_entry *vf_list[] = { #if HAVE_D3D_HWACCEL &vf_d3d11vpp, #endif +#if HAVE_AMF + &vf_amf_frc, +#endif #if (HAVE_GL && HAVE_EGL) || HAVE_VULKAN &vf_gpu, #endif diff --git a/filters/user_filters.h b/filters/user_filters.h index 639ffe7ddf..aa88649b33 100644 --- a/filters/user_filters.h +++ b/filters/user_filters.h @@ -35,5 +35,6 @@ extern const struct mp_user_filter_entry vf_format; extern const struct mp_user_filter_entry vf_vdpaupp; extern const struct mp_user_filter_entry vf_vavpp; extern const struct mp_user_filter_entry vf_d3d11vpp; +extern const struct mp_user_filter_entry vf_amf_frc; extern const struct mp_user_filter_entry vf_fingerprint; extern const struct mp_user_filter_entry vf_gpu; diff --git a/meson.build b/meson.build index 6dfbcb939e..b8decc1d82 100644 --- a/meson.build +++ b/meson.build @@ -1401,6 +1401,13 @@ if features['d3d-hwaccel'] 'video/filter/vf_d3d11vpp.c') endif +amf = cc.has_header('AMF/core/Factory.h', required: + get_option('amf').require(features['d3d-hwaccel'])) +features += {'amf': amf} +if features['amf'] + sources += files('video/filter/vf_amf.c') +endif + if features['d3d-hwaccel'] and features['egl-angle'] sources += files('video/out/opengl/hwdec_d3d11egl.c') endif diff --git a/meson.options b/meson.options index 8ced8d75a4..836d16d03f 100644 --- a/meson.options +++ b/meson.options @@ -57,6 +57,7 @@ option('sndio', type: 'feature', value: 'auto', description: 'sndio audio output option('wasapi', type: 'feature', value: 'auto', description: 'WASAPI audio output') # video output features +option('amf', type: 'feature', value: 'auto', description: 'AMD AMF') option('caca', type: 'feature', value: 'auto', description: 'CACA') option('cocoa', type: 'feature', value: 'auto', description: 'Cocoa') option('d3d11', type: 'feature', value: 'auto', description: 'Direct3D 11 video output') diff --git a/video/filter/vf_amf.c b/video/filter/vf_amf.c new file mode 100644 index 0000000000..6229802b9b --- /dev/null +++ b/video/filter/vf_amf.c @@ -0,0 +1,757 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see . + */ + +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "filters/filter_internal.h" +#include "filters/filter.h" +#include "filters/user_filters.h" +#include "osdep/windows_utils.h" +#include "video/hwdec.h" +#include "video/mp_image.h" + +#define ICALL(func, that, ...) (that)->pVtbl->func((that), ##__VA_ARGS__) + +// Refcounting helpers +typedef atomic_uint_fast32_t mp_rc_t; +#define mp_rc_init(rc) atomic_init(rc, 1) +#define mp_rc_ref(rc) ((void) atomic_fetch_add_explicit(rc, 1, memory_order_acquire)) +#define mp_rc_deref(rc) (atomic_fetch_sub_explicit(rc, 1, memory_order_release) == 1) +#define mp_rc_count(rc) atomic_load(rc) + +struct opts { + int profile; + int mv_search_mode; + bool fallback_blend; + bool indicator; + bool use_future_frame; +}; + +struct amf_priv { + mp_rc_t ref_count; + HMODULE dll; + AMFFactory *factory; + AMFContext *ctx; + AMFComponent *frc; +}; + +struct priv { + struct opts *opts; + + struct mp_image_params params; + struct mp_image *ref_image; + + ID3D11Device *d3d_dev; + ID3D11Texture2D *input_tex; + + bool has_frame; + + AVBufferRef *av_device_ref; + + struct amf_priv *amf; +}; + +static inline AMF_SURFACE_FORMAT imgfmt2amf(int fmt) +{ + switch (fmt) { + case IMGFMT_NV12: + return AMF_SURFACE_NV12; + case IMGFMT_BGRA: + return AMF_SURFACE_BGRA; + case IMGFMT_RGBA: + return AMF_SURFACE_RGBA; + case IMGFMT_P010: + return AMF_SURFACE_P010; + default: + return AMF_SURFACE_UNKNOWN; + } +} + +static void unload_amf_library(struct amf_priv *a) +{ + mp_assert(!a->frc && !a->ctx); + if (a->dll) { + FreeLibrary(a->dll); + a->dll = NULL; + } + a->factory = NULL; +} + +static int load_amf_library(struct amf_priv *a) +{ + a->dll = LoadLibraryW(AMF_DLL_NAME); + if (!a->dll) + return -1; + + AMFInit_Fn init_fn = (AMFInit_Fn)GetProcAddress(a->dll, AMF_INIT_FUNCTION_NAME); + if (!init_fn) + goto err; + + AMF_RESULT res = init_fn(AMF_FULL_VERSION, &a->factory); + if (res != AMF_OK) + goto err; + + return 0; + +err: + unload_amf_library(a); + return -1; +} + +static void flush(struct mp_filter *f) +{ + struct priv *p = f->priv; + + if (p->amf->frc) + ICALL(Flush, p->amf->frc); + p->has_frame = false; + mp_image_unrefp(&p->ref_image); +} + +static void destroy_amf(struct amf_priv *a) +{ + if (!mp_rc_deref(&a->ref_count)) + return; + + if (a->frc) { + ICALL(Release, a->frc); + a->frc = NULL; + } + + if (a->ctx) { + ICALL(Release, a->ctx); + a->ctx = NULL; + } + + unload_amf_library(a); + talloc_free(a); +} + +static void destroy(struct mp_filter *f) +{ + struct priv *p = f->priv; + struct amf_priv *a = p->amf; + + flush(f); + + if (a->frc) + ICALL(Terminate, a->frc); + + if (a->ctx) + ICALL(Terminate, a->ctx); + + SAFE_RELEASE(p->input_tex); + SAFE_RELEASE(p->d3d_dev); + av_buffer_unref(&p->av_device_ref); + + destroy_amf(a); +} + +static int create_amf_context(struct mp_filter *f) +{ + struct priv *p = f->priv; + struct amf_priv *a = p->amf; + + AMF_RESULT res = ICALL(CreateContext, a->factory, &a->ctx); + if (res != AMF_OK) { + MP_ERR(f, "Failed to create AMF context: %d\n", res); + goto err; + } + + struct mp_stream_info *info = mp_filter_find_stream_info(f); + if (!info || !info->hwdec_devs) { + MP_ERR(f, "No hwdec devices available for AMF initialization.\n"); + goto err; + } + + struct hwdec_imgfmt_request params = { + .imgfmt = IMGFMT_D3D11, + .probing = false, + }; + hwdec_devices_request_for_img_fmt(info->hwdec_devs, ¶ms); + struct mp_hwdec_ctx *hwctx = + hwdec_devices_get_by_imgfmt_and_type(info->hwdec_devs, IMGFMT_D3D11, + AV_HWDEVICE_TYPE_D3D11VA); + if (!hwctx || !hwctx->av_device_ref) { + MP_ERR(f, "No suitable D3D11 hwdec device found for AMF initialization.\n"); + goto err; + } + + p->av_device_ref = av_buffer_ref(hwctx->av_device_ref); + MP_HANDLE_OOM(p->av_device_ref); + AVHWDeviceContext *avhwctx = (void *)p->av_device_ref->data; + AVD3D11VADeviceContext *d3dctx = avhwctx->hwctx; + ID3D11Device_QueryInterface(d3dctx->device, &IID_ID3D11Device, (void **)&p->d3d_dev); + + // AMF uses its own separate D3D11 device. This avoids any conflicts with + // with our presentation device, as we run it on different thread. Things + // work well, until there is compute shader processing done on the same device. + res = ICALL(InitDX11, a->ctx, NULL, AMF_DX11_1); + if (res != AMF_OK) { + MP_ERR(f, "Failed to initialize AMF context with D3D11: %d\n", res); + goto err; + } + + return 0; + +err: + destroy(f); + + return -1; +} + +static int create_amf_frc(struct mp_filter *f) +{ + struct priv *p = f->priv; + struct amf_priv *a = p->amf; + + mp_assert(!a->frc); + AMF_RESULT res = ICALL(CreateComponent, a->factory, a->ctx, AMFFRC, &a->frc); + if (res != AMF_OK) { + MP_ERR(f, "Failed to create AMF FRC component: %d\n", res); + return -1; + } + + return 0; +} + +static int init_frc(struct mp_filter *f, struct mp_image *src, bool reinit) +{ + AMF_RESULT res; + struct priv *p = f->priv; + struct amf_priv *a = p->amf; + + mp_assert(a->frc); + + // Support only D3D11 input, while it's possible to input host memory, this + // adds unnecessary complexity. We already have host memory upload filters, + // that can be used, before calling this filter. Also using host memory here, + // would return source (non-interpolated) frames also in host memory, which + // again forces us to upload them back to GPU for presentation, duplicating work. + if (p->params.imgfmt != IMGFMT_D3D11) { + MP_ERR(f, "AMF FRC only supports D3D11 input. " + "Use `--hwdec=d3d11va` or `--vf-pre=format=d3d11` to upload the data.\n"); + goto err; + } + + if (reinit) + goto init; + + AMF_ASSIGN_PROPERTY_INT64(res, a->frc, AMF_FRC_ENGINE_TYPE, AMF_MEMORY_DX11); + if (res != AMF_OK) { + MP_WARN(f, "Failed to set FRC engine type to DX11: %d\n", res); + goto err; + } + + AMF_ASSIGN_PROPERTY_INT64(res, a->frc, AMF_FRC_MODE, FRC_x2_PRESENT); + if (res != AMF_OK) { + MP_ERR(f, "Failed to set FRC mode to x2_PRESENT: %d\n", res); + goto err; + } + + int profile = p->opts->profile; + if (profile == -1) { + if (p->params.h >= 1440) + profile = FRC_PROFILE_SUPER; + else + profile = FRC_PROFILE_HIGH; + } + profile = FRC_PROFILE_HIGH; + AMF_ASSIGN_PROPERTY_INT64(res, a->frc, AMF_FRC_PROFILE, profile); + if (res != AMF_OK) { + MP_WARN(f, "Failed to set FRC profile: %d\n", res); + goto err; + } + + AMF_ASSIGN_PROPERTY_INT64(res, a->frc, AMF_FRC_MV_SEARCH_MODE, + p->opts->mv_search_mode); + if (res != AMF_OK) { + MP_WARN(f, "Failed to set FRC motion vector search mode: %d\n", res); + goto err; + } + + AMF_ASSIGN_PROPERTY_BOOL(res, a->frc, AMF_FRC_ENABLE_FALLBACK, + p->opts->fallback_blend); + if (res != AMF_OK) { + MP_WARN(f, "Failed to set FRC fallback mode: %d\n", res); + goto err; + } + + AMF_ASSIGN_PROPERTY_BOOL(res, a->frc, AMF_FRC_INDICATOR, + p->opts->indicator); + if (res != AMF_OK) { + MP_WARN(f, "Failed to set FRC indicator: %d\n", res); + goto err; + } + + AMF_ASSIGN_PROPERTY_BOOL(res, a->frc, AMF_FRC_USE_FUTURE_FRAME, + p->opts->use_future_frame); + if (res != AMF_OK) { + MP_WARN(f, "Failed to set FRC use_future_frame: %d\n", res); + goto err; + } + +init:; + AMF_SURFACE_FORMAT amf_fmt = imgfmt2amf(p->params.hw_subfmt); + if (amf_fmt == AMF_SURFACE_UNKNOWN) { + MP_ERR(f, "AMF FRC does not support input format: %s\n", + mp_imgfmt_to_name(p->params.hw_subfmt)); + goto err; + } + + // FRC expects texture size here, not the coded image size + D3D11_TEXTURE2D_DESC desc = {0}; + ID3D11Texture2D_GetDesc((ID3D11Texture2D *)src->planes[0], &desc); + res = ICALL(Init, a->frc, amf_fmt, desc.Width, desc.Height); + if (res != AMF_OK) { + MP_ERR(f, "Failed to initialize FRC component: %d\n", res); + goto err; + } + + SAFE_RELEASE(p->input_tex); + + return 0; + +err: + ICALL(Terminate, a->frc); + return -1; +} + +struct amf_surface_free_ctx { + struct amf_priv *a; + AMFSurface *surface; + ID3D11Texture2D *tex; +}; + +static void free_amf_surface(void *arg) +{ + struct amf_surface_free_ctx *ctx = arg; + ICALL(Release, ctx->surface); + ID3D11Texture2D_Release(ctx->tex); // shared texture + destroy_amf(ctx->a); + talloc_free(ctx); +} + +static struct mp_image *mp_image_from_amf_surface(struct mp_filter *f, AMFSurface *src) +{ + struct priv *p = f->priv; + struct amf_priv *a = p->amf; + + mp_require(ICALL(GetMemoryType, src) == AMF_MEMORY_DX11); + + AMFPlane *plane = ICALL(GetPlane, src, AMF_PLANE_PACKED); + if (!plane) { + MP_ERR(f, "Failed to get plane from AMF surface.\n"); + return NULL; + } + + ID3D11Texture2D *amf_tex = ICALL(GetNative, plane); + if (!amf_tex) { + MP_ERR(f, "Failed to get D3D11 texture from AMF plane.\n"); + return NULL; + } + + ID3D11Device *amf_dev = ICALL(GetDX11Device, a->ctx, AMF_DX11_1); + mp_require(amf_dev); + + ID3D11DeviceContext *ctx = NULL; + ID3D11Device_GetImmediateContext(amf_dev, &ctx); + mp_require(ctx); + ID3D11DeviceContext_Flush(ctx); + SAFE_RELEASE(ctx); + + IDXGIResource* amf_res; + HRESULT hr = ID3D11Texture2D_QueryInterface(amf_tex, &IID_IDXGIResource, (void **)&amf_res); + if (FAILED(hr)) { + MP_ERR(f, "Failed to query IDXGIResource from AMF texture: %#lx\n", hr); + return NULL; + } + + HANDLE amf_shared; + hr = IDXGIResource_GetSharedHandle(amf_res, &amf_shared); + if (FAILED(hr)) { + MP_ERR(f, "Failed to get shared handle from IDXGIResource: %#lx\n", hr); + SAFE_RELEASE(amf_res); + return NULL; + } + + ID3D11Texture2D *tex; + hr = ID3D11Device_OpenSharedResource(p->d3d_dev, amf_shared, &IID_ID3D11Texture2D, (void **)&tex); + SAFE_RELEASE(amf_res); + if (FAILED(hr)) { + MP_ERR(f, "Failed to open shared resource: %#lx\n", hr); + return NULL; + } + + // At this point, we technically own a reference to a texture, but we have to + // keep also AMFSurface alive, as the implementation seems to reuse the same + // underlying texture for future frames. + struct amf_surface_free_ctx *free_ctx = talloc(NULL, struct amf_surface_free_ctx); + free_ctx->a = a; + free_ctx->surface = src; + free_ctx->tex = tex; + struct mp_image *out = mp_image_new_custom_ref(p->ref_image, (void *)free_ctx, free_amf_surface); + if (!out) { + MP_ERR(f, "Unexpected error when creating mp_image.\n"); + free_amf_surface(free_ctx); + return NULL; + } + mp_image_copy_attributes(out, p->ref_image); + // hwctx is not copied by mp_image_copy_attributes + out->hwctx = av_buffer_ref(p->ref_image->hwctx); + out->planes[0] = (uint8_t *)tex; + out->planes[1] = 0; + out->pts = ICALL(GetPts, src) / (double)AMF_SECOND; + + // Keep context alive until all returned images are freed. + mp_rc_ref(&a->ref_count); + + return out; +} + +static int copy_tex(ID3D11DeviceContext *ctx, ID3D11Device *dev, + ID3D11Texture2D **dst, ID3D11Texture2D *src, int index) +{ + D3D11_TEXTURE2D_DESC desc; + ID3D11Texture2D_GetDesc(src, &desc); + mp_require(desc.MipLevels == 1); + + if (!*dst) { + desc.ArraySize = 1; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET; + desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED; + + HRESULT hr = ID3D11Device_CreateTexture2D(dev, &desc, NULL, dst); + if (FAILED(hr)) + return -1; + } + + const D3D11_BOX srcBox = {0, 0, 0, desc.Width, desc.Height, 1}; + ID3D11DeviceContext_CopySubresourceRegion(ctx, (ID3D11Resource *)*dst, + 0, 0, 0, 0, (ID3D11Resource *)src, + index, &srcBox); + return 0; +} + +static AMF_STD_CALL void amf_surface_dtor(AMFSurfaceObserver *observer, AMFSurface *surface) +{ + talloc_free(observer); +} + +static const AMFSurfaceObserver amf_dtor = { + .pVtbl = &(AMFSurfaceObserverVtbl){ + .OnSurfaceDataRelease = amf_surface_dtor, + } +}; + +static AMFSurface *mp_image_to_amf_surface(struct mp_filter *f, struct mp_image *src) +{ + struct priv *p = f->priv; + struct amf_priv *a = p->amf; + IDXGIResource* resource = NULL; + ID3D11Texture2D *amf_tex = NULL; + AMFSurfaceObserver *dtor = talloc_dup(NULL, (AMFSurfaceObserver *)&amf_dtor); + talloc_steal(dtor, src); // release mp_image when no longer needed + + if (src->imgfmt != IMGFMT_D3D11) { + MP_ERR(f, "AMF FRC only supports D3D11 input. " + "Use `--hwdec=d3d11va` or `--vf-pre=format=d3d11` to upload the data.\n"); + goto err; + } + + ID3D11Texture2D *tex = (void *)src->planes[0]; + amf_int index = (intptr_t)src->planes[1]; + + ID3D11DeviceContext *ctx = NULL; + ID3D11Device_GetImmediateContext(p->d3d_dev, &ctx); + mp_require(ctx); + // Textures between AMF and our D3D11 device are shared using shared handles + // `D3D11_RESOURCE_MISC_SHARED`, which is also set on textures returned + // from AMF. These textures are implicitly synchronized, documentation states + // that after a shared texture is updated, `ID3D11DeviceContext::Flush()` + // must be called on the updating device. Flushing the command queue is + // sufficient for the driver to synchronize access to the shared resource. + // We intentionally do not use more explicit synchronization mechanisms such + // as `IDXGIKeyedMutex` or `D3D11_RESOURCE_MISC_SHARED_NTHANDLE`, as they + // are not supported by AMF, nor D3D11VA decoder and would require additional + // intermediate copies. + // However, resources created by a D3D11VA device are not implicitly + // synchronized, so we perform a GPU-GPU copy before sharing the texture + // with AMF. This ensures the video decoder has finished writing to the + // texture. A copy is faster than synchronizing through the CPU, without copies. + copy_tex(ctx, p->d3d_dev, &p->input_tex, tex, index); + tex = p->input_tex; + index = 0; + ID3D11DeviceContext_Flush(ctx); + SAFE_RELEASE(ctx); + + HRESULT hr = ID3D11Texture2D_QueryInterface(tex, &IID_IDXGIResource, (void **)&resource); + if (FAILED(hr)) { + MP_ERR(f, "Failed to query IDXGIResource from texture: %#lx\n", hr); + goto err; + } + + HANDLE shared; + hr = IDXGIResource_GetSharedHandle(resource, &shared); + if (FAILED(hr)) { + MP_ERR(f, "Failed to get shared handle from IDXGIResource: %#lx\n", hr); + goto err; + } + + ID3D11Device *amf_dev = ICALL(GetDX11Device, a->ctx, AMF_DX11_1); // not refcounted + hr = ID3D11Device_OpenSharedResource(amf_dev, shared, &IID_ID3D11Texture2D, (void **)&amf_tex); + SAFE_RELEASE(resource); + if (FAILED(hr)) { + MP_ERR(f, "Failed to open shared resource: %#lx\n", hr); + goto err; + } + + AMFSurface *out; + AMF_RESULT res = ICALL(CreateSurfaceFromDX11Native, a->ctx, amf_tex, &out, dtor); + if (res != AMF_OK) { + MP_ERR(f, "Failed to create AMF surface from D3D11: %d\n", res); + goto err; + } + + // Copied from + static const GUID AMFTextureArrayIndexGUID = {0x28115527, 0xe7c3, 0x4b66, {0x99, 0xd3, 0x4f, 0x2a, 0xe6, 0xb4, 0x7f, 0xaf}}; + ID3D11Resource_SetPrivateData(amf_tex, &AMFTextureArrayIndexGUID, sizeof(index), &index); + SAFE_RELEASE(amf_tex); + + if (src->pts != MP_NOPTS_VALUE) + ICALL(SetPts, out, (amf_pts)(src->pts * AMF_SECOND)); + else + MP_WARN(f, "Warning: source image has no PTS.\n"); + + return out; + +err: + SAFE_RELEASE(resource); + SAFE_RELEASE(amf_tex); + talloc_free(dtor); + return NULL; +} + +static void process(struct mp_filter *f) +{ + struct priv *p = f->priv; + struct amf_priv *a = p->amf; + + if (!mp_pin_in_needs_data(f->ppins[1])) + return; + + if (!p->has_frame && !mp_pin_out_request_data(f->ppins[0])) + return; + + // AMF_REPEAT requested or draining, we likely have a frame to output + if (p->has_frame) + goto read_out; + + struct mp_frame frame = mp_pin_out_read(f->ppins[0]); + + if (frame.type == MP_FRAME_NONE) { + MP_WARN(f, "Needs a frame, but got MP_FRAME_NONE...\n"); + return; + } + + if (frame.type == MP_FRAME_EOF) { + ICALL(Drain, a->frc); + goto read_out; + } + + if (frame.type != MP_FRAME_VIDEO) { + MP_ERR(f, "Unexpected frame type: %d\n", frame.type); + mp_frame_unref(&frame); + mp_filter_internal_mark_failed(f); + return; + } + + struct mp_image *in = frame.data; + mp_image_unrefp(&p->ref_image); + p->ref_image = mp_image_new_ref(in); + + if (!mp_image_params_static_equal(&in->params, &p->params)) { + MP_VERBOSE(f, "Input image parameters changed, reinitializing...\n"); + ICALL(Terminate, a->frc); + p->params = in->params; + if (init_frc(f, in, false) < 0) { + MP_ERR(f, "FRC init failed\n"); + mp_filter_internal_mark_failed(f); + return; + } + } + + AMFSurface *surface = mp_image_to_amf_surface(f, in); + if (!surface) { + MP_WARN(f, "Failed to create AMF surface\n"); + mp_filter_internal_mark_failed(f); + return; + } + + AMF_RESULT res = ICALL(SubmitInput, a->frc, (AMFData *)surface); + // This shouldn't happen according to docs, but it does... + if (res == AMF_EOF) { + MP_ERR(f, "FRC SubmitInput returned AMF_EOF, reinitializing...\n"); + if (init_frc(f, p->ref_image, true) < 0) { + MP_ERR(f, "FRC reinit failed\n"); + mp_filter_internal_mark_failed(f); + return; + } + res = ICALL(SubmitInput, a->frc, (AMFData *)surface); + } + + ICALL(Release, surface); + + if (res == AMF_INPUT_FULL) { + MP_WARN(f, "FRC input queue full...\n"); + mp_pin_out_unread(f->ppins[0], frame); + return; + } + + if (res != AMF_OK && res != AMF_EOF) { + MP_WARN(f, "FRC SubmitInput failed: %d\n", res); + mp_filter_internal_mark_failed(f); + return; + } + +read_out:; + AMFSurface *amf_out = NULL; + res = ICALL(QueryOutput, a->frc, (AMFData **)&amf_out); + + if (res == AMF_EOF) { + p->has_frame = false; + mp_pin_in_write(f->ppins[1], MAKE_FRAME(MP_FRAME_EOF, NULL)); + return; + } + + if (res != AMF_OK && res != AMF_REPEAT) { + MP_WARN(f, "FRC QueryOutput returned: %d\n", res); + mp_filter_internal_mark_failed(f); + return; + } + + if (!amf_out && res == AMF_REPEAT) { + MP_WARN(f, "FRC QueryOutput returned NULL surface with AMF_REPEAT\n"); + mp_filter_internal_mark_failed(f); + return; + } + + struct mp_image *out = mp_image_from_amf_surface(f, amf_out); + if (!out) { + MP_WARN(f, "Failed to create mp_image from AMF surface\n"); + ICALL(Release, amf_out); + mp_filter_internal_mark_failed(f); + return; + } + + if (res == AMF_OK) { + p->has_frame = false; + mp_image_unrefp(&p->ref_image); + } else if (res == AMF_REPEAT) { + p->has_frame = true; + } else { + MP_ASSERT_UNREACHABLE(); + } + + mp_pin_in_write(f->ppins[1], MAKE_FRAME(MP_FRAME_VIDEO, out)); +} + +static const struct mp_filter_info vf_amf_frc_filter = { + .name = "amf_frc", + .process = process, + .reset = flush, + .destroy = destroy, + .priv_size = sizeof(struct priv), +}; + +static struct mp_filter *vf_amf_frc_create(struct mp_filter *parent, + void *options) +{ + struct mp_filter *f = mp_filter_create(parent, &vf_amf_frc_filter); + if (!f) { + talloc_free(options); + return NULL; + } + + mp_filter_add_pin(f, MP_PIN_IN, "in"); + mp_filter_add_pin(f, MP_PIN_OUT, "out"); + + struct priv *p = f->priv; + p->opts = talloc_steal(p, options); + p->amf = talloc_zero(NULL, struct amf_priv); + mp_rc_init(&p->amf->ref_count); + + if (load_amf_library(p->amf) < 0) { + MP_ERR(f, "Failed to load AMF library\n"); + goto fail; + } + + if (create_amf_context(f) < 0) + goto fail; + + if (create_amf_frc(f) < 0) + goto fail; + + return f; + +fail: + talloc_free(f); + return NULL; +} + +#define OPT_BASE_STRUCT struct opts +static const m_option_t vf_opts_fields[] = { + {"profile", OPT_CHOICE(profile, + {"auto", -1}, + {"low", FRC_PROFILE_LOW}, + {"high", FRC_PROFILE_HIGH}, + {"super", FRC_PROFILE_SUPER})}, + {"mv-search", OPT_CHOICE(mv_search_mode, + {"native", FRC_MV_SEARCH_NATIVE}, + {"performance", FRC_MV_SEARCH_PERFORMANCE})}, + {"fallback", OPT_BOOL(fallback_blend)}, + {"indicator", OPT_BOOL(indicator)}, + {"future-frame", OPT_BOOL(use_future_frame)}, + {0} +}; + +const struct mp_user_filter_entry vf_amf_frc = { + .desc = { + .description = "AMD Frame Rate Conversion filter", + .name = "amf_frc", + .priv_size = sizeof(OPT_BASE_STRUCT), + .priv_defaults = &(const OPT_BASE_STRUCT) { + .profile = -1, + .mv_search_mode = FRC_MV_SEARCH_NATIVE, + .use_future_frame = true, + }, + .options = vf_opts_fields, + }, + .create = vf_amf_frc_create, +};