SDL: camera: Windows support, through the Media Foundation API!

From 7191a97fe3b8a203aacc0336e7b3f5de49a33084 Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Wed, 31 Jan 2024 15:07:07 -0500
Subject: [PATCH] camera: Windows support, through the Media Foundation API!

---
 VisualC/SDL/SDL.vcxproj                       |   1 +
 VisualC/SDL/SDL.vcxproj.filters               |   6 +
 Xcode/SDL/SDL.xcodeproj/project.pbxproj       |  12 +
 include/build_config/SDL_build_config.h.cmake |   3 +-
 .../build_config/SDL_build_config_windows.h   |   5 +-
 src/camera/SDL_camera.c                       |  40 +-
 src/camera/SDL_syscamera.h                    |  11 +
 src/camera/dummy/SDL_camera_dummy.c           |   2 +-
 src/camera/emscripten/SDL_camera_emscripten.c |   2 -
 .../SDL_camera_mediafoundation.c              | 918 ++++++++++++++++++
 src/camera/v4l2/SDL_camera_v4l2.c             |  42 +-
 11 files changed, 998 insertions(+), 44 deletions(-)
 create mode 100644 src/camera/mediafoundation/SDL_camera_mediafoundation.c

diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj
index 2ddcdb1a6bb2c..1a07f3c2fb9e8 100644
--- a/VisualC/SDL/SDL.vcxproj
+++ b/VisualC/SDL/SDL.vcxproj
@@ -397,6 +397,7 @@
       <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
     </ClCompile>
     <ClCompile Include="..\..\src\camera\dummy\SDL_camera_dummy.c" />
+    <ClCompile Include="..\..\src\camera\mediafoundation\SDL_camera_mediafoundation.c" />
     <ClCompile Include="..\..\src\camera\SDL_camera.c" />
     <ClCompile Include="..\..\src\main\generic\SDL_sysmain_callbacks.c" />
     <ClCompile Include="..\..\src\main\SDL_main_callbacks.c" />
diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters
index 6673e9e816e2b..7c0f5190f73ab 100644
--- a/VisualC/SDL/SDL.vcxproj.filters
+++ b/VisualC/SDL/SDL.vcxproj.filters
@@ -184,6 +184,9 @@
     <Filter Include="camera\dummy">
       <UniqueIdentifier>{0000fc2700d453b3c8d79fe81e1c0000}</UniqueIdentifier>
     </Filter>
+    <Filter Include="camera\mediafoundation">
+      <UniqueIdentifier>{0000fbfe2d21e4f451142e7d0e870000}</UniqueIdentifier>
+    </Filter>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\..\include\SDL3\SDL_begin_code.h">
@@ -856,6 +859,9 @@
     <ClCompile Include="..\..\src\camera\dummy\SDL_camera_dummy.c">
       <Filter>camera\dummy</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\src\camera\mediafoundation\SDL_camera_mediafoundation.c">
+      <Filter>camera\mediafoundation</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\src\camera\SDL_camera.c">
       <Filter>camera</Filter>
     </ClCompile>
diff --git a/Xcode/SDL/SDL.xcodeproj/project.pbxproj b/Xcode/SDL/SDL.xcodeproj/project.pbxproj
index e5623fe149a7f..7ec548712d9a7 100644
--- a/Xcode/SDL/SDL.xcodeproj/project.pbxproj
+++ b/Xcode/SDL/SDL.xcodeproj/project.pbxproj
@@ -499,6 +499,7 @@
 		F3FA5A242B59ACE000FEAD97 /* yuv_rgb_lsx.h in Headers */ = {isa = PBXBuildFile; fileRef = F3FA5A1B2B59ACE000FEAD97 /* yuv_rgb_lsx.h */; };
 		F3FA5A252B59ACE000FEAD97 /* yuv_rgb_common.h in Headers */ = {isa = PBXBuildFile; fileRef = F3FA5A1C2B59ACE000FEAD97 /* yuv_rgb_common.h */; };
 		FA73671D19A540EF004122E4 /* CoreVideo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA73671C19A540EF004122E4 /* CoreVideo.framework */; platformFilters = (ios, maccatalyst, macos, tvos, watchos, ); };
+		00009F560664255CCB6C0000 /* SDL_camera_mediafoundation.c in Sources */ = {isa = PBXBuildFile; fileRef = 0000C61C247BAAAF757D0000 /* SDL_camera_mediafoundation.c */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -1027,6 +1028,7 @@
 		F59C710600D5CB5801000001 /* SDL.info */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = SDL.info; sourceTree = "<group>"; };
 		F5A2EF3900C6A39A01000001 /* BUGS.txt */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; name = BUGS.txt; path = ../../BUGS.txt; sourceTree = SOURCE_ROOT; };
 		FA73671C19A540EF004122E4 /* CoreVideo.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreVideo.framework; path = System/Library/Frameworks/CoreVideo.framework; sourceTree = SDKROOT; };
+		0000C61C247BAAAF757D0000 /* SDL_camera_mediafoundation.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = SDL_camera_mediafoundation.c; path = SDL_camera_mediafoundation.c; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -1068,6 +1070,7 @@
 				0000035D38C3899C7EFD0000 /* SDL_camera.c */,
 				00009003C7148E1126CA0000 /* SDL_camera_c.h */,
 				00005D3EB902478835E20000 /* SDL_syscamera.h */,
