From 7191a97fe3b8a203aacc0336e7b3f5de49a33084 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" 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 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