Browse Source

camera: Windows support, through the Media Foundation API!

Ryan C. Gordon 1 year ago
parent
commit
7191a97fe3

+ 1 - 0
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" />

+ 6 - 0
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>

+ 12 - 0
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;
 		};

+ 2 - 1
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@

+ 3 - 2
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_ */

+ 39 - 1
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;

+ 11 - 0
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_

+ 1 - 1
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

+ 0 - 2
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)

+ 918 - 0
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 = NULL;
+    DWORD num_streams = 0;
+    LONG lstride = 0;
+    //PROPVARIANT var;
+    HRESULT ret;
+
+SDL_Log("MEDIAFOUNDATION spec format: %s", SDL_GetPixelFormatName(spec->format));
+
+    #if 0
+    IMFStreamDescriptor *streamdesc = NULL;
+    IMFPresentationDescriptor *presentdesc = NULL;
+    IMFMediaTypeHandler *handler = NULL;
+    #endif
+
+    #if DEBUG_CAMERA
+    SDL_Log("CAMERA: opening device with symlink of '%s'", utf8symlink);
+    #endif
+
+    wstrsymlink = WIN_UTF8ToString(utf8symlink);
+    if (!wstrsymlink) {
+        goto failed;
+    }
+
+    #define CHECK_HRESULT(what, r) if (FAILED(r)) { WIN_SetErrorFromHRESULT(what " failed", r); goto failed; }
+
+    ret = pMFCreateAttributes(&attrs, 1);
+    CHECK_HRESULT("MFCreateAttributes", ret);
+
+    ret = IMFAttributes_SetGUID(attrs, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
+    CHECK_HRESULT("IMFAttributes_SetGUID(srctype)", ret);
+
+    ret = IMFAttributes_SetString(attrs, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, wstrsymlink);
+    CHECK_HRESULT("IMFAttributes_SetString(symlink)", ret);
+
+    ret = pMFCreateDeviceSource(attrs, &source);
+    CHECK_HRESULT("MFCreateDeviceSource", ret);
+
+    IMFAttributes_Release(attrs);
+    SDL_free(wstrsymlink);
+    attrs = NULL;
+    wstrsymlink = NULL;
+
+    // !!! FIXME: I think it'd be nice to do this without an IMFSourceReader,
+    // since it's just utility code that has to handle more complex media streams
+    // than we're dealing with, but this will do for now. The docs are slightly
+    // insistent that you should use one, though...Maybe it's extremely hard
+    // to handle directly at the IMFMediaSource layer...?
+    ret = pMFCreateSourceReaderFromMediaSource(source, NULL, &srcreader);
+    CHECK_HRESULT("MFCreateSourceReaderFromMediaSource", ret);
+
+    // !!! FIXME: do we actually have to find the media type object in the source reader or can we just roll our own like this?
+    ret = pMFCreateMediaType(&mediatype);
+    CHECK_HRESULT("MFCreateMediaType", ret);
+
+    ret = IMFMediaType_SetGUID(mediatype, &SDL_MF_MT_MAJOR_TYPE, &SDL_MFMediaType_Video);
+    CHECK_HRESULT("IMFMediaType_SetGUID(major_type)", ret);
+
+    ret = IMFMediaType_SetGUID(mediatype, &SDL_MF_MT_SUBTYPE, SDLFmtToMFVidFmtGuid(spec->format));
+    CHECK_HRESULT("IMFMediaType_SetGUID(subtype)", ret);
+
+    ret = IMFMediaType_SetUINT64(mediatype, &SDL_MF_MT_FRAME_SIZE, (((UINT64)spec->width) << 32) | ((UINT64)spec->height));
+    CHECK_HRESULT("MFSetAttributeSize(frame_size)", ret);
+
+    ret = IMFMediaType_SetUINT64(mediatype, &SDL_MF_MT_FRAME_RATE, (((UINT64)spec->interval_numerator) << 32) | ((UINT64)spec->interval_denominator));
+    CHECK_HRESULT("MFSetAttributeRatio(frame_rate)", ret);
+
+    ret = IMFSourceReader_SetCurrentMediaType(srcreader, MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL, mediatype);
+    CHECK_HRESULT("IMFSourceReader_SetCurrentMediaType", ret);
+
+    #if 0  // this (untested thing) is what we would do to get started with a IMFMediaSource that _doesn't_ use IMFSourceReader...
+    ret = IMFMediaSource_CreatePresentationDescriptor(source, &presentdesc);
+    CHECK_HRESULT("IMFMediaSource_CreatePresentationDescriptor", ret);
+
+    ret = IMFPresentationDescriptor_GetStreamDescriptorCount(presentdesc, &num_streams);
+    CHECK_HRESULT("IMFPresentationDescriptor_GetStreamDescriptorCount", ret);
+
+    for (DWORD i = 0; i < num_streams; i++) {
+        BOOL selected = FALSE;
+        ret = IMFPresentationDescriptor_GetStreamDescriptorByIndex(presentdesc, i, &selected, &streamdesc);
+        CHECK_HRESULT("IMFPresentationDescriptor_GetStreamDescriptorByIndex", ret);
+
+        if (selected) {
+            ret = IMFStreamDescriptor_GetMediaTypeHandler(streamdesc, &handler);
+            CHECK_HRESULT("IMFStreamDescriptor_GetMediaTypeHandler", ret);
+            IMFMediaTypeHandler_SetCurrentMediaType(handler, mediatype);
+            IMFMediaTypeHandler_Release(handler);
+            handler = NULL;
+        }
+
+        IMFStreamDescriptor_Release(streamdesc);
+        streamdesc = NULL;
+    }
+
+    PropVariantInit(&var);
+    var.vt = VT_EMPTY;
+    ret = IMFMediaSource_Start(source, presentdesc, NULL, &var);
+    PropVariantClear(&var);
+    CHECK_HRESULT("IMFMediaSource_Start", ret);
+
+    IMFPresentationDescriptor_Release(presentdesc);
+    presentdesc = NULL;
+    #endif
+
+    ret = GetDefaultStride(mediatype, &lstride);
+    CHECK_HRESULT("GetDefaultStride", ret);
+
+    IMFMediaType_Release(mediatype);
+    mediatype = NULL;
+
+    device->hidden = (SDL_PrivateCameraData *) SDL_calloc(1, sizeof (SDL_PrivateCameraData));
+    if (!device->hidden) {
+        goto failed;
+    }
+
+    device->hidden->pitch = (int) lstride;
+    device->hidden->srcreader = srcreader;
+    IMFMediaSource_Release(source);  // srcreader is holding a reference to this.
+
+    // There is no user permission prompt for camera access (I think?)
+    SDL_CameraDevicePermissionOutcome(device, SDL_TRUE);
+
+    #undef CHECK_HRESULT
+
+    return 0;
+
+failed:
+
+    if (srcreader) {
+        IMFSourceReader_Release(srcreader);
+    }
+
+    #if 0
+    if (handler) {
+        IMFMediaTypeHandler_Release(handler);
+    }
+
+    if (streamdesc) {
+        IMFStreamDescriptor_Release(streamdesc);
+    }
+
+    if (presentdesc) {
+        IMFPresentationDescriptor_Release(presentdesc);
+    }
+    #endif
+
+    if (source) {
+        IMFMediaSource_Shutdown(source);
+        IMFMediaSource_Release(source);
+    }
+
+    if (mediatype) {
+        IMFMediaType_Release(mediatype);
+    }
+
+    if (attrs) {
+        IMFAttributes_Release(attrs);
+    }
+    SDL_free(wstrsymlink);
+
+    return -1;
+}
+
+static void MEDIAFOUNDATION_FreeDeviceHandle(SDL_CameraDevice *device)
+{
+    if (device) {
+        SDL_free(device->handle);  // the device's symlink string.
+    }
+}
+
+static char *QueryActivationObjectString(IMFActivate *activation, const GUID *pguid)
+{
+    LPWSTR wstr = NULL;
+    UINT32 wlen = 0;
+    HRESULT ret = IMFActivate_GetAllocatedString(activation, pguid, &wstr, &wlen);
+    if (FAILED(ret)) {
+        return NULL;
+    }
+
+    char *utf8str = WIN_StringToUTF8(wstr);
+    CoTaskMemFree(wstr);
+    return utf8str;
+}
+
+static void GatherCameraSpecs(IMFMediaSource *source, CameraFormatAddData *add_data)
+{
+    HRESULT ret;
+
+    // this has like a thousand steps.  :/
+
+    SDL_zerop(add_data);
+
+    IMFPresentationDescriptor *presentdesc = NULL;
+    ret = IMFMediaSource_CreatePresentationDescriptor(source, &presentdesc);
+    if (FAILED(ret) || !presentdesc) {
+        return;
+    }
+
+    DWORD num_streams = 0;
+    ret = IMFPresentationDescriptor_GetStreamDescriptorCount(presentdesc, &num_streams);
+    if (FAILED(ret)) {
+        num_streams = 0;
+    }
+
+    for (DWORD i = 0; i < num_streams; i++) {
+        IMFStreamDescriptor *streamdesc = NULL;
+        BOOL selected = FALSE;
+        ret = IMFPresentationDescriptor_GetStreamDescriptorByIndex(presentdesc, i, &selected, &streamdesc);
+        if (FAILED(ret) || !streamdesc) {
+            continue;
+        }
+
+        if (selected) {
+            IMFMediaTypeHandler *handler = NULL;
+            ret = IMFStreamDescriptor_GetMediaTypeHandler(streamdesc, &handler);
+            if (SUCCEEDED(ret) && handler) {
+                DWORD num_mediatype = 0;
+                ret = IMFMediaTypeHandler_GetMediaTypeCount(handler, &num_mediatype);
+                if (FAILED(ret)) {
+                    num_mediatype = 0;
+                }
+
+                for (DWORD j = 0; j < num_mediatype; j++) {
+                    IMFMediaType *mediatype = NULL;
+                    ret = IMFMediaTypeHandler_GetMediaTypeByIndex(handler, j, &mediatype);
+                    if (SUCCEEDED(ret) && mediatype) {
+                        GUID type;
+                        ret = IMFMediaType_GetGUID(mediatype, &SDL_MF_MT_MAJOR_TYPE, &type);
+                        if (SUCCEEDED(ret) && WIN_IsEqualGUID(&type, &SDL_MFMediaType_Video)) {
+                            ret = IMFMediaType_GetGUID(mediatype, &SDL_MF_MT_SUBTYPE, &type);
+                            if (SUCCEEDED(ret)) {
+                                const Uint32 sdlfmt = MFVidFmtGuidToSDLFmt(&type);
+                                if (sdlfmt != SDL_PIXELFORMAT_UNKNOWN) {
+                                    UINT64 val = 0;
+                                    UINT32 w = 0, h = 0;
+                                    ret = IMFMediaType_GetUINT64(mediatype, &SDL_MF_MT_FRAME_SIZE, &val);
+                                    w = (UINT32)(val >> 32);
+                                    h = (UINT32)val;
+                                    if (SUCCEEDED(ret) && w && h) {
+                                        UINT32 interval_numerator = 0, interval_denominator = 0;
+                                        ret = IMFMediaType_GetUINT64(mediatype, &SDL_MF_MT_FRAME_RATE, &val);
+                                        interval_numerator = (UINT32)(val >> 32);
+                                        interval_denominator = (UINT32)val;
+                                        if (SUCCEEDED(ret) && interval_numerator && interval_denominator) {
+                                            SDL_AddCameraFormat(add_data, sdlfmt, (int) w, (int) h, (int) interval_numerator, (int) interval_denominator);  // whew.
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                        IMFMediaType_Release(mediatype);
+                    }
+                }
+                IMFMediaTypeHandler_Release(handler);
+            }
+        }
+        IMFStreamDescriptor_Release(streamdesc);
+    }
+
+    IMFPresentationDescriptor_Release(presentdesc);
+}
+
+static SDL_bool FindMediaFoundationCameraDeviceBySymlink(SDL_CameraDevice *device, void *userdata)
+{
+    return (SDL_strcmp((const char *) device->handle, (const char *) userdata) == 0);
+}
+
+static void MaybeAddDevice(IMFActivate *activation)
+{
+    char *symlink = QueryActivationObjectString(activation, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK);
+
+    if (SDL_FindPhysicalCameraDeviceByCallback(FindMediaFoundationCameraDeviceBySymlink, symlink)) {
+        SDL_free(symlink);
+        return;  // already have this one.
+    }
+
+    char *name = QueryActivationObjectString(activation, &MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME);
+    if (name && symlink) {
+        IMFMediaSource *source = NULL;
+        // "activating" here only creates an object, it doesn't open the actual camera hardware or start recording.
+        HRESULT ret = IMFActivate_ActivateObject(activation, &SDL_IID_IMFMediaSource, &source);
+        if (SUCCEEDED(ret) && source) {
+            CameraFormatAddData add_data;
+            GatherCameraSpecs(source, &add_data);
+            if (add_data.num_specs > 0) {
+                SDL_AddCameraDevice(name, add_data.num_specs, add_data.specs, symlink);
+            }
+            SDL_free(add_data.specs);
+            IMFActivate_ShutdownObject(activation);
+            IMFMediaSource_Release(source);
+        }
+    }
+
+    SDL_free(name);
+}
+
+static void MEDIAFOUNDATION_DetectDevices(void)
+{
+    // !!! FIXME: use CM_Register_Notification (Win8+) to get device notifications.
+    // !!! FIXME: Earlier versions can use RegisterDeviceNotification, but I'm not bothering: no hotplug for you!
+    HRESULT ret;
+
+    IMFAttributes *attrs = NULL;
+    ret = pMFCreateAttributes(&attrs, 1);
+    if (FAILED(ret)) {
+        return;  // oh well, no cameras for you.
+    }
+
+    // !!! FIXME: We need these GUIDs hardcoded in this file.
+    ret = IMFAttributes_SetGUID(attrs, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
+    if (FAILED(ret)) {
+        IMFAttributes_Release(attrs);
+        return;  // oh well, no cameras for you.
+    }
+
+    IMFActivate **activations = NULL;
+    UINT32 total = 0;
+    ret = pMFEnumDeviceSources(attrs, &activations, &total);
+    IMFAttributes_Release(attrs);
+    if (FAILED(ret)) {
+        return;  // oh well, no cameras for you.
+    }
+
+    for (UINT32 i = 0; i < total; i++) {
+        MaybeAddDevice(activations[i]);
+        IMFActivate_Release(activations[i]);
+    }
+
+    CoTaskMemFree(activations);
+}
+
+static void MEDIAFOUNDATION_Deinitialize(void)
+{
+    pMFShutdown();
+
+    FreeLibrary(libmfreadwrite);
+    libmfreadwrite = NULL;
+    FreeLibrary(libmfplat);
+    libmfplat = NULL;
+    FreeLibrary(libmf);
+    libmf = NULL;
+
+    pMFEnumDeviceSources = NULL;
+    pMFCreateDeviceSource = NULL;
+    pMFStartup = NULL;
+    pMFShutdown = NULL;
+    pMFCreateAttributes = NULL;
+    pMFCreateMediaType = NULL;
+    pMFCreateSourceReaderFromMediaSource = NULL;
+    pMFGetStrideForBitmapInfoHeader = NULL;
+}
+
+static SDL_bool MEDIAFOUNDATION_Init(SDL_CameraDriverImpl *impl)
+{
+    // !!! FIXME: slide this off into a subroutine
+    HANDLE mf = LoadLibrary(TEXT("Mf.dll")); // this library is available in Vista and later, but also can be on XP with service packs and Windows
+    if (!mf) {
+        return SDL_FALSE;
+    }
+
+    HANDLE mfplat = LoadLibrary(TEXT("Mfplat.dll")); // this library is available in Vista and later. No WinXP, so have to LoadLibrary to use it for now!
+    if (!mfplat) {
+        FreeLibrary(mf);
+        return SDL_FALSE;
+    }
+
+    HANDLE mfreadwrite = LoadLibrary(TEXT("Mfreadwrite.dll")); // this library is available in Vista and later. No WinXP, so have to LoadLibrary to use it for now!
+    if (!mfreadwrite) {
+        FreeLibrary(mfplat);
+        FreeLibrary(mf);
+        return SDL_FALSE;
+    }
+
+    SDL_bool okay = SDL_TRUE;
+    #define LOADSYM(lib, fn) if (okay) { p##fn = (pfn##fn) GetProcAddress(lib, #fn); if (!p##fn) { okay = SDL_FALSE; } }
+    LOADSYM(mf, MFEnumDeviceSources);
+    LOADSYM(mf, MFCreateDeviceSource);
+    LOADSYM(mfplat, MFStartup);
+    LOADSYM(mfplat, MFShutdown);
+    LOADSYM(mfplat, MFCreateAttributes);
+    LOADSYM(mfplat, MFCreateMediaType);
+    LOADSYM(mfplat, MFGetStrideForBitmapInfoHeader);
+    LOADSYM(mfreadwrite, MFCreateSourceReaderFromMediaSource);
+    #undef LOADSYM
+
+    if (!okay) {
+        FreeLibrary(mfreadwrite);
+        FreeLibrary(mfplat);
+        FreeLibrary(mf);
+    }
+
+    libmf = mf;
+    libmfplat = mfplat;
+    libmfreadwrite = mfreadwrite;
+
+    const HRESULT ret = pMFStartup(MF_VERSION, MFSTARTUP_LITE);
+    if (FAILED(ret)) {
+        FreeLibrary(libmfplat);
+        libmfplat = NULL;
+        FreeLibrary(libmf);
+        libmf = NULL;
+        return SDL_FALSE;
+    }
+
+    impl->DetectDevices = MEDIAFOUNDATION_DetectDevices;
+    impl->OpenDevice = MEDIAFOUNDATION_OpenDevice;
+    impl->CloseDevice = MEDIAFOUNDATION_CloseDevice;
+    impl->WaitDevice = MEDIAFOUNDATION_WaitDevice;
+    impl->AcquireFrame = MEDIAFOUNDATION_AcquireFrame;
+    impl->ReleaseFrame = MEDIAFOUNDATION_ReleaseFrame;
+    impl->FreeDeviceHandle = MEDIAFOUNDATION_FreeDeviceHandle;
+    impl->Deinitialize = MEDIAFOUNDATION_Deinitialize;
+
+    return SDL_TRUE;
+}
+
+CameraBootStrap MEDIAFOUNDATION_bootstrap = {
+    "mediafoundation", "SDL Windows Media Foundation camera driver", MEDIAFOUNDATION_Init, SDL_FALSE
+};
+
+#endif // SDL_CAMERA_DRIVER_MEDIAFOUNDATION
+

+ 5 - 37
src/camera/v4l2/SDL_camera_v4l2.c

@@ -631,39 +631,7 @@ static SDL_bool FindV4L2CameraDeviceByBusInfoCallback(SDL_CameraDevice *device,
     return (SDL_strcmp(handle->bus_info, (const char *) userdata) == 0);
 }
 
-typedef struct FormatAddData
-{
-    SDL_CameraSpec *specs;
-    int num_specs;
-    int allocated_specs;
-} FormatAddData;
-
-static int AddCameraCompleteFormat(FormatAddData *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 int AddCameraFormat(const int fd, FormatAddData *data, Uint32 sdlfmt, Uint32 v4l2fmt, int w, int h)
+static int AddCameraFormat(const int fd, CameraFormatAddData *data, Uint32 sdlfmt, Uint32 v4l2fmt, int w, int h)
 {
     struct v4l2_frmivalenum frmivalenum;
     SDL_zero(frmivalenum);
@@ -679,7 +647,7 @@ static int AddCameraFormat(const int fd, FormatAddData *data, Uint32 sdlfmt, Uin
             const float fps = (float) denominator / (float) numerator;
             SDL_Log("CAMERA:       * Has discrete frame interval (%d / %d), fps=%f", numerator, denominator, fps);
             #endif
-            if (AddCameraCompleteFormat(data, sdlfmt, w, h, numerator, denominator) == -1) {
+            if (SDL_AddCameraFormat(data, sdlfmt, w, h, numerator, denominator) == -1) {
                 return -1;  // Probably out of memory; we'll go with what we have, if anything.
             }
             frmivalenum.index++;  // set up for the next one.
@@ -691,7 +659,7 @@ static int AddCameraFormat(const int fd, FormatAddData *data, Uint32 sdlfmt, Uin
                 const float fps = (float) d / (float) n;
                 SDL_Log("CAMERA:       * Has %s frame interval (%d / %d), fps=%f", (frmivalenum.type == V4L2_FRMIVAL_TYPE_STEPWISE) ? "stepwise" : "continuous", n, d, fps);
                 #endif
-                if (AddCameraCompleteFormat(data, sdlfmt, w, h, n, d) == -1) {
+                if (SDL_AddCameraFormat(data, sdlfmt, w, h, n, d) == -1) {
                     return -1;  // Probably out of memory; we'll go with what we have, if anything.
                 }
                 d += (int) frmivalenum.stepwise.step.denominator;
@@ -739,7 +707,7 @@ static void MaybeAddDevice(const char *path)
     SDL_Log("CAMERA: V4L2 camera path='%s' bus_info='%s' name='%s'", path, (const char *) vcap.bus_info, vcap.card);
     #endif
 
-    FormatAddData add_data;
+    CameraFormatAddData add_data;
     SDL_zero(add_data);
 
     struct v4l2_fmtdesc fmtdesc;
@@ -911,5 +879,5 @@ CameraBootStrap V4L2_bootstrap = {
     "v4l2", "SDL Video4Linux2 camera driver", V4L2_Init, SDL_FALSE
 };
 
-#endif // SDL_CAMERA_V4L2
+#endif // SDL_CAMERA_DRIVER_V4L2