+				0000926F2501CA0BDD650000 /* mediafoundation */,
 			);
 			path = camera;
 			sourceTree = "<group>";
@@ -2175,6 +2178,14 @@
 			path = resources;
 			sourceTree = "<group>";
 		};
+		0000926F2501CA0BDD650000 /* mediafoundation */ = {
+			isa = PBXGroup;
+			children = (
+				0000C61C247BAAAF757D0000 /* SDL_camera_mediafoundation.c */,
+			);
+			path = mediafoundation;
+			sourceTree = "<group>";
+		};
 /* End PBXGroup section */
 
 /* Begin PBXHeadersBuildPhase section */
@@ -2754,6 +2765,7 @@
 				000098E9DAA43EF6FF7F0000 /* SDL_camera.c in Sources */,
 				00001B2471F503DD3C1B0000 /* SDL_camera_dummy.c in Sources */,
 				00002B20A48E055EB0350000 /* SDL_camera_coremedia.m in Sources */,
+				00009F560664255CCB6C0000 /* SDL_camera_mediafoundation.c in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
diff --git a/include/build_config/SDL_build_config.h.cmake b/include/build_config/SDL_build_config.h.cmake
index 9f4302a79a5f2..11c1ad716ecd0 100644
--- a/include/build_config/SDL_build_config.h.cmake
+++ b/include/build_config/SDL_build_config.h.cmake
@@ -472,7 +472,8 @@
 #cmakedefine SDL_CAMERA_DRIVER_V4L2 @SDL_CAMERA_DRIVER_V4L2@
 #cmakedefine SDL_CAMERA_DRIVER_COREMEDIA @SDL_CAMERA_DRIVER_COREMEDIA@
 #cmakedefine SDL_CAMERA_DRIVER_ANDROID @SDL_CAMERA_DRIVER_ANDROID@
-#cmakedefine SDL_CAMERA_DRIVER_EMSCRIPTEN @SDL_CAMERA_DRIVER_EMSCRIPTEND@
+#cmakedefine SDL_CAMERA_DRIVER_EMSCRIPTEN @SDL_CAMERA_DRIVER_EMSCRIPTEN@
+#cmakedefine SDL_CAMERA_DRIVER_MEDIAFOUNDATION @SDL_CAMERA_DRIVER_MEDIAFOUNDATION@
 
 /* Enable misc subsystem */
 #cmakedefine SDL_MISC_DUMMY @SDL_MISC_DUMMY@
diff --git a/include/build_config/SDL_build_config_windows.h b/include/build_config/SDL_build_config_windows.h
index 066888fbd7bc7..a8d2dc4fd49c0 100644
--- a/include/build_config/SDL_build_config_windows.h
+++ b/include/build_config/SDL_build_config_windows.h
@@ -311,7 +311,8 @@ typedef unsigned int uintptr_t;
 /* Enable filesystem support */
 #define SDL_FILESYSTEM_WINDOWS  1
 
-/* Enable the camera driver (src/camera/dummy/\*.c) */  /* !!! FIXME */
-#define SDL_CAMERA_DRIVER_DUMMY  1
+/* Enable the camera driver */
+#define SDL_CAMERA_DRIVER_MEDIAFOUNDATION 1
+#define SDL_CAMERA_DRIVER_DUMMY 1
 
 #endif /* SDL_build_config_windows_h_ */
diff --git a/src/camera/SDL_camera.c b/src/camera/SDL_camera.c
index 01179dc44f97b..486b5001361e3 100644
--- a/src/camera/SDL_camera.c
+++ b/src/camera/SDL_camera.c
@@ -43,6 +43,9 @@ static const CameraBootStrap *const bootstrap[] = {
 #ifdef SDL_CAMERA_DRIVER_EMSCRIPTEN
     &EMSCRIPTENCAMERA_bootstrap,
 #endif
+#ifdef SDL_CAMERA_DRIVER_MEDIAFOUNDATION
+    &MEDIAFOUNDATION_bootstrap,
+#endif
 #ifdef SDL_CAMERA_DRIVER_DUMMY
     &DUMMYCAMERA_bootstrap,
 #endif
@@ -70,6 +73,32 @@ const char *SDL_GetCurrentCameraDriver(void)
     return camera_driver.name;
 }
 
