From 271ea405ba6d9e093c044ac5ec2a5651c401fa6a Mon Sep 17 00:00:00 2001
From: Trystan Mata <trystan.mata@tytanium.xyz>
Date: Sat, 21 May 2022 16:26:56 +0200
Subject: [PATCH] avcodec/mfenc: Dynamically load MediaFoundation library

Allows non-UWP builds of FFmpeg with MediaFoundation to work on
N editions of Windows which are without MediaFoundation by default.

LoadLibrary is not available on UWP target.

This commit adresses https://trac.ffmpeg.org/ticket/9788

Signed-off-by: Trystan Mata <trystan.mata@tytanium.xyz>
---
 configure             |   5 +-
 libavcodec/mf_utils.c | 106 ++++++++++++++++++++++++++++++++++--------
 libavcodec/mf_utils.h |  19 ++++++--
 libavcodec/mfenc.c    |  23 ++++++++-
 4 files changed, 129 insertions(+), 24 deletions(-)

diff --git a/configure b/configure
index f115b21064..432a0d163d 100755
--- a/configure
+++ b/configure
@@ -3130,7 +3130,6 @@ wmv3_vdpau_hwaccel_select="vc1_vdpau_hwaccel"
 
 # hardware-accelerated codecs
 mediafoundation_deps="mftransform_h MFCreateAlignedMemoryBuffer"
-mediafoundation_extralibs="-lmfplat -lmfuuid -lole32 -lstrmiids"
 omx_deps="libdl pthreads"
 omx_rpi_select="omx"
 qsv_deps="libmfx"
@@ -6876,6 +6875,10 @@ test_cpp <<EOF && enable uwp && d3d11va_extralibs="-ldxgi -ld3d11"
 #endif
 EOF
 
+# mediafoundation requires linking directly to mfplat and mfuuid if building
+# for uwp target
+enabled uwp && mediafoundation_extralibs="-lmfplat -lmfuuid -lole32 -lstrmiids" || mediafoundation_extralibs="-lole32 -lstrmiids"
+
 enabled libdrm &&
     check_pkg_config libdrm_getfb2 libdrm "xf86drmMode.h" drmModeGetFB2
 
diff --git a/libavcodec/mf_utils.c b/libavcodec/mf_utils.c
index eeabd0ce0b..b8756cccea 100644
--- a/libavcodec/mf_utils.c
+++ b/libavcodec/mf_utils.c
@@ -24,6 +24,7 @@
 
 #include "mf_utils.h"
 #include "libavutil/pixdesc.h"
+#include "compat/w32dlfcn.h"
 
 HRESULT ff_MFGetAttributeSize(IMFAttributes *pattr, REFGUID guid,
                               UINT32 *pw, UINT32 *ph)
