diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj
index 2ddcdb1a6..1a07f3c2f 100644
--- a/VisualC/SDL/SDL.vcxproj
+++ b/VisualC/SDL/SDL.vcxproj
@@ -397,6 +397,7 @@
Create
+
diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters
index 6673e9e81..7c0f5190f 100644
--- a/VisualC/SDL/SDL.vcxproj.filters
+++ b/VisualC/SDL/SDL.vcxproj.filters
@@ -184,6 +184,9 @@
{0000fc2700d453b3c8d79fe81e1c0000}
+
+ {0000fbfe2d21e4f451142e7d0e870000}
+
@@ -856,6 +859,9 @@
camera\dummy
+
+ camera\mediafoundation
+
camera
diff --git a/Xcode/SDL/SDL.xcodeproj/project.pbxproj b/Xcode/SDL/SDL.xcodeproj/project.pbxproj
index e5623fe14..7ec548712 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 = ""; };
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 = ""; };
/* 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 = "";
@@ -2175,6 +2178,14 @@
path = resources;
sourceTree = "";
};
+ 0000926F2501CA0BDD650000 /* mediafoundation */ = {
+ isa = PBXGroup;
+ children = (
+ 0000C61C247BAAAF757D0000 /* SDL_camera_mediafoundation.c */,
+ );
+ path = mediafoundation;
+ sourceTree = "";
+ };
/* 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 9f4302a79..11c1ad716 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 066888fbd..a8d2dc4fd 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 01179dc44..486b50013 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 e6b204771..b99239ec5 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 1dcdd2958..b93eecbbe 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 5e74cb928..45be94969 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 000000000..960ab1e54
--- /dev/null
+++ b/src/camera/mediafoundation/SDL_camera_mediafoundation.c
@@ -0,0 +1,918 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2023 Sam Lantinga
+
+ 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
+#include
+#include
+
+#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, ×tamp100NS);
+ 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, ¤tlen);
+ 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, ×tamp100NS);
+ 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, ¤tlen);
+ 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
+
diff --git a/src/camera/v4l2/SDL_camera_v4l2.c b/src/camera/v4l2/SDL_camera_v4l2.c
index 9f3b1e08b..0e2915910 100644
--- a/src/camera/v4l2/SDL_camera_v4l2.c
+++ b/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