+int SDL_AddCameraFormat(CameraFormatAddData *data, Uint32 fmt, int w, int h, int interval_numerator, int interval_denominator)
+{
+    SDL_assert(data != NULL);
+    if (data->allocated_specs <= data->num_specs) {
+        const int newalloc = data->allocated_specs ? (data->allocated_specs * 2) : 16;
+        void *ptr = SDL_realloc(data->specs, sizeof (SDL_CameraSpec) * newalloc);
+        if (!ptr) {
+            return -1;
+        }
+        data->specs = (SDL_CameraSpec *) ptr;
+        data->allocated_specs = newalloc;
+    }
+
+    SDL_CameraSpec *spec = &data->specs[data->num_specs];
+    spec->format = fmt;
+    spec->width = w;
+    spec->height = h;
+    spec->interval_numerator = interval_numerator;
+    spec->interval_denominator = interval_denominator;
+
+    data->num_specs++;
+
+    return 0;
+}
+
+
 static void ClosePhysicalCameraDevice(SDL_CameraDevice *device)
 {
     if (!device) {
@@ -610,10 +639,13 @@ SDL_bool SDL_CameraThreadIterate(SDL_CameraDevice *device)
 
     if (rc == 1) {  // new frame acquired!
         #if DEBUG_CAMERA
-        SDL_Log("CAMERA: New frame available!");
+        SDL_Log("CAMERA: New frame available! pixels=%p pitch=%d", device->acquire_surface->pixels, device->acquire_surface->pitch);
         #endif
 
         if (device->drop_frames > 0) {
+            #if DEBUG_CAMERA
+            SDL_Log("CAMERA: Dropping an initial frame");
+            #endif
             device->drop_frames--;
             camera_driver.impl.ReleaseFrame(device, device->acquire_surface);
             device->acquire_surface->pixels = NULL;
@@ -662,9 +694,15 @@ SDL_bool SDL_CameraThreadIterate(SDL_CameraDevice *device)
     } else if (acquired) {  // we have a new frame, scale/convert if necessary and queue it for the app!
         SDL_assert(slist != NULL);
         if (!device->needs_scaling && !device->needs_conversion) {  // no conversion needed? Just move the pointer/pitch into the output surface.
+            #if DEBUG_CAMERA
+            SDL_Log("CAMERA: Frame is going through without conversion!");
+            #endif
             output_surface->pixels = acquired->pixels;
             output_surface->pitch = acquired->pitch;
         } else {  // convert/scale into a different surface.
+            #if DEBUG_CAMERA
+            SDL_Log("CAMERA: Frame is getting converted!");
+            #endif
             SDL_Surface *srcsurf = acquired;
             if (device->needs_scaling == -1) {  // downscaling? Do it first.  -1: downscale, 0: no scaling, 1: upscale
                 SDL_Surface *dstsurf = device->needs_conversion ? device->conversion_surface : output_surface;
diff --git a/src/camera/SDL_syscamera.h b/src/camera/SDL_syscamera.h
index e6b204771a441..b99239ec51d72 100644
--- a/src/camera/SDL_syscamera.h
+++ b/src/camera/SDL_syscamera.h
@@ -58,6 +58,16 @@ extern void SDL_CameraThreadSetup(SDL_CameraDevice *device);
 extern SDL_bool SDL_CameraThreadIterate(SDL_CameraDevice *device);
 extern void SDL_CameraThreadShutdown(SDL_CameraDevice *device);
 
+// common utility functionality to gather up camera specs. Not required!
+typedef struct CameraFormatAddData
+{
+    SDL_CameraSpec *specs;
+    int num_specs;
+    int allocated_specs;
+} CameraFormatAddData;
+
+int SDL_AddCameraFormat(CameraFormatAddData *data, Uint32 fmt, int w, int h, int interval_numerator, int interval_denominator);
+
 typedef struct SurfaceList
 {
     SDL_Surface *surface;
@@ -189,5 +199,6 @@ extern CameraBootStrap V4L2_bootstrap;
 extern CameraBootStrap COREMEDIA_bootstrap;
 extern CameraBootStrap ANDROIDCAMERA_bootstrap;
 extern CameraBootStrap EMSCRIPTENCAMERA_bootstrap;
+extern CameraBootStrap MEDIAFOUNDATION_bootstrap;
 
 #endif // SDL_syscamera_h_
diff --git a/src/camera/dummy/SDL_camera_dummy.c b/src/camera/dummy/SDL_camera_dummy.c
index 1dcdd295858b3..b93eecbbe1824 100644
--- a/src/camera/dummy/SDL_camera_dummy.c
+++ b/src/camera/dummy/SDL_camera_dummy.c
@@ -77,4 +77,4 @@ CameraBootStrap DUMMYCAMERA_bootstrap = {
     "dummy", "SDL dummy camera driver", DUMMYCAMERA_Init, SDL_TRUE
 };
 
-#endif
+#endif  // SDL_CAMERA_DRIVER_DUMMY
diff --git a/src/camera/emscripten/SDL_camera_emscripten.c b/src/camera/emscripten/SDL_camera_emscripten.c
index 5e74cb928a535..45be949692181 100644
--- a/src/camera/emscripten/SDL_camera_emscripten.c
+++ b/src/camera/emscripten/SDL_camera_emscripten.c
@@ -79,8 +79,6 @@ static int EMSCRIPTENCAMERA_AcquireFrame(SDL_CameraDevice *device, SDL_Surface *
 static void EMSCRIPTENCAMERA_ReleaseFrame(SDL_CameraDevice *device, SDL_Surface *frame)
 {
     SDL_free(frame->pixels);
-    frame->pixels = NULL;
-    frame->pitch = 0;
 }
 
 static void EMSCRIPTENCAMERA_CloseDevice(SDL_CameraDevice *device)
diff --git a/src/camera/mediafoundation/SDL_camera_mediafoundation.c b/src/camera/mediafoundation/SDL_camera_mediafoundation.c
new file mode 100644
index 0000000000000..960ab1e543a02
--- /dev/null
+++ b/src/camera/mediafoundation/SDL_camera_mediafoundation.c
@@ -0,0 +1,918 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+#include "SDL_internal.h"
+
+// the Windows Media Foundation API
+
+#ifdef SDL_CAMERA_DRIVER_MEDIAFOUNDATION
+
+#define COBJMACROS
+
+// this seems to be a bug in mfidl.h, just define this to avoid the problem section.
+#define __IMFVideoProcessorControl3_INTERFACE_DEFINED__
+
+#include "../../core/windows/SDL_windows.h"
+
+#include <mfapi.h>
+#include <mfidl.h>
+#include <mfreadwrite.h>
+
+#include "../SDL_syscamera.h"
+#include "../SDL_camera_c.h"
+
+static const IID SDL_IID_IMFMediaSource = { 0x279a808d, 0xaec7, 0x40c8, { 0x9c, 0x6b, 0xa6, 0xb4, 0x92, 0xc7, 0x8a, 0x66 } };
+static const IID SDL_IID_IMF2DBuffer = { 0x7dc9d5f9, 0x9ed9, 0x44ec, { 0x9b, 0xbf, 0x06, 0x00, 0xbb, 0x58, 0x9f, 0xbb } };
+static const IID SDL_IID_IMF2DBuffer2 = { 0x33ae5ea6, 0x4316, 0x436f, { 0x8d, 0xdd, 0xd7, 0x3d, 0x22, 0xf8, 0x29, 0xec } };
+static const GUID SDL_MF_MT_DEFAULT_STRIDE = { 0x644b4e48, 0x1e02, 0x4516, { 0xb0, 0xeb, 0xc0, 0x1c, 0xa9, 0xd4, 0x9a, 0xc6 } };
+static const GUID SDL_MF_MT_MAJOR_TYPE = { 0x48eba18e, 0xf8c9, 0x4687, { 0xbf, 0x11, 0x0a, 0x74, 0xc9, 0xf9, 0x6a, 0x8f } };
+static const GUID SDL_MF_MT_SUBTYPE = { 0xf7e34c9a, 0x42e8, 0x4714, { 0xb7, 0x4b, 0xcb, 0x29, 0xd7, 0x2c, 0x35, 0xe5 } };
+static const GUID SDL_MF_MT_FRAME_SIZE = { 0x1652c33d, 0xd6b2, 0x4012, { 0xb8, 0x34, 0x72, 0x03, 0x08, 0x49, 0xa3, 0x7d } };
+static const GUID SDL_MF_MT_FRAME_RATE = { 0xc459a2e8, 0x3d2c, 0x4e44, { 0xb1, 0x32, 0xfe, 0xe5, 0x15, 0x6c, 0x7b, 0xb0 } };
+static const GUID SDL_MFMediaType_Video = { 0x73646976, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 } };
+
+#define SDL_DEFINE_MEDIATYPE_GUID(name, fmt) static const GUID SDL_##name = { fmt, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }
+SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_RGB555, 24);
+SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_RGB565, 23);
+SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_RGB24, 20);
+SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_RGB32, 22);
+SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_ARGB32, 21);
+SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_A2R10G10B10, 31);
+SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_YV12, FCC('YV12'));
+SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_IYUV, FCC('IYUV'));
+SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_YUY2, FCC('YUY2'));
+SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_UYVY, FCC('UYVY'));
+SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_YVYU, FCC('YVYU'));
+SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_NV12, FCC('NV12'));
+SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_NV21, FCC('NV21'));
+#undef SDL_DEFINE_MEDIATYPE_GUID
+
+static const struct { const GUID *guid; const Uint32 sdlfmt; } fmtmappings[] = {
+    // This is not every possible format, just popular ones that SDL can reasonably handle.
+    // (and we should probably trim this list more.)
+    { &SDL_MFVideoFormat_RGB555, SDL_PIXELFORMAT_XRGB1555 },
+    { &SDL_MFVideoFormat_RGB565, SDL_PIXELFORMAT_RGB565 },
+    { &SDL_MFVideoFormat_RGB24, SDL_PIXELFORMAT_RGB24 },
+    { &SDL_MFVideoFormat_RGB32, SDL_PIXELFORMAT_XRGB8888 },
+    { &SDL_MFVideoFormat_ARGB32, SDL_PIXELFORMAT_ARGB8888 },
+    { &SDL_MFVideoFormat_A2R10G10B10, SDL_PIXELFORMAT_ARGB2101010 },
+    { &SDL_MFVideoFormat_YV12, SDL_PIXELFORMAT_YV12 },
+    { &SDL_MFVideoFormat_IYUV, SDL_PIXELFORMAT_IYUV },
+    { &SDL_MFVideoFormat_YUY2,  SDL_PIXELFORMAT_YUY2 },
+    { &SDL_MFVideoFormat_UYVY, SDL_PIXELFORMAT_UYVY },
+    { &SDL_MFVideoFormat_YVYU, SDL_PIXELFORMAT_YVYU },
+    { &SDL_MFVideoFormat_NV12, SDL_PIXELFORMAT_NV12 },
+    { &SDL_MFVideoFormat_NV21, SDL_PIXELFORMAT_NV21 }
+};
+
+static Uint32 MFVidFmtGuidToSDLFmt(const GUID *guid) {
+    for (size_t i = 0; i < SDL_arraysize(fmtmappings); i++) {
+        if (WIN_IsEqualGUID(guid, fmtmappings[i].guid)) {
+            return fmtmappings[i].sdlfmt;
+        }
+    }
+    return SDL_PIXELFORMAT_UNKNOWN;
+}
+
+static const GUID *SDLFmtToMFVidFmtGuid(Uint32 sdlfmt) {
+    for (size_t i = 0; i < SDL_arraysize(fmtmappings); i++) {
+        if (fmtmappings[i].sdlfmt == sdlfmt) {
+            return fmtmappings[i].guid;
+        }
+    }
+    return NULL;
+}
+
+
+// handle to Media Foundation libs--Vista and later!--for access to the Media Foundation API.
+
+// mf.dll ...
+static HMODULE libmf = NULL;
+typedef HRESULT(WINAPI *pfnMFEnumDeviceSources)(IMFAttributes *,IMFActivate ***,UINT32 *);
+typedef HRESULT(WINAPI *pfnMFCreateDeviceSource)(IMFAttributes  *, IMFMediaSource **);
+static pfnMFEnumDeviceSources pMFEnumDeviceSources = NULL;
+static pfnMFCreateDeviceSource pMFCreateDeviceSource = NULL;
+
+// mfplat.dll ...
+static HMODULE libmfplat = NULL;
+typedef HRESULT(WINAPI *pfnMFStartup)(ULONG, DWORD);
+typedef HRESULT(WINAPI *pfnMFShutdown)(void);
+typedef HRESULT(WINAPI *pfnMFCreateAttributes)(IMFAttributes **, UINT32);
+typedef HRESULT(WINAPI *pfnMFCreateMediaType)(IMFMediaType **);
+typedef HRESULT(WINAPI *pfnMFGetStrideForBitmapInfoHeader)(DWORD, DWORD, LONG *);
+
+static pfnMFStartup pMFStartup = NULL;
+static pfnMFShutdown pMFShutdown = NULL;
+static pfnMFCreateAttributes pMFCreateAttributes = NULL;
+static pfnMFCreateMediaType pMFCreateMediaType = NULL;
+static pfnMFGetStrideForBitmapInfoHeader pMFGetStrideForBitmapInfoHeader = NULL;
+
+// mfreadwrite.dll ...
+static HMODULE libmfreadwrite = NULL;
+typedef HRESULT(WINAPI *pfnMFCreateSourceReaderFromMediaSource)(IMFMediaSource *, IMFAttributes *, IMFSourceReader **);
+static pfnMFCreateSourceReaderFromMediaSource pMFCreateSourceReaderFromMediaSource = NULL;
+
+
+typedef struct SDL_PrivateCameraData
+{
+    IMFSourceReader *srcreader;
+    IMFSample *current_sample;
+    int pitch;
+} SDL_PrivateCameraData;
+
+static int MEDIAFOUNDATION_WaitDevice(SDL_CameraDevice *device)
+{
+    SDL_assert(device->hidden->current_sample == NULL);
+
+    IMFSourceReader *srcreader = device->hidden->srcreader;
+    IMFSample *sample = NULL;
+
+    while (!SDL_AtomicGet(&device->shutdown)) {
+        DWORD stream_flags = 0;
+        const HRESULT ret = IMFSourceReader_ReadSample(srcreader, MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, NULL, &stream_flags, NULL, &sample);
+        if (FAILED(ret)) {
+            return -1;   // ruh roh.
+        }
+
+        // we currently ignore stream_flags format changes, but my _hope_ is that IMFSourceReader is handling this and
+        // will continue to give us the explictly-specified format we requested when opening the device, though, and
+        // we don't have to manually deal with it.
+
+        if (sample != NULL) {
+            break;
+        } else if (stream_flags & (MF_SOURCE_READERF_ERROR | MF_SOURCE_READERF_ENDOFSTREAM)) {
+            return -1;  // apparently this camera has gone down.  :/
+        }
+
+        // otherwise, there was some minor burp, probably; just try again.
+    }
+
+    device->hidden->current_sample = sample;
+
+    return 0;
+}
+
+
+#if KEEP_ACQUIRED_BUFFERS_LOCKED
+
+#define PROP_SURFACE_IMFOBJS_POINTER "SDL.camera.mediafoundation.imfobjs"
+
+typedef struct SDL_IMFObjects
+{
+    IMF2DBuffer2 *buffer2d2;
+    IMF2DBuffer *buffer2d;
+    IMFMediaBuffer *buffer;
+    IMFSample *sample;
+} SDL_IMFObjects;
+
+static void SDLCALL CleanupIMF2DBuffer2(void *userdata, void *value)
+{
+    SDL_IMFObjects *objs = (SDL_IMFObjects *)value;
+    IMF2DBuffer2_Unlock2D(objs->buffer2d2);
+    IMF2DBuffer2_Release(objs->buffer2d2);
+    IMFMediaBuffer_Release(objs->buffer);
+    IMFSample_Release(objs->sample);
+    SDL_free(objs);
+}
+
+static void SDLCALL CleanupIMF2DBuffer(void *userdata, void *value)
+{
+    SDL_IMFObjects *objs = (SDL_IMFObjects *)value;
+    IMF2DBuffer_Unlock2D(objs->buffer2d);
+    IMF2DBuffer_Release(objs->buffer2d);
+    IMFMediaBuffer_Release(objs->buffer);
+    IMFSample_Release(objs->sample);
+    SDL_free(objs);
+}
+
+static void SDLCALL CleanupIMFMediaBuffer(void *userdata, void *value)
+{
+    SDL_IMFObjects *objs = (SDL_IMFObjects *)value;
+    IMFMediaBuffer_Unlock(objs->buffer);
+    IMFMediaBuffer_Release(objs->buffer);
+    IMFSample_Release(objs->sample);
+    SDL_free(objs);
+}
+
+static int MEDIAFOUNDATION_AcquireFrame(SDL_CameraDevice *device, SDL_Surface *frame, Uint64 *timestampNS)
+{
+    SDL_assert(device->hidden->current_sample != NULL);
+
+    int retval = 1;
+    HRESULT ret;
+    LONGLONG timestamp100NS = 0;
+    SDL_IMFObjects *objs = (SDL_IMFObjects *) SDL_calloc(1, sizeof (SDL_IMFObjects));
+
+    if (objs == NULL) {
+        return -1;
+    }
+
+    objs->sample = device->hidden->current_sample;
+    device->hidden->current_sample = NULL;
+
+    const SDL_PropertiesID surfprops = SDL_GetSurfaceProperties(frame);
+    if (!surfprops) {
+        retval = -1;
+    } else {
+        ret = IMFSample_GetSampleTime(objs->sample, &timestamp100NS);
+        if (FAILED(ret)) {
+            retval = -1;
+        }
+
+        *timestampNS = timestamp100NS * 100;  // the timestamps are in 100-nanosecond increments; move to full nanoseconds.
+    }
+
+    ret = (retval < 0) ? E_FAIL : IMFSample_ConvertToContiguousBuffer(objs->sample, &objs->buffer);  /*IMFSample_GetBufferByIndex(objs->sample, 0, &objs->buffer);*/
+
+    if (FAILED(ret)) {
+        SDL_free(objs);
+        retval = -1;
+    } else {
+        BYTE *pixels = NULL;
+        LONG pitch = 0;
+
+        if (SUCCEEDED(IMFMediaBuffer_QueryInterface(objs->buffer, &SDL_IID_IMF2DBuffer2, (void **)&objs->buffer2d2))) {
+            BYTE *bufstart = NULL;
+            DWORD buflen = 0;
+            ret = IMF2DBuffer2_Lock2DSize(objs->buffer2d2, MF2DBuffer_LockFlags_Read, &pixels, &pitch, &bufstart, &buflen);
+            if (FAILED(ret)) {
+                retval = -1;
+                CleanupIMF2DBuffer2(NULL, objs);
+            } else {
+                frame->pixels = pixels;
+                frame->pitch = (int) pitch;
+                if (SDL_SetPropertyWithCleanup(surfprops, PROP_SURFACE_IMFOBJS_POINTER, objs, CleanupIMF2DBuffer2, NULL) == -1) {
+                    CleanupIMF2DBuffer2(NULL, objs);
+                    retval = -1;
+                }
+            }
+        } else if (SUCCEEDED(IMFMediaBuffer_QueryInterface(objs->buffer, &SDL_IID_IMF2DBuffer, (void **)&objs->buffer2d))) {
+            ret = IMF2DBuffer_Lock2D(objs->buffer2d, &pixels, &pitch);
+            if (FAILED(ret)) {
+                CleanupIMF2DBuffer(NULL, objs);
+                retval = -1;
+            } else {
+                frame->pixels = pixels;
+                frame->pitch = (int) pitch;
+                if (SDL_SetPropertyWithCleanup(surfprops, PROP_SURFACE_IMFOBJS_POINTER, objs, CleanupIMF2DBuffer, NULL) == -1) {
+                    CleanupIMF2DBuffer(NULL, objs);
+                    retval = -1;
+                }
+            }
+        } else {
+            DWORD maxlen = 0, currentlen = 0;
+            ret = IMFMediaBuffer_Lock(objs->buffer, &pixels, &maxlen, &currentlen);
+            if (FAILED(ret)) {
+                CleanupIMFMediaBuffer(NULL, objs);
+                retval = -1;
+            } else {
+                pitch = (LONG) device->hidden->pitch;
+                if (pitch < 0) {  // image rows are reversed.
+                    pixels += -pitch * (frame->h - 1);
+                }
+                frame->pixels = pixels;
+                frame->pitch = (int) pitch;
+                if (SDL_SetPropertyWithCleanup(surfprops, PROP_SURFACE_IMFOBJS_POINTER, objs, CleanupIMFMediaBuffer, NULL) == -1) {
+                    CleanupIMFMediaBuffer(NULL, objs);
+                    retval = -1;
+                }
+            }
+        }
+    }
+
+    if (retval < 0) {
+        *timestampNS = 0;
+    }
+
+    return retval;
+}
+
+static void MEDIAFOUNDATION_ReleaseFrame(SDL_CameraDevice *device, SDL_Surface *frame)
+{
+    const SDL_PropertiesID surfprops = SDL_GetSurfaceProperties(frame);
+    if (surfprops) {
+        // this will release the IMFBuffer and IMFSample objects for this frame.
+        SDL_ClearProperty(surfprops, PROP_SURFACE_IMFOBJS_POINTER);
+    }
+}
+
+#else
+
+static int MEDIAFOUNDATION_AcquireFrame(SDL_CameraDevice *device, SDL_Surface *frame, Uint64 *timestampNS)
+{
+    SDL_assert(device->hidden->current_sample != NULL);
+
+    int retval = 1;
+    HRESULT ret;
+    LONGLONG timestamp100NS = 0;
+
+    IMFSample *sample = device->hidden->current_sample;
+    device->hidden->current_sample = NULL;
+
+    const SDL_PropertiesID surfprops = SDL_GetSurfaceProperties(frame);
+    if (!surfprops) {
+        retval = -1;
+    } else {
+        ret = IMFSample_GetSampleTime(sample, &timestamp100NS);
+        if (FAILED(ret)) {
+            retval = -1;
+        }
+
+        *timestampNS = timestamp100NS * 100; // the timestamps are in 100-nanosecond increments; move to full nanoseconds.
+    }
+
+    IMFMediaBuffer *buffer = NULL;
+    ret = (retval < 0) ? E_FAIL : IMFSample_ConvertToContiguousBuffer(sample, &buffer); /*IMFSample_GetBufferByIndex(sample, 0, &buffer);*/
+
+    if (FAILED(ret)) {
+        retval = -1;
+    } else {
+        IMF2DBuffer *buffer2d = NULL;
+        IMF2DBuffer2 *buffer2d2 = NULL;
+        BYTE *pixels = NULL;
+        LONG pitch = 0;
+
+        if (SUCCEEDED(IMFMediaBuffer_QueryInterface(buffer, &SDL_IID_IMF2DBuffer2, (void **)&buffer2d2))) {
+            BYTE *bufstart = NULL;
+            DWORD buflen = 0;
+            ret = IMF2DBuffer2_Lock2DSize(buffer2d2, MF2DBuffer_LockFlags_Read, &pixels, &pitch, &bufstart, &buflen);
+            if (FAILED(ret)) {
+                retval = -1;
+            } else {
+                frame->pixels = SDL_aligned_alloc(SDL_SIMDGetAlignment(), buflen);
+                if (frame->pixels == NULL) {
+                    retval = -1;
+                } else {
+                    SDL_memcpy(frame->pixels, pixels, buflen);
+                    frame->pitch = (int)pitch;
+                }
+                IMF2DBuffer2_Unlock2D(buffer2d2);
+            }
+            IMF2DBuffer2_Release(buffer2d2);
+        } else if (SUCCEEDED(IMFMediaBuffer_QueryInterface(buffer, &SDL_IID_IMF2DBuffer, (void **)&buffer2d))) {
+            ret = IMF2DBuffer_Lock2D(buffer2d, &pixels, &pitch);
+            if (FAILED(ret)) {
+                retval = -1;
+            } else {
+                BYTE *bufstart = pixels;
+                const DWORD buflen = (SDL_abs((int)pitch) * frame->w) * frame->h;
+                if (pitch < 0) { // image rows are reversed.
+                    bufstart += -pitch * (frame->h - 1);
+                }
+                frame->pixels = SDL_aligned_alloc(SDL_SIMDGetAlignment(), buflen);
+                if (frame->pixels == NULL) {
+                    retval = -1;
+                } else {
+                    SDL_memcpy(frame->pixels, bufstart, buflen);
+                    frame->pitch = (int)pitch;
+                }
+                IMF2DBuffer_Unlock2D(buffer2d);
+            }
+            IMF2DBuffer_Release(buffer2d);
+        } else {
+            DWORD maxlen = 0, currentlen = 0;
+            ret = IMFMediaBuffer_Lock(buffer, &pixels, &maxlen, &currentlen);
+            if (FAILED(ret)) {
+                retval = -1;
+            } else {
+                BYTE *bufstart = pixels;
+                pitch = (LONG)device->hidden->pitch;
+                const DWORD buflen = (SDL_abs((int)pitch) * frame->w) * frame->h;
+                if (pitch < 0) { // image rows are reversed.
+                    bufstart += -pitch * (frame->h - 1);
+                }
+                frame->pixels = SDL_aligned_alloc(SDL_SIMDGetAlignment(), buflen);
+                if (frame->pixels == NULL) {
+                    retval = -1;
+                } else {
+                    SDL_memcpy(frame->pixels, bufstart, buflen);
+                    frame->pitch = (int)pitch;
+                }
+                IMFMediaBuffer_Unlock(buffer);
+            }
+        }
+        IMFMediaBuffer_Release(buffer);
+    }
+
+    IMFSample_Release(sample);
+
+    if (retval < 0) {
+        *timestampNS = 0;
+    }
+
+    return retval;
+}
+
+static void MEDIAFOUNDATION_ReleaseFrame(SDL_CameraDevice *device, SDL_Surface *frame)
+{
+    SDL_aligned_free(frame->pixels);
+}
+
+#endif
+
+static void MEDIAFOUNDATION_CloseDevice(SDL_CameraDevice *device)
+{
+    if (device && device->hidden) {
+        if (device->hidden->srcreader) {
+            IMFSourceReader_Release(device->hidden->srcreader);
+        }
+        if (device->hidden->current_sample) {
+            IMFSample_Release(device->hidden->current_sample);
+        }
+        SDL_free(device->hidden);
+        device->hidden = NULL;
+    }
+}
+
+// this function is from https://learn.microsoft.com/en-us/windows/win32/medfound/uncompressed-video-buffers
+static HRESULT GetDefaultStride(IMFMediaType *pType, LONG *plStride)
+{
+    LONG lStride = 0;
+
+    // Try to get the default stride from the media type.
+    HRESULT ret = IMFMediaType_GetUINT32(pType, &SDL_MF_MT_DEFAULT_STRIDE, (UINT32*)&lStride);
+    if (FAILED(ret)) {
+        // Attribute not set. Try to calculate the default stride.
+
+        GUID subtype = GUID_NULL;
+        UINT32 width = 0;
+        UINT32 height = 0;
+        UINT64 val = 0;
+
+        // Get the subtype and the image size.
+        ret = IMFMediaType_GetGUID(pType, &SDL_MF_MT_SUBTYPE, &subtype);
+        if (FAILED(ret)) {
+            goto done;
+        }
+
+        ret = IMFMediaType_GetUINT64(pType, &SDL_MF_MT_FRAME_SIZE, &val);
+        if (FAILED(ret)) {
+            goto done;
+        }
+
+        width = (UINT32) (val >> 32);
+        height = (UINT32) val;
+
+        ret = pMFGetStrideForBitmapInfoHeader(subtype.Data1, width, &lStride);
+        if (FAILED(ret)) {
+            goto done;
+        }
+
+        // Set the attribute for later reference.
+        IMFMediaType_SetUINT32(pType, &SDL_MF_MT_DEFAULT_STRIDE, (UINT32) lStride);
+    }
+
+    if (SUCCEEDED(ret)) {
+        *plStride = lStride;
+    }
+
+done:
+    return ret;
+}
+
+
+static int MEDIAFOUNDATION_OpenDevice(SDL_CameraDevice *device, const SDL_CameraSpec *spec)
+{
+    const char *utf8symlink = (const char *) device->handle;
+    IMFAttributes *attrs = NULL;
+    LPWSTR wstrsymlink = NULL;
+    IMFMediaSource *source = NULL;
+    IMFMediaType *mediatype = NULL;
+    IMFSourceReader *srcreader

(Patch may be truncated, please check the link at the top of this post.)