@@ -47,9 +48,83 @@ HRESULT ff_MFSetAttributeSize(IMFAttributes *pattr, REFGUID guid,
 #define ff_MFSetAttributeRatio ff_MFSetAttributeSize
 #define ff_MFGetAttributeRatio ff_MFGetAttributeSize
 
-// MFTEnumEx was missing from mingw-w64's mfplat import library until
-// mingw-w64 v6.0.0, thus wrap it and load it using GetProcAddress.
-// It's also missing in Windows Vista's mfplat.dll.
+// Windows N editions does not provide MediaFoundation by default.
+// So to avoid DLL loading error, MediaFoundation is dynamically loaded except
+// on UWP build since LoadLibrary is not available on it.
+#if !HAVE_UWP
+static HMODULE mf_library = NULL;
+
+int ff_mf_load_library(void *log)
+{
+    mf_library = win32_dlopen("mfplat.dll");
+
+    if (!mf_library) {
+        av_log(log, AV_LOG_ERROR, "DLL mfplat.dll failed to open\n");
+        return AVERROR_UNKNOWN;
+    }
+
+    return 0;
+}
+
+void ff_mf_unload_library(void)
+{
+    FreeLibrary(mf_library);
+    mf_library = NULL;
+}
+
+#define GET_MF_FUNCTION(ptr, func_name) \
+    ptr = (void *)GetProcAddress(mf_library, #func_name ""); \
+    if (!ptr) \
+        return E_FAIL;
+#else
+// In UWP (which lacks LoadLibrary), just link directly against
+// the functions - this requires building with new/complete enough
+// import libraries.
+#define GET_MF_FUNCTION(ptr, func_name) \
+    ptr = func_name; \
+    if (!ptr) \
+        return E_FAIL;
+#endif
+
+HRESULT ff_MFStartup(ULONG Version, DWORD dwFlags)
+{
+    HRESULT (WINAPI *MFStartup_ptr)(ULONG Version, DWORD dwFlags) = NULL;
+    GET_MF_FUNCTION(MFStartup_ptr, MFStartup);
+    return MFStartup_ptr(Version, dwFlags);
+}
+
+HRESULT ff_MFShutdown(void)
+{
+    HRESULT (WINAPI *MFShutdown_ptr)(void) = NULL;
+    GET_MF_FUNCTION(MFShutdown_ptr, MFShutdown);
+    return MFShutdown_ptr();
+}
+
+HRESULT ff_MFCreateSample(IMFSample **ppIMFSample)
+{
+    HRESULT (WINAPI *MFCreateSample_ptr)(IMFSample **ppIMFSample) = NULL;
+    GET_MF_FUNCTION(MFCreateSample_ptr, MFCreateSample);
+    return MFCreateSample_ptr(ppIMFSample);
+}
+
+HRESULT ff_MFCreateAlignedMemoryBuffer(DWORD cbMaxLength, DWORD cbAligment,
+                                       IMFMediaBuffer **ppBuffer)
+{
+    HRESULT (WINAPI *MFCreateAlignedMemoryBuffer_ptr)(DWORD cbMaxLength,
+                                                      DWORD cbAligment,
+                                                      IMFMediaBuffer **ppBuffer) = NULL;
+    GET_MF_FUNCTION(MFCreateAlignedMemoryBuffer_ptr, MFCreateAlignedMemoryBuffer);
+    return MFCreateAlignedMemoryBuffer_ptr(cbMaxLength, cbAligment, ppBuffer);
+}
+
+HRESULT ff_MFCreateMediaType(IMFMediaType **ppMFType)
+{
+    HRESULT (WINAPI *MFCreateMediaType_ptr)(IMFMediaType **ppMFType) = NULL;
+    GET_MF_FUNCTION(MFCreateMediaType_ptr, MFCreateMediaType);
+    return MFCreateMediaType_ptr(ppMFType);
+}
+
+// MFTEnumEx is missing in Windows Vista's mfplat.dll.
 HRESULT ff_MFTEnumEx(GUID guidCategory, UINT32 Flags,
                      const MFT_REGISTER_TYPE_INFO *pInputType,
                      const MFT_REGISTER_TYPE_INFO *pOutputType,
@@ -60,18 +135,7 @@ HRESULT ff_MFTEnumEx(GUID guidCategory, UINT32 Flags,
                                     const MFT_REGISTER_TYPE_INFO *pOutputType,
                                     IMFActivate ***pppMFTActivate,
                                     UINT32 *pnumMFTActivate) = NULL;
-#if !HAVE_UWP
-    HANDLE lib = GetModuleHandleW(L"mfplat.dll");
-    if (lib)
-        MFTEnumEx_ptr = (void *)GetProcAddress(lib, "MFTEnumEx");
-#else
-    // In UWP (which lacks GetModuleHandle), just link directly against
-    // the functions - this requires building with new/complete enough
-    // import libraries.
-    MFTEnumEx_ptr = MFTEnumEx;
-#endif
-    if (!MFTEnumEx_ptr)
-        return E_FAIL;
+    GET_MF_FUNCTION(MFTEnumEx_ptr, MFTEnumEx);
     return MFTEnumEx_ptr(guidCategory,
                          Flags,
                          pInputType,
@@ -112,13 +176,13 @@ IMFSample *ff_create_memory_sample(void *fill_data, size_t size, size_t align)
     IMFSample *sample;
     IMFMediaBuffer *buffer;
 
-    hr = MFCreateSample(&sample);
+    hr = ff_MFCreateSample(&sample);
     if (FAILED(hr))
         return NULL;
 
     align = FFMAX(align, 16); // 16 is "recommended", even if not required
 
-    hr = MFCreateAlignedMemoryBuffer(size, align - 1, &buffer);
+    hr = ff_MFCreateAlignedMemoryBuffer(size, align - 1, &buffer);
     if (FAILED(hr))
         return NULL;
 
@@ -561,7 +625,7 @@ static int init_com_mf(void *log)
         return AVERROR(ENOSYS);
     }
 
-    hr = MFStartup(MF_VERSION, MFSTARTUP_FULL);
+    hr = ff_MFStartup(MF_VERSION, MFSTARTUP_FULL);
     if (FAILED(hr)) {
         av_log(log, AV_LOG_ERROR, "could not initialize MediaFoundation\n");
         CoUninitialize();
@@ -573,7 +637,7 @@ static int init_com_mf(void *log)
 
 static void uninit_com_mf(void)
 {
-    MFShutdown();
+    ff_MFShutdown();
     CoUninitialize();
 }
 
@@ -673,6 +737,10 @@ error_uninit_mf:
 
 void ff_free_mf(IMFTransform **mft)
 {
+#if !HAVE_UWP
+    if (!mf_library)
+        return;
+#endif
     if (*mft)
         IMFTransform_Release(*mft);
     *mft = NULL;
diff --git a/libavcodec/mf_utils.h b/libavcodec/mf_utils.h
index d514723c3b..6384bc2133 100644
--- a/libavcodec/mf_utils.h
+++ b/libavcodec/mf_utils.h
@@ -50,9 +50,22 @@ HRESULT ff_MFSetAttributeSize(IMFAttributes *pattr, REFGUID guid,
 #define ff_MFSetAttributeRatio ff_MFSetAttributeSize
 #define ff_MFGetAttributeRatio ff_MFGetAttributeSize
 
-// MFTEnumEx was missing from mingw-w64's mfplat import library until
-// mingw-w64 v6.0.0, thus wrap it and load it using GetProcAddress.
-// It's also missing in Windows Vista's mfplat.dll.
+// Windows N editions does not provide MediaFoundation by default.
+// So to avoid DLL loading error, MediaFoundation is dynamically loaded except
+// on UWP build since LoadLibrary is not available on it.
+#if !HAVE_UWP
+int ff_mf_load_library(void *log);
+void ff_mf_unload_library(void);
+#endif
+
+HRESULT ff_MFCreateSample(IMFSample **ppIMFSample);
+HRESULT ff_MFCreateAlignedMemoryBuffer(DWORD cbMaxLength, DWORD cbAligment,
+                                       IMFMediaBuffer **ppBuffer);
+HRESULT ff_MFStartup(ULONG Version, DWORD dwFlags);
+HRESULT ff_MFShutdown(void);
+HRESULT ff_MFCreateMediaType(IMFMediaType **ppMFType);
+
+// MFTEnumEx is missing in Windows Vista's mfplat.dll.
 HRESULT ff_MFTEnumEx(GUID guidCategory, UINT32 Flags,
                      const MFT_REGISTER_TYPE_INFO *pInputType,
                      const MFT_REGISTER_TYPE_INFO *pOutputType,
diff --git a/libavcodec/mfenc.c b/libavcodec/mfenc.c
index 280941cf2e..fbfb4534fd 100644
--- a/libavcodec/mfenc.c
+++ b/libavcodec/mfenc.c
@@ -777,7 +777,7 @@ static int mf_choose_output_type(AVCodecContext *avctx)
     if (out_type) {
         av_log(avctx, AV_LOG_VERBOSE, "picking output type %d.\n", out_type_index);
     } else {
-        hr = MFCreateMediaType(&out_type);
+        hr = ff_MFCreateMediaType(&out_type);
         if (FAILED(hr)) {
             ret = AVERROR(ENOMEM);
             goto done;
@@ -1034,7 +1034,11 @@ static int mf_create(void *log, IMFTransform **mft, const AVCodec *codec, int us
     return 0;
 }
 
+#if !HAVE_UWP
+static int mf_init_encoder(AVCodecContext *avctx)
+#else
 static int mf_init(AVCodecContext *avctx)
+#endif
 {
     MFContext *c = avctx->priv_data;
     HRESULT hr;
@@ -1134,6 +1138,10 @@ static int mf_close(AVCodecContext *avctx)
 
     ff_free_mf(&c->mft);
 
+#if !HAVE_UWP
+    ff_mf_unload_library();
+#endif
+
     av_frame_free(&c->frame);
 
     av_freep(&avctx->extradata);
@@ -1142,6 +1150,19 @@ static int mf_close(AVCodecContext *avctx)
     return 0;
 }
 
+static int mf_init(AVCodecContext *avctx)
+{
+    int ret;
+
+    if ((ret = ff_mf_load_library(avctx)) == 0) {
+           if ((ret = mf_init_encoder(avctx)) == 0) {
+                return 0;
+        }
+    }
+    mf_close(avctx);
+    return ret;
+}
+
 #define OFFSET(x) offsetof(MFContext, x)
 
 #define MF_ENCODER(MEDIATYPE, NAME, ID, OPTS, EXTRA) \
-- 
2.36.1

