diff options
Diffstat (limited to 'contrib/SDL-3.2.8/src/camera/mediafoundation/SDL_camera_mediafoundation.c')
| -rw-r--r-- | contrib/SDL-3.2.8/src/camera/mediafoundation/SDL_camera_mediafoundation.c | 1143 |
1 files changed, 1143 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/camera/mediafoundation/SDL_camera_mediafoundation.c b/contrib/SDL-3.2.8/src/camera/mediafoundation/SDL_camera_mediafoundation.c new file mode 100644 index 0000000..d9d627d --- /dev/null +++ b/contrib/SDL-3.2.8/src/camera/mediafoundation/SDL_camera_mediafoundation.c | |||
| @@ -0,0 +1,1143 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | // the Windows Media Foundation API | ||
| 24 | |||
| 25 | #ifdef SDL_CAMERA_DRIVER_MEDIAFOUNDATION | ||
| 26 | |||
| 27 | #define COBJMACROS | ||
| 28 | |||
| 29 | // this seems to be a bug in mfidl.h, just define this to avoid the problem section. | ||
| 30 | #define __IMFVideoProcessorControl3_INTERFACE_DEFINED__ | ||
| 31 | |||
| 32 | #include "../../core/windows/SDL_windows.h" | ||
| 33 | |||
| 34 | #include <mfapi.h> | ||
| 35 | #include <mfidl.h> | ||
| 36 | #include <mfreadwrite.h> | ||
| 37 | |||
| 38 | #include "../SDL_syscamera.h" | ||
| 39 | #include "../SDL_camera_c.h" | ||
| 40 | |||
| 41 | static const IID SDL_IID_IMFMediaSource = { 0x279a808d, 0xaec7, 0x40c8, { 0x9c, 0x6b, 0xa6, 0xb4, 0x92, 0xc7, 0x8a, 0x66 } }; | ||
| 42 | static const IID SDL_IID_IMF2DBuffer = { 0x7dc9d5f9, 0x9ed9, 0x44ec, { 0x9b, 0xbf, 0x06, 0x00, 0xbb, 0x58, 0x9f, 0xbb } }; | ||
| 43 | static const IID SDL_IID_IMF2DBuffer2 = { 0x33ae5ea6, 0x4316, 0x436f, { 0x8d, 0xdd, 0xd7, 0x3d, 0x22, 0xf8, 0x29, 0xec } }; | ||
| 44 | static const GUID SDL_MF_MT_DEFAULT_STRIDE = { 0x644b4e48, 0x1e02, 0x4516, { 0xb0, 0xeb, 0xc0, 0x1c, 0xa9, 0xd4, 0x9a, 0xc6 } }; | ||
| 45 | static const GUID SDL_MF_MT_MAJOR_TYPE = { 0x48eba18e, 0xf8c9, 0x4687, { 0xbf, 0x11, 0x0a, 0x74, 0xc9, 0xf9, 0x6a, 0x8f } }; | ||
| 46 | static const GUID SDL_MF_MT_SUBTYPE = { 0xf7e34c9a, 0x42e8, 0x4714, { 0xb7, 0x4b, 0xcb, 0x29, 0xd7, 0x2c, 0x35, 0xe5 } }; | ||
| 47 | static const GUID SDL_MF_MT_VIDEO_NOMINAL_RANGE = { 0xc21b8ee5, 0xb956, 0x4071, { 0x8d, 0xaf, 0x32, 0x5e, 0xdf, 0x5c, 0xab, 0x11 } }; | ||
| 48 | static const GUID SDL_MF_MT_VIDEO_PRIMARIES = { 0xdbfbe4d7, 0x0740, 0x4ee0, { 0x81, 0x92, 0x85, 0x0a, 0xb0, 0xe2, 0x19, 0x35 } }; | ||
| 49 | static const GUID SDL_MF_MT_TRANSFER_FUNCTION = { 0x5fb0fce9, 0xbe5c, 0x4935, { 0xa8, 0x11, 0xec, 0x83, 0x8f, 0x8e, 0xed, 0x93 } }; | ||
| 50 | static const GUID SDL_MF_MT_YUV_MATRIX = { 0x3e23d450, 0x2c75, 0x4d25, { 0xa0, 0x0e, 0xb9, 0x16, 0x70, 0xd1, 0x23, 0x27 } }; | ||
| 51 | static const GUID SDL_MF_MT_VIDEO_CHROMA_SITING = { 0x65df2370, 0xc773, 0x4c33, { 0xaa, 0x64, 0x84, 0x3e, 0x06, 0x8e, 0xfb, 0x0c } }; | ||
| 52 | static const GUID SDL_MF_MT_FRAME_SIZE = { 0x1652c33d, 0xd6b2, 0x4012, { 0xb8, 0x34, 0x72, 0x03, 0x08, 0x49, 0xa3, 0x7d } }; | ||
| 53 | static const GUID SDL_MF_MT_FRAME_RATE = { 0xc459a2e8, 0x3d2c, 0x4e44, { 0xb1, 0x32, 0xfe, 0xe5, 0x15, 0x6c, 0x7b, 0xb0 } }; | ||
| 54 | static const GUID SDL_MFMediaType_Video = { 0x73646976, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 } }; | ||
| 55 | static const IID SDL_MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME = { 0x60d0e559, 0x52f8, 0x4fa2, { 0xbb, 0xce, 0xac, 0xdb, 0x34, 0xa8, 0xec, 0x1 } }; | ||
| 56 | static const IID SDL_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE = { 0xc60ac5fe, 0x252a, 0x478f, { 0xa0, 0xef, 0xbc, 0x8f, 0xa5, 0xf7, 0xca, 0xd3 } }; | ||
| 57 | static const IID SDL_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK = { 0x58f0aad8, 0x22bf, 0x4f8a, { 0xbb, 0x3d, 0xd2, 0xc4, 0x97, 0x8c, 0x6e, 0x2f } }; | ||
| 58 | static const IID SDL_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID = { 0x8ac3587a, 0x4ae7, 0x42d8, { 0x99, 0xe0, 0x0a, 0x60, 0x13, 0xee, 0xf9, 0x0f } }; | ||
| 59 | |||
| 60 | #ifdef __GNUC__ | ||
| 61 | #pragma GCC diagnostic push | ||
| 62 | #pragma GCC diagnostic ignored "-Wmultichar" | ||
| 63 | #endif | ||
| 64 | |||
| 65 | #define SDL_DEFINE_MEDIATYPE_GUID(name, fmt) static const GUID SDL_##name = { fmt, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } } | ||
| 66 | SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_RGB555, 24); | ||
| 67 | SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_RGB565, 23); | ||
| 68 | SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_RGB24, 20); | ||
| 69 | SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_RGB32, 22); | ||
| 70 | SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_ARGB32, 21); | ||
| 71 | SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_A2R10G10B10, 31); | ||
| 72 | SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_YV12, FCC('YV12')); | ||
| 73 | SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_IYUV, FCC('IYUV')); | ||
| 74 | SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_YUY2, FCC('YUY2')); | ||
| 75 | SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_UYVY, FCC('UYVY')); | ||
| 76 | SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_YVYU, FCC('YVYU')); | ||
| 77 | SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_NV12, FCC('NV12')); | ||
| 78 | SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_NV21, FCC('NV21')); | ||
| 79 | SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_MJPG, FCC('MJPG')); | ||
| 80 | #undef SDL_DEFINE_MEDIATYPE_GUID | ||
| 81 | |||
| 82 | #ifdef __GNUC__ | ||
| 83 | #pragma GCC diagnostic pop | ||
| 84 | #endif | ||
| 85 | |||
| 86 | static const struct | ||
| 87 | { | ||
| 88 | const GUID *guid; | ||
| 89 | SDL_PixelFormat format; | ||
| 90 | SDL_Colorspace colorspace; | ||
| 91 | } fmtmappings[] = { | ||
| 92 | // This is not every possible format, just popular ones that SDL can reasonably handle. | ||
| 93 | // (and we should probably trim this list more.) | ||
| 94 | { &SDL_MFVideoFormat_RGB555, SDL_PIXELFORMAT_XRGB1555, SDL_COLORSPACE_SRGB }, | ||
| 95 | { &SDL_MFVideoFormat_RGB565, SDL_PIXELFORMAT_RGB565, SDL_COLORSPACE_SRGB }, | ||
| 96 | { &SDL_MFVideoFormat_RGB24, SDL_PIXELFORMAT_RGB24, SDL_COLORSPACE_SRGB }, | ||
| 97 | { &SDL_MFVideoFormat_RGB32, SDL_PIXELFORMAT_XRGB8888, SDL_COLORSPACE_SRGB }, | ||
| 98 | { &SDL_MFVideoFormat_ARGB32, SDL_PIXELFORMAT_ARGB8888, SDL_COLORSPACE_SRGB }, | ||
| 99 | { &SDL_MFVideoFormat_A2R10G10B10, SDL_PIXELFORMAT_ARGB2101010, SDL_COLORSPACE_SRGB }, | ||
| 100 | { &SDL_MFVideoFormat_YV12, SDL_PIXELFORMAT_YV12, SDL_COLORSPACE_BT709_LIMITED }, | ||
| 101 | { &SDL_MFVideoFormat_IYUV, SDL_PIXELFORMAT_IYUV, SDL_COLORSPACE_BT709_LIMITED }, | ||
| 102 | { &SDL_MFVideoFormat_YUY2, SDL_PIXELFORMAT_YUY2, SDL_COLORSPACE_BT709_LIMITED }, | ||
| 103 | { &SDL_MFVideoFormat_UYVY, SDL_PIXELFORMAT_UYVY, SDL_COLORSPACE_BT709_LIMITED }, | ||
| 104 | { &SDL_MFVideoFormat_YVYU, SDL_PIXELFORMAT_YVYU, SDL_COLORSPACE_BT709_LIMITED }, | ||
| 105 | { &SDL_MFVideoFormat_NV12, SDL_PIXELFORMAT_NV12, SDL_COLORSPACE_BT709_LIMITED }, | ||
| 106 | { &SDL_MFVideoFormat_NV21, SDL_PIXELFORMAT_NV21, SDL_COLORSPACE_BT709_LIMITED }, | ||
| 107 | { &SDL_MFVideoFormat_MJPG, SDL_PIXELFORMAT_MJPG, SDL_COLORSPACE_SRGB } | ||
| 108 | }; | ||
| 109 | |||
| 110 | static SDL_Colorspace GetMediaTypeColorspace(IMFMediaType *mediatype, SDL_Colorspace default_colorspace) | ||
| 111 | { | ||
| 112 | SDL_Colorspace colorspace = default_colorspace; | ||
| 113 | |||
| 114 | if (SDL_COLORSPACETYPE(colorspace) == SDL_COLOR_TYPE_YCBCR) { | ||
| 115 | HRESULT ret; | ||
| 116 | UINT32 range = 0, primaries = 0, transfer = 0, matrix = 0, chroma = 0; | ||
| 117 | |||
| 118 | ret = IMFMediaType_GetUINT32(mediatype, &SDL_MF_MT_VIDEO_NOMINAL_RANGE, &range); | ||
| 119 | if (SUCCEEDED(ret)) { | ||
| 120 | switch (range) { | ||
| 121 | case MFNominalRange_0_255: | ||
| 122 | range = SDL_COLOR_RANGE_FULL; | ||
| 123 | break; | ||
| 124 | case MFNominalRange_16_235: | ||
| 125 | range = SDL_COLOR_RANGE_LIMITED; | ||
| 126 | break; | ||
| 127 | default: | ||
| 128 | range = (UINT32)SDL_COLORSPACERANGE(default_colorspace); | ||
| 129 | break; | ||
| 130 | } | ||
| 131 | } else { | ||
| 132 | range = (UINT32)SDL_COLORSPACERANGE(default_colorspace); | ||
| 133 | } | ||
| 134 | |||
| 135 | ret = IMFMediaType_GetUINT32(mediatype, &SDL_MF_MT_VIDEO_PRIMARIES, &primaries); | ||
| 136 | if (SUCCEEDED(ret)) { | ||
| 137 | switch (primaries) { | ||
| 138 | case MFVideoPrimaries_BT709: | ||
| 139 | primaries = SDL_COLOR_PRIMARIES_BT709; | ||
| 140 | break; | ||
| 141 | case MFVideoPrimaries_BT470_2_SysM: | ||
| 142 | primaries = SDL_COLOR_PRIMARIES_BT470M; | ||
| 143 | break; | ||
| 144 | case MFVideoPrimaries_BT470_2_SysBG: | ||
| 145 | primaries = SDL_COLOR_PRIMARIES_BT470BG; | ||
| 146 | break; | ||
| 147 | case MFVideoPrimaries_SMPTE170M: | ||
| 148 | primaries = SDL_COLOR_PRIMARIES_BT601; | ||
| 149 | break; | ||
| 150 | case MFVideoPrimaries_SMPTE240M: | ||
| 151 | primaries = SDL_COLOR_PRIMARIES_SMPTE240; | ||
| 152 | break; | ||
| 153 | case MFVideoPrimaries_EBU3213: | ||
| 154 | primaries = SDL_COLOR_PRIMARIES_EBU3213; | ||
| 155 | break; | ||
| 156 | case MFVideoPrimaries_BT2020: | ||
| 157 | primaries = SDL_COLOR_PRIMARIES_BT2020; | ||
| 158 | break; | ||
| 159 | case MFVideoPrimaries_XYZ: | ||
| 160 | primaries = SDL_COLOR_PRIMARIES_XYZ; | ||
| 161 | break; | ||
| 162 | case MFVideoPrimaries_DCI_P3: | ||
| 163 | primaries = SDL_COLOR_PRIMARIES_SMPTE432; | ||
| 164 | break; | ||
| 165 | default: | ||
| 166 | primaries = (UINT32)SDL_COLORSPACEPRIMARIES(default_colorspace); | ||
| 167 | break; | ||
| 168 | } | ||
| 169 | } else { | ||
| 170 | primaries = (UINT32)SDL_COLORSPACEPRIMARIES(default_colorspace); | ||
| 171 | } | ||
| 172 | |||
| 173 | ret = IMFMediaType_GetUINT32(mediatype, &SDL_MF_MT_TRANSFER_FUNCTION, &transfer); | ||
| 174 | if (SUCCEEDED(ret)) { | ||
| 175 | switch (transfer) { | ||
| 176 | case MFVideoTransFunc_10: | ||
| 177 | transfer = SDL_TRANSFER_CHARACTERISTICS_LINEAR; | ||
| 178 | break; | ||
| 179 | case MFVideoTransFunc_22: | ||
| 180 | transfer = SDL_TRANSFER_CHARACTERISTICS_GAMMA22; | ||
| 181 | break; | ||
| 182 | case MFVideoTransFunc_709: | ||
| 183 | transfer = SDL_TRANSFER_CHARACTERISTICS_BT709; | ||
| 184 | break; | ||
| 185 | case MFVideoTransFunc_240M: | ||
| 186 | transfer = SDL_TRANSFER_CHARACTERISTICS_SMPTE240; | ||
| 187 | break; | ||
| 188 | case MFVideoTransFunc_sRGB: | ||
| 189 | transfer = SDL_TRANSFER_CHARACTERISTICS_SRGB; | ||
| 190 | break; | ||
| 191 | case MFVideoTransFunc_28: | ||
| 192 | transfer = SDL_TRANSFER_CHARACTERISTICS_GAMMA28; | ||
| 193 | break; | ||
| 194 | case MFVideoTransFunc_Log_100: | ||
| 195 | transfer = SDL_TRANSFER_CHARACTERISTICS_LOG100; | ||
| 196 | break; | ||
| 197 | case MFVideoTransFunc_2084: | ||
| 198 | transfer = SDL_TRANSFER_CHARACTERISTICS_PQ; | ||
| 199 | break; | ||
| 200 | case MFVideoTransFunc_HLG: | ||
| 201 | transfer = SDL_TRANSFER_CHARACTERISTICS_HLG; | ||
| 202 | break; | ||
| 203 | case 18 /* MFVideoTransFunc_BT1361_ECG */: | ||
| 204 | transfer = SDL_TRANSFER_CHARACTERISTICS_BT1361; | ||
| 205 | break; | ||
| 206 | case 19 /* MFVideoTransFunc_SMPTE428 */: | ||
| 207 | transfer = SDL_TRANSFER_CHARACTERISTICS_SMPTE428; | ||
| 208 | break; | ||
| 209 | default: | ||
| 210 | transfer = (UINT32)SDL_COLORSPACETRANSFER(default_colorspace); | ||
| 211 | break; | ||
| 212 | } | ||
| 213 | } else { | ||
| 214 | transfer = (UINT32)SDL_COLORSPACETRANSFER(default_colorspace); | ||
| 215 | } | ||
| 216 | |||
| 217 | ret = IMFMediaType_GetUINT32(mediatype, &SDL_MF_MT_YUV_MATRIX, &matrix); | ||
| 218 | if (SUCCEEDED(ret)) { | ||
| 219 | switch (matrix) { | ||
| 220 | case MFVideoTransferMatrix_BT709: | ||
| 221 | matrix = SDL_MATRIX_COEFFICIENTS_BT709; | ||
| 222 | break; | ||
| 223 | case MFVideoTransferMatrix_BT601: | ||
| 224 | matrix = SDL_MATRIX_COEFFICIENTS_BT601; | ||
| 225 | break; | ||
| 226 | case MFVideoTransferMatrix_SMPTE240M: | ||
| 227 | matrix = SDL_MATRIX_COEFFICIENTS_SMPTE240; | ||
| 228 | break; | ||
| 229 | case MFVideoTransferMatrix_BT2020_10: | ||
| 230 | matrix = SDL_MATRIX_COEFFICIENTS_BT2020_NCL; | ||
| 231 | break; | ||
| 232 | case 6 /* MFVideoTransferMatrix_Identity */: | ||
| 233 | matrix = SDL_MATRIX_COEFFICIENTS_IDENTITY; | ||
| 234 | break; | ||
| 235 | case 7 /* MFVideoTransferMatrix_FCC47 */: | ||
| 236 | matrix = SDL_MATRIX_COEFFICIENTS_FCC; | ||
| 237 | break; | ||
| 238 | case 8 /* MFVideoTransferMatrix_YCgCo */: | ||
| 239 | matrix = SDL_MATRIX_COEFFICIENTS_YCGCO; | ||
| 240 | break; | ||
| 241 | case 9 /* MFVideoTransferMatrix_SMPTE2085 */: | ||
| 242 | matrix = SDL_MATRIX_COEFFICIENTS_SMPTE2085; | ||
| 243 | break; | ||
| 244 | case 10 /* MFVideoTransferMatrix_Chroma */: | ||
| 245 | matrix = SDL_MATRIX_COEFFICIENTS_CHROMA_DERIVED_NCL; | ||
| 246 | break; | ||
| 247 | case 11 /* MFVideoTransferMatrix_Chroma_const */: | ||
| 248 | matrix = SDL_MATRIX_COEFFICIENTS_CHROMA_DERIVED_CL; | ||
| 249 | break; | ||
| 250 | case 12 /* MFVideoTransferMatrix_ICtCp */: | ||
| 251 | matrix = SDL_MATRIX_COEFFICIENTS_ICTCP; | ||
| 252 | break; | ||
| 253 | default: | ||
| 254 | matrix = (UINT32)SDL_COLORSPACEMATRIX(default_colorspace); | ||
| 255 | break; | ||
| 256 | } | ||
| 257 | } else { | ||
| 258 | matrix = (UINT32)SDL_COLORSPACEMATRIX(default_colorspace); | ||
| 259 | } | ||
| 260 | |||
| 261 | ret = IMFMediaType_GetUINT32(mediatype, &SDL_MF_MT_VIDEO_CHROMA_SITING, &chroma); | ||
| 262 | if (SUCCEEDED(ret)) { | ||
| 263 | switch (chroma) { | ||
| 264 | case MFVideoChromaSubsampling_MPEG2: | ||
| 265 | chroma = SDL_CHROMA_LOCATION_LEFT; | ||
| 266 | break; | ||
| 267 | case MFVideoChromaSubsampling_MPEG1: | ||
| 268 | chroma = SDL_CHROMA_LOCATION_CENTER; | ||
| 269 | break; | ||
| 270 | case MFVideoChromaSubsampling_DV_PAL: | ||
| 271 | chroma = SDL_CHROMA_LOCATION_TOPLEFT; | ||
| 272 | break; | ||
| 273 | default: | ||
| 274 | chroma = (UINT32)SDL_COLORSPACECHROMA(default_colorspace); | ||
| 275 | break; | ||
| 276 | } | ||
| 277 | } else { | ||
| 278 | chroma = (UINT32)SDL_COLORSPACECHROMA(default_colorspace); | ||
| 279 | } | ||
| 280 | |||
| 281 | colorspace = SDL_DEFINE_COLORSPACE(SDL_COLOR_TYPE_YCBCR, range, primaries, transfer, matrix, chroma); | ||
| 282 | } | ||
| 283 | return colorspace; | ||
| 284 | } | ||
| 285 | |||
| 286 | static void MediaTypeToSDLFmt(IMFMediaType *mediatype, SDL_PixelFormat *format, SDL_Colorspace *colorspace) | ||
| 287 | { | ||
| 288 | HRESULT ret; | ||
| 289 | GUID type; | ||
| 290 | |||
| 291 | ret = IMFMediaType_GetGUID(mediatype, &SDL_MF_MT_SUBTYPE, &type); | ||
| 292 | if (SUCCEEDED(ret)) { | ||
| 293 | for (size_t i = 0; i < SDL_arraysize(fmtmappings); i++) { | ||
| 294 | if (WIN_IsEqualGUID(&type, fmtmappings[i].guid)) { | ||
| 295 | *format = fmtmappings[i].format; | ||
| 296 | *colorspace = GetMediaTypeColorspace(mediatype, fmtmappings[i].colorspace); | ||
| 297 | return; | ||
| 298 | } | ||
| 299 | } | ||
| 300 | } | ||
| 301 | #if DEBUG_CAMERA | ||
| 302 | SDL_Log("Unknown media type: 0x%x (%c%c%c%c)", type.Data1, | ||
| 303 | (char)(Uint8)(type.Data1 >> 0), | ||
| 304 | (char)(Uint8)(type.Data1 >> 8), | ||
| 305 | (char)(Uint8)(type.Data1 >> 16), | ||
| 306 | (char)(Uint8)(type.Data1 >> 24)); | ||
| 307 | #endif | ||
| 308 | *format = SDL_PIXELFORMAT_UNKNOWN; | ||
| 309 | *colorspace = SDL_COLORSPACE_UNKNOWN; | ||
| 310 | } | ||
| 311 | |||
| 312 | static const GUID *SDLFmtToMFVidFmtGuid(SDL_PixelFormat format) | ||
| 313 | { | ||
| 314 | for (size_t i = 0; i < SDL_arraysize(fmtmappings); i++) { | ||
| 315 | if (fmtmappings[i].format == format) { | ||
| 316 | return fmtmappings[i].guid; | ||
| 317 | } | ||
| 318 | } | ||
| 319 | return NULL; | ||
| 320 | } | ||
| 321 | |||
| 322 | |||
| 323 | // handle to Media Foundation libs--Vista and later!--for access to the Media Foundation API. | ||
| 324 | |||
| 325 | // mf.dll ... | ||
| 326 | static HMODULE libmf = NULL; | ||
| 327 | typedef HRESULT(WINAPI *pfnMFEnumDeviceSources)(IMFAttributes *,IMFActivate ***,UINT32 *); | ||
| 328 | typedef HRESULT(WINAPI *pfnMFCreateDeviceSource)(IMFAttributes *, IMFMediaSource **); | ||
| 329 | static pfnMFEnumDeviceSources pMFEnumDeviceSources = NULL; | ||
| 330 | static pfnMFCreateDeviceSource pMFCreateDeviceSource = NULL; | ||
| 331 | |||
| 332 | // mfplat.dll ... | ||
| 333 | static HMODULE libmfplat = NULL; | ||
| 334 | typedef HRESULT(WINAPI *pfnMFStartup)(ULONG, DWORD); | ||
| 335 | typedef HRESULT(WINAPI *pfnMFShutdown)(void); | ||
| 336 | typedef HRESULT(WINAPI *pfnMFCreateAttributes)(IMFAttributes **, UINT32); | ||
| 337 | typedef HRESULT(WINAPI *pfnMFCreateMediaType)(IMFMediaType **); | ||
| 338 | typedef HRESULT(WINAPI *pfnMFGetStrideForBitmapInfoHeader)(DWORD, DWORD, LONG *); | ||
| 339 | |||
| 340 | static pfnMFStartup pMFStartup = NULL; | ||
| 341 | static pfnMFShutdown pMFShutdown = NULL; | ||
| 342 | static pfnMFCreateAttributes pMFCreateAttributes = NULL; | ||
| 343 | static pfnMFCreateMediaType pMFCreateMediaType = NULL; | ||
| 344 | static pfnMFGetStrideForBitmapInfoHeader pMFGetStrideForBitmapInfoHeader = NULL; | ||
| 345 | |||
| 346 | // mfreadwrite.dll ... | ||
| 347 | static HMODULE libmfreadwrite = NULL; | ||
| 348 | typedef HRESULT(WINAPI *pfnMFCreateSourceReaderFromMediaSource)(IMFMediaSource *, IMFAttributes *, IMFSourceReader **); | ||
| 349 | static pfnMFCreateSourceReaderFromMediaSource pMFCreateSourceReaderFromMediaSource = NULL; | ||
| 350 | |||
| 351 | |||
| 352 | typedef struct SDL_PrivateCameraData | ||
| 353 | { | ||
| 354 | IMFSourceReader *srcreader; | ||
| 355 | IMFSample *current_sample; | ||
| 356 | int pitch; | ||
| 357 | } SDL_PrivateCameraData; | ||
| 358 | |||
| 359 | static bool MEDIAFOUNDATION_WaitDevice(SDL_Camera *device) | ||
| 360 | { | ||
| 361 | SDL_assert(device->hidden->current_sample == NULL); | ||
| 362 | |||
| 363 | IMFSourceReader *srcreader = device->hidden->srcreader; | ||
| 364 | IMFSample *sample = NULL; | ||
| 365 | |||
| 366 | while (!SDL_GetAtomicInt(&device->shutdown)) { | ||
| 367 | DWORD stream_flags = 0; | ||
| 368 | const HRESULT ret = IMFSourceReader_ReadSample(srcreader, (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, NULL, &stream_flags, NULL, &sample); | ||
| 369 | if (FAILED(ret)) { | ||
| 370 | return false; // ruh roh. | ||
| 371 | } | ||
| 372 | |||
| 373 | // we currently ignore stream_flags format changes, but my _hope_ is that IMFSourceReader is handling this and | ||
| 374 | // will continue to give us the explicitly-specified format we requested when opening the device, though, and | ||
| 375 | // we don't have to manually deal with it. | ||
| 376 | |||
| 377 | if (sample != NULL) { | ||
| 378 | break; | ||
| 379 | } else if (stream_flags & (MF_SOURCE_READERF_ERROR | MF_SOURCE_READERF_ENDOFSTREAM)) { | ||
| 380 | return false; // apparently this camera has gone down. :/ | ||
| 381 | } | ||
| 382 | |||
| 383 | // otherwise, there was some minor burp, probably; just try again. | ||
| 384 | } | ||
| 385 | |||
| 386 | device->hidden->current_sample = sample; | ||
| 387 | |||
| 388 | return true; | ||
| 389 | } | ||
| 390 | |||
| 391 | |||
| 392 | #ifdef KEEP_ACQUIRED_BUFFERS_LOCKED | ||
| 393 | |||
| 394 | #define PROP_SURFACE_IMFOBJS_POINTER "SDL.camera.mediafoundation.imfobjs" | ||
| 395 | |||
| 396 | typedef struct SDL_IMFObjects | ||
| 397 | { | ||
| 398 | IMF2DBuffer2 *buffer2d2; | ||
| 399 | IMF2DBuffer *buffer2d; | ||
| 400 | IMFMediaBuffer *buffer; | ||
| 401 | IMFSample *sample; | ||
| 402 | } SDL_IMFObjects; | ||
| 403 | |||
| 404 | static void SDLCALL CleanupIMF2DBuffer2(void *userdata, void *value) | ||
| 405 | { | ||
| 406 | SDL_IMFObjects *objs = (SDL_IMFObjects *)value; | ||
| 407 | IMF2DBuffer2_Unlock2D(objs->buffer2d2); | ||
| 408 | IMF2DBuffer2_Release(objs->buffer2d2); | ||
| 409 | IMFMediaBuffer_Release(objs->buffer); | ||
| 410 | IMFSample_Release(objs->sample); | ||
| 411 | SDL_free(objs); | ||
| 412 | } | ||
| 413 | |||
| 414 | static void SDLCALL CleanupIMF2DBuffer(void *userdata, void *value) | ||
| 415 | { | ||
| 416 | SDL_IMFObjects *objs = (SDL_IMFObjects *)value; | ||
| 417 | IMF2DBuffer_Unlock2D(objs->buffer2d); | ||
| 418 | IMF2DBuffer_Release(objs->buffer2d); | ||
| 419 | IMFMediaBuffer_Release(objs->buffer); | ||
| 420 | IMFSample_Release(objs->sample); | ||
| 421 | SDL_free(objs); | ||
| 422 | } | ||
| 423 | |||
| 424 | static void SDLCALL CleanupIMFMediaBuffer(void *userdata, void *value) | ||
| 425 | { | ||
| 426 | SDL_IMFObjects *objs = (SDL_IMFObjects *)value; | ||
| 427 | IMFMediaBuffer_Unlock(objs->buffer); | ||
| 428 | IMFMediaBuffer_Release(objs->buffer); | ||
| 429 | IMFSample_Release(objs->sample); | ||
| 430 | SDL_free(objs); | ||
| 431 | } | ||
| 432 | |||
| 433 | static SDL_CameraFrameResult MEDIAFOUNDATION_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS) | ||
| 434 | { | ||
| 435 | SDL_assert(device->hidden->current_sample != NULL); | ||
| 436 | |||
| 437 | SDL_CameraFrameResult result = SDL_CAMERA_FRAME_READY; | ||
| 438 | HRESULT ret; | ||
| 439 | LONGLONG timestamp100NS = 0; | ||
| 440 | SDL_IMFObjects *objs = (SDL_IMFObjects *) SDL_calloc(1, sizeof (SDL_IMFObjects)); | ||
| 441 | |||
| 442 | if (objs == NULL) { | ||
| 443 | return SDL_CAMERA_FRAME_ERROR; | ||
| 444 | } | ||
| 445 | |||
| 446 | objs->sample = device->hidden->current_sample; | ||
| 447 | device->hidden->current_sample = NULL; | ||
| 448 | |||
| 449 | const SDL_PropertiesID surfprops = SDL_GetSurfaceProperties(frame); | ||
| 450 | if (!surfprops) { | ||
| 451 | result = SDL_CAMERA_FRAME_ERROR; | ||
| 452 | } else { | ||
| 453 | ret = IMFSample_GetSampleTime(objs->sample, ×tamp100NS); | ||
| 454 | if (FAILED(ret)) { | ||
| 455 | result = SDL_CAMERA_FRAME_ERROR; | ||
| 456 | } | ||
| 457 | |||
| 458 | *timestampNS = timestamp100NS * 100; // the timestamps are in 100-nanosecond increments; move to full nanoseconds. | ||
| 459 | } | ||
| 460 | |||
| 461 | ret = (result == SDL_CAMERA_FRAME_ERROR) ? E_FAIL : IMFSample_ConvertToContiguousBuffer(objs->sample, &objs->buffer); // IMFSample_GetBufferByIndex(objs->sample, 0, &objs->buffer); | ||
| 462 | |||
| 463 | if (FAILED(ret)) { | ||
| 464 | SDL_free(objs); | ||
| 465 | result = SDL_CAMERA_FRAME_ERROR; | ||
| 466 | } else { | ||
| 467 | BYTE *pixels = NULL; | ||
| 468 | LONG pitch = 0; | ||
| 469 | DWORD buflen = 0; | ||
| 470 | |||
| 471 | if (SUCCEEDED(IMFMediaBuffer_QueryInterface(objs->buffer, &SDL_IID_IMF2DBuffer2, (void **)&objs->buffer2d2))) { | ||
| 472 | BYTE *bufstart = NULL; | ||
| 473 | ret = IMF2DBuffer2_Lock2DSize(objs->buffer2d2, MF2DBuffer_LockFlags_Read, &pixels, &pitch, &bufstart, &buflen); | ||
| 474 | if (FAILED(ret)) { | ||
| 475 | result = SDL_CAMERA_FRAME_ERROR; | ||
| 476 | CleanupIMF2DBuffer2(NULL, objs); | ||
| 477 | } else { | ||
| 478 | if (frame->format == SDL_PIXELFORMAT_MJPG) { | ||
| 479 | pitch = (LONG)buflen; | ||
| 480 | } | ||
| 481 | if (pitch < 0) { // image rows are reversed. | ||
| 482 | pixels += -pitch * (frame->h - 1); | ||
| 483 | } | ||
| 484 | frame->pixels = pixels; | ||
| 485 | frame->pitch = (int)pitch; | ||
| 486 | if (!SDL_SetPointerPropertyWithCleanup(surfprops, PROP_SURFACE_IMFOBJS_POINTER, objs, CleanupIMF2DBuffer2, NULL)) { | ||
| 487 | result = SDL_CAMERA_FRAME_ERROR; | ||
| 488 | } | ||
| 489 | } | ||
| 490 | } else if (frame->format != SDL_PIXELFORMAT_MJPG && | ||
| 491 | SUCCEEDED(IMFMediaBuffer_QueryInterface(objs->buffer, &SDL_IID_IMF2DBuffer, (void **)&objs->buffer2d))) { | ||
| 492 | ret = IMF2DBuffer_Lock2D(objs->buffer2d, &pixels, &pitch); | ||
| 493 | if (FAILED(ret)) { | ||
| 494 | CleanupIMF2DBuffer(NULL, objs); | ||
| 495 | result = SDL_CAMERA_FRAME_ERROR; | ||
| 496 | } else { | ||
| 497 | if (pitch < 0) { // image rows are reversed. | ||
| 498 | pixels += -pitch * (frame->h - 1); | ||
| 499 | } | ||
| 500 | frame->pixels = pixels; | ||
| 501 | frame->pitch = (int)pitch; | ||
| 502 | if (!SDL_SetPointerPropertyWithCleanup(surfprops, PROP_SURFACE_IMFOBJS_POINTER, objs, CleanupIMF2DBuffer, NULL)) { | ||
| 503 | result = SDL_CAMERA_FRAME_ERROR; | ||
| 504 | } | ||
| 505 | } | ||
| 506 | } else { | ||
| 507 | DWORD maxlen = 0; | ||
| 508 | ret = IMFMediaBuffer_Lock(objs->buffer, &pixels, &maxlen, &buflen); | ||
| 509 | if (FAILED(ret)) { | ||
| 510 | CleanupIMFMediaBuffer(NULL, objs); | ||
| 511 | result = SDL_CAMERA_FRAME_ERROR; | ||
| 512 | } else { | ||
| 513 | if (frame->format == SDL_PIXELFORMAT_MJPG) { | ||
| 514 | pitch = (LONG)buflen; | ||
| 515 | } else { | ||
| 516 | pitch = (LONG)device->hidden->pitch; | ||
| 517 | } | ||
| 518 | if (pitch < 0) { // image rows are reversed. | ||
| 519 | pixels += -pitch * (frame->h - 1); | ||
| 520 | } | ||
| 521 | frame->pixels = pixels; | ||
| 522 | frame->pitch = (int)pitch; | ||
| 523 | if (!SDL_SetPointerPropertyWithCleanup(surfprops, PROP_SURFACE_IMFOBJS_POINTER, objs, CleanupIMFMediaBuffer, NULL)) { | ||
| 524 | result = SDL_CAMERA_FRAME_ERROR; | ||
| 525 | } | ||
| 526 | } | ||
| 527 | } | ||
| 528 | } | ||
| 529 | |||
| 530 | if (result != SDL_CAMERA_FRAME_READY) { | ||
| 531 | *timestampNS = 0; | ||
| 532 | } | ||
| 533 | |||
| 534 | return result; | ||
| 535 | } | ||
| 536 | |||
| 537 | static void MEDIAFOUNDATION_ReleaseFrame(SDL_Camera *device, SDL_Surface *frame) | ||
| 538 | { | ||
| 539 | const SDL_PropertiesID surfprops = SDL_GetSurfaceProperties(frame); | ||
| 540 | if (surfprops) { | ||
| 541 | // this will release the IMFBuffer and IMFSample objects for this frame. | ||
| 542 | SDL_ClearProperty(surfprops, PROP_SURFACE_IMFOBJS_POINTER); | ||
| 543 | } | ||
| 544 | } | ||
| 545 | |||
| 546 | #else | ||
| 547 | |||
| 548 | static SDL_CameraFrameResult MEDIAFOUNDATION_CopyFrame(SDL_Surface *frame, const BYTE *pixels, LONG pitch, DWORD buflen) | ||
| 549 | { | ||
| 550 | frame->pixels = SDL_aligned_alloc(SDL_GetSIMDAlignment(), buflen); | ||
| 551 | if (!frame->pixels) { | ||
| 552 | return SDL_CAMERA_FRAME_ERROR; | ||
| 553 | } | ||
| 554 | |||
| 555 | const BYTE *start = pixels; | ||
| 556 | if (pitch < 0) { // image rows are reversed. | ||
| 557 | start += -pitch * (frame->h - 1); | ||
| 558 | } | ||
| 559 | SDL_memcpy(frame->pixels, start, buflen); | ||
| 560 | frame->pitch = (int)pitch; | ||
| 561 | |||
| 562 | return SDL_CAMERA_FRAME_READY; | ||
| 563 | } | ||
| 564 | |||
| 565 | static SDL_CameraFrameResult MEDIAFOUNDATION_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS) | ||
| 566 | { | ||
| 567 | SDL_assert(device->hidden->current_sample != NULL); | ||
| 568 | |||
| 569 | SDL_CameraFrameResult result = SDL_CAMERA_FRAME_READY; | ||
| 570 | HRESULT ret; | ||
| 571 | LONGLONG timestamp100NS = 0; | ||
| 572 | |||
| 573 | IMFSample *sample = device->hidden->current_sample; | ||
| 574 | device->hidden->current_sample = NULL; | ||
| 575 | |||
| 576 | const SDL_PropertiesID surfprops = SDL_GetSurfaceProperties(frame); | ||
| 577 | if (!surfprops) { | ||
| 578 | result = SDL_CAMERA_FRAME_ERROR; | ||
| 579 | } else { | ||
| 580 | ret = IMFSample_GetSampleTime(sample, ×tamp100NS); | ||
| 581 | if (FAILED(ret)) { | ||
| 582 | result = SDL_CAMERA_FRAME_ERROR; | ||
| 583 | } | ||
| 584 | |||
| 585 | *timestampNS = timestamp100NS * 100; // the timestamps are in 100-nanosecond increments; move to full nanoseconds. | ||
| 586 | } | ||
| 587 | |||
| 588 | IMFMediaBuffer *buffer = NULL; | ||
| 589 | ret = (result < 0) ? E_FAIL : IMFSample_ConvertToContiguousBuffer(sample, &buffer); // IMFSample_GetBufferByIndex(sample, 0, &buffer); | ||
| 590 | |||
| 591 | if (FAILED(ret)) { | ||
| 592 | result = SDL_CAMERA_FRAME_ERROR; | ||
| 593 | } else { | ||
| 594 | IMF2DBuffer *buffer2d = NULL; | ||
| 595 | IMF2DBuffer2 *buffer2d2 = NULL; | ||
| 596 | BYTE *pixels = NULL; | ||
| 597 | LONG pitch = 0; | ||
| 598 | DWORD buflen = 0; | ||
| 599 | |||
| 600 | if (SUCCEEDED(IMFMediaBuffer_QueryInterface(buffer, &SDL_IID_IMF2DBuffer2, (void **)&buffer2d2))) { | ||
| 601 | BYTE *bufstart = NULL; | ||
| 602 | ret = IMF2DBuffer2_Lock2DSize(buffer2d2, MF2DBuffer_LockFlags_Read, &pixels, &pitch, &bufstart, &buflen); | ||
| 603 | if (FAILED(ret)) { | ||
| 604 | result = SDL_CAMERA_FRAME_ERROR; | ||
| 605 | } else { | ||
| 606 | if (frame->format == SDL_PIXELFORMAT_MJPG) { | ||
| 607 | pitch = (LONG)buflen; | ||
| 608 | } | ||
| 609 | result = MEDIAFOUNDATION_CopyFrame(frame, pixels, pitch, buflen); | ||
| 610 | IMF2DBuffer2_Unlock2D(buffer2d2); | ||
| 611 | } | ||
| 612 | IMF2DBuffer2_Release(buffer2d2); | ||
| 613 | } else if (frame->format != SDL_PIXELFORMAT_MJPG && | ||
| 614 | SUCCEEDED(IMFMediaBuffer_QueryInterface(buffer, &SDL_IID_IMF2DBuffer, (void **)&buffer2d))) { | ||
| 615 | ret = IMF2DBuffer_Lock2D(buffer2d, &pixels, &pitch); | ||
| 616 | if (FAILED(ret)) { | ||
| 617 | result = SDL_CAMERA_FRAME_ERROR; | ||
| 618 | } else { | ||
| 619 | buflen = SDL_abs((int)pitch) * frame->h; | ||
| 620 | result = MEDIAFOUNDATION_CopyFrame(frame, pixels, pitch, buflen); | ||
| 621 | IMF2DBuffer_Unlock2D(buffer2d); | ||
| 622 | } | ||
| 623 | IMF2DBuffer_Release(buffer2d); | ||
| 624 | } else { | ||
| 625 | DWORD maxlen = 0; | ||
| 626 | ret = IMFMediaBuffer_Lock(buffer, &pixels, &maxlen, &buflen); | ||
| 627 | if (FAILED(ret)) { | ||
| 628 | result = SDL_CAMERA_FRAME_ERROR; | ||
| 629 | } else { | ||
| 630 | if (frame->format == SDL_PIXELFORMAT_MJPG) { | ||
| 631 | pitch = (LONG)buflen; | ||
| 632 | } else { | ||
| 633 | pitch = (LONG)device->hidden->pitch; | ||
| 634 | } | ||
| 635 | result = MEDIAFOUNDATION_CopyFrame(frame, pixels, pitch, buflen); | ||
| 636 | IMFMediaBuffer_Unlock(buffer); | ||
| 637 | } | ||
| 638 | } | ||
| 639 | IMFMediaBuffer_Release(buffer); | ||
| 640 | } | ||
| 641 | |||
| 642 | IMFSample_Release(sample); | ||
| 643 | |||
| 644 | if (result != SDL_CAMERA_FRAME_READY) { | ||
| 645 | *timestampNS = 0; | ||
| 646 | } | ||
| 647 | |||
| 648 | return result; | ||
| 649 | } | ||
| 650 | |||
| 651 | static void MEDIAFOUNDATION_ReleaseFrame(SDL_Camera *device, SDL_Surface *frame) | ||
| 652 | { | ||
| 653 | SDL_aligned_free(frame->pixels); | ||
| 654 | } | ||
| 655 | |||
| 656 | #endif | ||
| 657 | |||
| 658 | static void MEDIAFOUNDATION_CloseDevice(SDL_Camera *device) | ||
| 659 | { | ||
| 660 | if (device && device->hidden) { | ||
| 661 | if (device->hidden->srcreader) { | ||
| 662 | IMFSourceReader_Release(device->hidden->srcreader); | ||
| 663 | } | ||
| 664 | if (device->hidden->current_sample) { | ||
| 665 | IMFSample_Release(device->hidden->current_sample); | ||
| 666 | } | ||
| 667 | SDL_free(device->hidden); | ||
| 668 | device->hidden = NULL; | ||
| 669 | } | ||
| 670 | } | ||
| 671 | |||
| 672 | // this function is from https://learn.microsoft.com/en-us/windows/win32/medfound/uncompressed-video-buffers | ||
| 673 | static HRESULT GetDefaultStride(IMFMediaType *pType, LONG *plStride) | ||
| 674 | { | ||
| 675 | LONG lStride = 0; | ||
| 676 | |||
| 677 | // Try to get the default stride from the media type. | ||
| 678 | HRESULT ret = IMFMediaType_GetUINT32(pType, &SDL_MF_MT_DEFAULT_STRIDE, (UINT32*)&lStride); | ||
| 679 | if (FAILED(ret)) { | ||
| 680 | // Attribute not set. Try to calculate the default stride. | ||
| 681 | |||
| 682 | GUID subtype = GUID_NULL; | ||
| 683 | UINT32 width = 0; | ||
| 684 | // UINT32 height = 0; | ||
| 685 | UINT64 val = 0; | ||
| 686 | |||
| 687 | // Get the subtype and the image size. | ||
| 688 | ret = IMFMediaType_GetGUID(pType, &SDL_MF_MT_SUBTYPE, &subtype); | ||
| 689 | if (FAILED(ret)) { | ||
| 690 | goto done; | ||
| 691 | } | ||
| 692 | |||
| 693 | ret = IMFMediaType_GetUINT64(pType, &SDL_MF_MT_FRAME_SIZE, &val); | ||
| 694 | if (FAILED(ret)) { | ||
| 695 | goto done; | ||
| 696 | } | ||
| 697 | |||
| 698 | width = (UINT32) (val >> 32); | ||
| 699 | // height = (UINT32) val; | ||
| 700 | |||
| 701 | ret = pMFGetStrideForBitmapInfoHeader(subtype.Data1, width, &lStride); | ||
| 702 | if (FAILED(ret)) { | ||
| 703 | goto done; | ||
| 704 | } | ||
| 705 | |||
| 706 | // Set the attribute for later reference. | ||
| 707 | IMFMediaType_SetUINT32(pType, &SDL_MF_MT_DEFAULT_STRIDE, (UINT32) lStride); | ||
| 708 | } | ||
| 709 | |||
| 710 | if (SUCCEEDED(ret)) { | ||
| 711 | *plStride = lStride; | ||
| 712 | } | ||
| 713 | |||
| 714 | done: | ||
| 715 | return ret; | ||
| 716 | } | ||
| 717 | |||
| 718 | |||
| 719 | static bool MEDIAFOUNDATION_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec) | ||
| 720 | { | ||
| 721 | const char *utf8symlink = (const char *) device->handle; | ||
| 722 | IMFAttributes *attrs = NULL; | ||
| 723 | LPWSTR wstrsymlink = NULL; | ||
| 724 | IMFMediaSource *source = NULL; | ||
| 725 | IMFMediaType *mediatype = NULL; | ||
| 726 | IMFSourceReader *srcreader = NULL; | ||
| 727 | #if 0 | ||
| 728 | DWORD num_streams = 0; | ||
| 729 | #endif | ||
| 730 | LONG lstride = 0; | ||
| 731 | //PROPVARIANT var; | ||
| 732 | HRESULT ret; | ||
| 733 | |||
| 734 | #if 0 | ||
| 735 | IMFStreamDescriptor *streamdesc = NULL; | ||
| 736 | IMFPresentationDescriptor *presentdesc = NULL; | ||
| 737 | IMFMediaTypeHandler *handler = NULL; | ||
| 738 | #endif | ||
| 739 | |||
| 740 | #if DEBUG_CAMERA | ||
| 741 | SDL_Log("CAMERA: opening device with symlink of '%s'", utf8symlink); | ||
| 742 | #endif | ||
| 743 | |||
| 744 | wstrsymlink = WIN_UTF8ToString(utf8symlink); | ||
| 745 | if (!wstrsymlink) { | ||
| 746 | goto failed; | ||
| 747 | } | ||
| 748 | |||
| 749 | #define CHECK_HRESULT(what, r) if (FAILED(r)) { WIN_SetErrorFromHRESULT(what " failed", r); goto failed; } | ||
| 750 | |||
| 751 | ret = pMFCreateAttributes(&attrs, 1); | ||
| 752 | CHECK_HRESULT("MFCreateAttributes", ret); | ||
| 753 | |||
| 754 | ret = IMFAttributes_SetGUID(attrs, &SDL_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, &SDL_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID); | ||
| 755 | CHECK_HRESULT("IMFAttributes_SetGUID(srctype)", ret); | ||
| 756 | |||
| 757 | ret = IMFAttributes_SetString(attrs, &SDL_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, wstrsymlink); | ||
| 758 | CHECK_HRESULT("IMFAttributes_SetString(symlink)", ret); | ||
| 759 | |||
| 760 | ret = pMFCreateDeviceSource(attrs, &source); | ||
| 761 | CHECK_HRESULT("MFCreateDeviceSource", ret); | ||
| 762 | |||
| 763 | IMFAttributes_Release(attrs); | ||
| 764 | SDL_free(wstrsymlink); | ||
| 765 | attrs = NULL; | ||
| 766 | wstrsymlink = NULL; | ||
| 767 | |||
| 768 | // !!! FIXME: I think it'd be nice to do this without an IMFSourceReader, | ||
| 769 | // since it's just utility code that has to handle more complex media streams | ||
| 770 | // than we're dealing with, but this will do for now. The docs are slightly | ||
| 771 | // insistent that you should use one, though...Maybe it's extremely hard | ||
| 772 | // to handle directly at the IMFMediaSource layer...? | ||
| 773 | ret = pMFCreateSourceReaderFromMediaSource(source, NULL, &srcreader); | ||
| 774 | CHECK_HRESULT("MFCreateSourceReaderFromMediaSource", ret); | ||
| 775 | |||
| 776 | // !!! FIXME: do we actually have to find the media type object in the source reader or can we just roll our own like this? | ||
| 777 | ret = pMFCreateMediaType(&mediatype); | ||
| 778 | CHECK_HRESULT("MFCreateMediaType", ret); | ||
| 779 | |||
| 780 | ret = IMFMediaType_SetGUID(mediatype, &SDL_MF_MT_MAJOR_TYPE, &SDL_MFMediaType_Video); | ||
| 781 | CHECK_HRESULT("IMFMediaType_SetGUID(major_type)", ret); | ||
| 782 | |||
| 783 | ret = IMFMediaType_SetGUID(mediatype, &SDL_MF_MT_SUBTYPE, SDLFmtToMFVidFmtGuid(spec->format)); | ||
| 784 | CHECK_HRESULT("IMFMediaType_SetGUID(subtype)", ret); | ||
| 785 | |||
| 786 | ret = IMFMediaType_SetUINT64(mediatype, &SDL_MF_MT_FRAME_SIZE, (((UINT64)spec->width) << 32) | ((UINT64)spec->height)); | ||
| 787 | CHECK_HRESULT("MFSetAttributeSize(frame_size)", ret); | ||
| 788 | |||
| 789 | ret = IMFMediaType_SetUINT64(mediatype, &SDL_MF_MT_FRAME_RATE, (((UINT64)spec->framerate_numerator) << 32) | ((UINT64)spec->framerate_denominator)); | ||
| 790 | CHECK_HRESULT("MFSetAttributeRatio(frame_rate)", ret); | ||
| 791 | |||
| 792 | ret = IMFSourceReader_SetCurrentMediaType(srcreader, (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL, mediatype); | ||
| 793 | CHECK_HRESULT("IMFSourceReader_SetCurrentMediaType", ret); | ||
| 794 | |||
| 795 | #if 0 // this (untested thing) is what we would do to get started with a IMFMediaSource that _doesn't_ use IMFSourceReader... | ||
| 796 | ret = IMFMediaSource_CreatePresentationDescriptor(source, &presentdesc); | ||
| 797 | CHECK_HRESULT("IMFMediaSource_CreatePresentationDescriptor", ret); | ||
| 798 | |||
| 799 | ret = IMFPresentationDescriptor_GetStreamDescriptorCount(presentdesc, &num_streams); | ||
| 800 | CHECK_HRESULT("IMFPresentationDescriptor_GetStreamDescriptorCount", ret); | ||
| 801 | |||
| 802 | for (DWORD i = 0; i < num_streams; i++) { | ||
| 803 | BOOL selected = FALSE; | ||
| 804 | ret = IMFPresentationDescriptor_GetStreamDescriptorByIndex(presentdesc, i, &selected, &streamdesc); | ||
| 805 | CHECK_HRESULT("IMFPresentationDescriptor_GetStreamDescriptorByIndex", ret); | ||
| 806 | |||
| 807 | if (selected) { | ||
| 808 | ret = IMFStreamDescriptor_GetMediaTypeHandler(streamdesc, &handler); | ||
| 809 | CHECK_HRESULT("IMFStreamDescriptor_GetMediaTypeHandler", ret); | ||
| 810 | IMFMediaTypeHandler_SetCurrentMediaType(handler, mediatype); | ||
| 811 | IMFMediaTypeHandler_Release(handler); | ||
| 812 | handler = NULL; | ||
| 813 | } | ||
| 814 | |||
| 815 | IMFStreamDescriptor_Release(streamdesc); | ||
| 816 | streamdesc = NULL; | ||
| 817 | } | ||
| 818 | |||
| 819 | PropVariantInit(&var); | ||
| 820 | var.vt = VT_EMPTY; | ||
| 821 | ret = IMFMediaSource_Start(source, presentdesc, NULL, &var); | ||
| 822 | PropVariantClear(&var); | ||
| 823 | CHECK_HRESULT("IMFMediaSource_Start", ret); | ||
| 824 | |||
| 825 | IMFPresentationDescriptor_Release(presentdesc); | ||
| 826 | presentdesc = NULL; | ||
| 827 | #endif | ||
| 828 | |||
| 829 | ret = GetDefaultStride(mediatype, &lstride); | ||
| 830 | CHECK_HRESULT("GetDefaultStride", ret); | ||
| 831 | |||
| 832 | IMFMediaType_Release(mediatype); | ||
| 833 | mediatype = NULL; | ||
| 834 | |||
| 835 | device->hidden = (SDL_PrivateCameraData *) SDL_calloc(1, sizeof (SDL_PrivateCameraData)); | ||
| 836 | if (!device->hidden) { | ||
| 837 | goto failed; | ||
| 838 | } | ||
| 839 | |||
| 840 | device->hidden->pitch = (int) lstride; | ||
| 841 | device->hidden->srcreader = srcreader; | ||
| 842 | IMFMediaSource_Release(source); // srcreader is holding a reference to this. | ||
| 843 | |||
| 844 | // There is no user permission prompt for camera access (I think?) | ||
| 845 | SDL_CameraPermissionOutcome(device, true); | ||
| 846 | |||
| 847 | #undef CHECK_HRESULT | ||
| 848 | |||
| 849 | return true; | ||
| 850 | |||
| 851 | failed: | ||
| 852 | |||
| 853 | if (srcreader) { | ||
| 854 | IMFSourceReader_Release(srcreader); | ||
| 855 | } | ||
| 856 | |||
| 857 | #if 0 | ||
| 858 | if (handler) { | ||
| 859 | IMFMediaTypeHandler_Release(handler); | ||
| 860 | } | ||
| 861 | |||
| 862 | if (streamdesc) { | ||
| 863 | IMFStreamDescriptor_Release(streamdesc); | ||
| 864 | } | ||
| 865 | |||
| 866 | if (presentdesc) { | ||
| 867 | IMFPresentationDescriptor_Release(presentdesc); | ||
| 868 | } | ||
| 869 | #endif | ||
| 870 | |||
| 871 | if (source) { | ||
| 872 | IMFMediaSource_Shutdown(source); | ||
| 873 | IMFMediaSource_Release(source); | ||
| 874 | } | ||
| 875 | |||
| 876 | if (mediatype) { | ||
| 877 | IMFMediaType_Release(mediatype); | ||
| 878 | } | ||
| 879 | |||
| 880 | if (attrs) { | ||
| 881 | IMFAttributes_Release(attrs); | ||
| 882 | } | ||
| 883 | SDL_free(wstrsymlink); | ||
| 884 | |||
| 885 | return false; | ||
| 886 | } | ||
| 887 | |||
| 888 | static void MEDIAFOUNDATION_FreeDeviceHandle(SDL_Camera *device) | ||
| 889 | { | ||
| 890 | if (device) { | ||
| 891 | SDL_free(device->handle); // the device's symlink string. | ||
| 892 | } | ||
| 893 | } | ||
| 894 | |||
| 895 | static char *QueryActivationObjectString(IMFActivate *activation, const GUID *pguid) | ||
| 896 | { | ||
| 897 | LPWSTR wstr = NULL; | ||
| 898 | UINT32 wlen = 0; | ||
| 899 | HRESULT ret = IMFActivate_GetAllocatedString(activation, pguid, &wstr, &wlen); | ||
| 900 | if (FAILED(ret)) { | ||
| 901 | return NULL; | ||
| 902 | } | ||
| 903 | |||
| 904 | char *utf8str = WIN_StringToUTF8(wstr); | ||
| 905 | CoTaskMemFree(wstr); | ||
| 906 | return utf8str; | ||
| 907 | } | ||
| 908 | |||
| 909 | static void GatherCameraSpecs(IMFMediaSource *source, CameraFormatAddData *add_data) | ||
| 910 | { | ||
| 911 | HRESULT ret; | ||
| 912 | |||
| 913 | // this has like a thousand steps. :/ | ||
| 914 | |||
| 915 | SDL_zerop(add_data); | ||
| 916 | |||
| 917 | IMFPresentationDescriptor *presentdesc = NULL; | ||
| 918 | ret = IMFMediaSource_CreatePresentationDescriptor(source, &presentdesc); | ||
| 919 | if (FAILED(ret) || !presentdesc) { | ||
| 920 | return; | ||
| 921 | } | ||
| 922 | |||
| 923 | DWORD num_streams = 0; | ||
| 924 | ret = IMFPresentationDescriptor_GetStreamDescriptorCount(presentdesc, &num_streams); | ||
| 925 | if (FAILED(ret)) { | ||
| 926 | num_streams = 0; | ||
| 927 | } | ||
| 928 | |||
| 929 | for (DWORD i = 0; i < num_streams; i++) { | ||
| 930 | IMFStreamDescriptor *streamdesc = NULL; | ||
| 931 | BOOL selected = FALSE; | ||
| 932 | ret = IMFPresentationDescriptor_GetStreamDescriptorByIndex(presentdesc, i, &selected, &streamdesc); | ||
| 933 | if (FAILED(ret) || !streamdesc) { | ||
| 934 | continue; | ||
| 935 | } | ||
| 936 | |||
| 937 | if (selected) { | ||
| 938 | IMFMediaTypeHandler *handler = NULL; | ||
| 939 | ret = IMFStreamDescriptor_GetMediaTypeHandler(streamdesc, &handler); | ||
| 940 | if (SUCCEEDED(ret) && handler) { | ||
| 941 | DWORD num_mediatype = 0; | ||
| 942 | ret = IMFMediaTypeHandler_GetMediaTypeCount(handler, &num_mediatype); | ||
| 943 | if (FAILED(ret)) { | ||
| 944 | num_mediatype = 0; | ||
| 945 | } | ||
| 946 | |||
| 947 | for (DWORD j = 0; j < num_mediatype; j++) { | ||
| 948 | IMFMediaType *mediatype = NULL; | ||
| 949 | ret = IMFMediaTypeHandler_GetMediaTypeByIndex(handler, j, &mediatype); | ||
| 950 | if (SUCCEEDED(ret) && mediatype) { | ||
| 951 | GUID type; | ||
| 952 | ret = IMFMediaType_GetGUID(mediatype, &SDL_MF_MT_MAJOR_TYPE, &type); | ||
| 953 | if (SUCCEEDED(ret) && WIN_IsEqualGUID(&type, &SDL_MFMediaType_Video)) { | ||
| 954 | SDL_PixelFormat sdlfmt = SDL_PIXELFORMAT_UNKNOWN; | ||
| 955 | SDL_Colorspace colorspace = SDL_COLORSPACE_UNKNOWN; | ||
| 956 | MediaTypeToSDLFmt(mediatype, &sdlfmt, &colorspace); | ||
| 957 | if (sdlfmt != SDL_PIXELFORMAT_UNKNOWN) { | ||
| 958 | UINT64 val = 0; | ||
| 959 | UINT32 w = 0, h = 0; | ||
| 960 | ret = IMFMediaType_GetUINT64(mediatype, &SDL_MF_MT_FRAME_SIZE, &val); | ||
| 961 | w = (UINT32)(val >> 32); | ||
| 962 | h = (UINT32)val; | ||
| 963 | if (SUCCEEDED(ret) && w && h) { | ||
| 964 | UINT32 framerate_numerator = 0, framerate_denominator = 0; | ||
| 965 | ret = IMFMediaType_GetUINT64(mediatype, &SDL_MF_MT_FRAME_RATE, &val); | ||
| 966 | framerate_numerator = (UINT32)(val >> 32); | ||
| 967 | framerate_denominator = (UINT32)val; | ||
| 968 | if (SUCCEEDED(ret) && framerate_numerator && framerate_denominator) { | ||
| 969 | SDL_AddCameraFormat(add_data, sdlfmt, colorspace, (int) w, (int) h, (int)framerate_numerator, (int)framerate_denominator); | ||
| 970 | } | ||
| 971 | } | ||
| 972 | } | ||
| 973 | } | ||
| 974 | IMFMediaType_Release(mediatype); | ||
| 975 | } | ||
| 976 | } | ||
| 977 | IMFMediaTypeHandler_Release(handler); | ||
| 978 | } | ||
| 979 | } | ||
| 980 | IMFStreamDescriptor_Release(streamdesc); | ||
| 981 | } | ||
| 982 | |||
| 983 | IMFPresentationDescriptor_Release(presentdesc); | ||
| 984 | } | ||
| 985 | |||
| 986 | static bool FindMediaFoundationCameraBySymlink(SDL_Camera *device, void *userdata) | ||
| 987 | { | ||
| 988 | return (SDL_strcmp((const char *) device->handle, (const char *) userdata) == 0); | ||
| 989 | } | ||
| 990 | |||
| 991 | static void MaybeAddDevice(IMFActivate *activation) | ||
| 992 | { | ||
| 993 | char *symlink = QueryActivationObjectString(activation, &SDL_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK); | ||
| 994 | |||
| 995 | if (SDL_FindPhysicalCameraByCallback(FindMediaFoundationCameraBySymlink, symlink)) { | ||
| 996 | SDL_free(symlink); | ||
| 997 | return; // already have this one. | ||
| 998 | } | ||
| 999 | |||
| 1000 | char *name = QueryActivationObjectString(activation, &SDL_MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME); | ||
| 1001 | if (name && symlink) { | ||
| 1002 | IMFMediaSource *source = NULL; | ||
| 1003 | // "activating" here only creates an object, it doesn't open the actual camera hardware or start recording. | ||
| 1004 | HRESULT ret = IMFActivate_ActivateObject(activation, &SDL_IID_IMFMediaSource, (void**)&source); | ||
| 1005 | if (SUCCEEDED(ret) && source) { | ||
| 1006 | CameraFormatAddData add_data; | ||
| 1007 | GatherCameraSpecs(source, &add_data); | ||
| 1008 | if (add_data.num_specs > 0) { | ||
| 1009 | SDL_AddCamera(name, SDL_CAMERA_POSITION_UNKNOWN, add_data.num_specs, add_data.specs, symlink); | ||
| 1010 | } | ||
| 1011 | SDL_free(add_data.specs); | ||
| 1012 | IMFActivate_ShutdownObject(activation); | ||
| 1013 | IMFMediaSource_Release(source); | ||
| 1014 | } | ||
| 1015 | } | ||
| 1016 | |||
| 1017 | SDL_free(name); | ||
| 1018 | } | ||
| 1019 | |||
| 1020 | static void MEDIAFOUNDATION_DetectDevices(void) | ||
| 1021 | { | ||
| 1022 | // !!! FIXME: use CM_Register_Notification (Win8+) to get device notifications. | ||
| 1023 | // !!! FIXME: Earlier versions can use RegisterDeviceNotification, but I'm not bothering: no hotplug for you! | ||
| 1024 | HRESULT ret; | ||
| 1025 | |||
| 1026 | IMFAttributes *attrs = NULL; | ||
| 1027 | ret = pMFCreateAttributes(&attrs, 1); | ||
| 1028 | if (FAILED(ret)) { | ||
| 1029 | return; // oh well, no cameras for you. | ||
| 1030 | } | ||
| 1031 | |||
| 1032 | ret = IMFAttributes_SetGUID(attrs, &SDL_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, &SDL_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID); | ||
| 1033 | if (FAILED(ret)) { | ||
| 1034 | IMFAttributes_Release(attrs); | ||
| 1035 | return; // oh well, no cameras for you. | ||
| 1036 | } | ||
| 1037 | |||
| 1038 | IMFActivate **activations = NULL; | ||
| 1039 | UINT32 total = 0; | ||
| 1040 | ret = pMFEnumDeviceSources(attrs, &activations, &total); | ||
| 1041 | IMFAttributes_Release(attrs); | ||
| 1042 | if (FAILED(ret)) { | ||
| 1043 | return; // oh well, no cameras for you. | ||
| 1044 | } | ||
| 1045 | |||
| 1046 | for (UINT32 i = 0; i < total; i++) { | ||
| 1047 | MaybeAddDevice(activations[i]); | ||
| 1048 | IMFActivate_Release(activations[i]); | ||
| 1049 | } | ||
| 1050 | |||
| 1051 | CoTaskMemFree(activations); | ||
| 1052 | } | ||
| 1053 | |||
| 1054 | static void MEDIAFOUNDATION_Deinitialize(void) | ||
| 1055 | { | ||
| 1056 | pMFShutdown(); | ||
| 1057 | |||
| 1058 | FreeLibrary(libmfreadwrite); | ||
| 1059 | libmfreadwrite = NULL; | ||
| 1060 | FreeLibrary(libmfplat); | ||
| 1061 | libmfplat = NULL; | ||
| 1062 | FreeLibrary(libmf); | ||
| 1063 | libmf = NULL; | ||
| 1064 | |||
| 1065 | pMFEnumDeviceSources = NULL; | ||
| 1066 | pMFCreateDeviceSource = NULL; | ||
| 1067 | pMFStartup = NULL; | ||
| 1068 | pMFShutdown = NULL; | ||
| 1069 | pMFCreateAttributes = NULL; | ||
| 1070 | pMFCreateMediaType = NULL; | ||
| 1071 | pMFCreateSourceReaderFromMediaSource = NULL; | ||
| 1072 | pMFGetStrideForBitmapInfoHeader = NULL; | ||
| 1073 | } | ||
| 1074 | |||
| 1075 | static bool MEDIAFOUNDATION_Init(SDL_CameraDriverImpl *impl) | ||
| 1076 | { | ||
| 1077 | // !!! FIXME: slide this off into a subroutine | ||
| 1078 | HMODULE mf = LoadLibrary(TEXT("Mf.dll")); // this library is available in Vista and later, but also can be on XP with service packs and Windows | ||
| 1079 | if (!mf) { | ||
| 1080 | return false; | ||
| 1081 | } | ||
| 1082 | |||
| 1083 | HMODULE mfplat = LoadLibrary(TEXT("Mfplat.dll")); // this library is available in Vista and later. No WinXP, so have to LoadLibrary to use it for now! | ||
| 1084 | if (!mfplat) { | ||
| 1085 | FreeLibrary(mf); | ||
| 1086 | return false; | ||
| 1087 | } | ||
| 1088 | |||
| 1089 | HMODULE mfreadwrite = LoadLibrary(TEXT("Mfreadwrite.dll")); // this library is available in Vista and later. No WinXP, so have to LoadLibrary to use it for now! | ||
| 1090 | if (!mfreadwrite) { | ||
| 1091 | FreeLibrary(mfplat); | ||
| 1092 | FreeLibrary(mf); | ||
| 1093 | return false; | ||
| 1094 | } | ||
| 1095 | |||
| 1096 | bool okay = true; | ||
| 1097 | #define LOADSYM(lib, fn) if (okay) { p##fn = (pfn##fn) GetProcAddress(lib, #fn); if (!p##fn) { okay = false; } } | ||
| 1098 | LOADSYM(mf, MFEnumDeviceSources); | ||
| 1099 | LOADSYM(mf, MFCreateDeviceSource); | ||
| 1100 | LOADSYM(mfplat, MFStartup); | ||
| 1101 | LOADSYM(mfplat, MFShutdown); | ||
| 1102 | LOADSYM(mfplat, MFCreateAttributes); | ||
| 1103 | LOADSYM(mfplat, MFCreateMediaType); | ||
| 1104 | LOADSYM(mfplat, MFGetStrideForBitmapInfoHeader); | ||
| 1105 | LOADSYM(mfreadwrite, MFCreateSourceReaderFromMediaSource); | ||
| 1106 | #undef LOADSYM | ||
| 1107 | |||
| 1108 | if (okay) { | ||
| 1109 | const HRESULT ret = pMFStartup(MF_VERSION, MFSTARTUP_LITE); | ||
| 1110 | if (FAILED(ret)) { | ||
| 1111 | okay = false; | ||
| 1112 | } | ||
| 1113 | } | ||
| 1114 | |||
| 1115 | if (!okay) { | ||
| 1116 | FreeLibrary(mfreadwrite); | ||
| 1117 | FreeLibrary(mfplat); | ||
| 1118 | FreeLibrary(mf); | ||
| 1119 | return false; | ||
| 1120 | } | ||
| 1121 | |||
| 1122 | libmf = mf; | ||
| 1123 | libmfplat = mfplat; | ||
| 1124 | libmfreadwrite = mfreadwrite; | ||
| 1125 | |||
| 1126 | impl->DetectDevices = MEDIAFOUNDATION_DetectDevices; | ||
| 1127 | impl->OpenDevice = MEDIAFOUNDATION_OpenDevice; | ||
| 1128 | impl->CloseDevice = MEDIAFOUNDATION_CloseDevice; | ||
| 1129 | impl->WaitDevice = MEDIAFOUNDATION_WaitDevice; | ||
| 1130 | impl->AcquireFrame = MEDIAFOUNDATION_AcquireFrame; | ||
| 1131 | impl->ReleaseFrame = MEDIAFOUNDATION_ReleaseFrame; | ||
| 1132 | impl->FreeDeviceHandle = MEDIAFOUNDATION_FreeDeviceHandle; | ||
| 1133 | impl->Deinitialize = MEDIAFOUNDATION_Deinitialize; | ||
| 1134 | |||
| 1135 | return true; | ||
| 1136 | } | ||
| 1137 | |||
| 1138 | CameraBootStrap MEDIAFOUNDATION_bootstrap = { | ||
| 1139 | "mediafoundation", "SDL Windows Media Foundation camera driver", MEDIAFOUNDATION_Init, false | ||
| 1140 | }; | ||
| 1141 | |||
| 1142 | #endif // SDL_CAMERA_DRIVER_MEDIAFOUNDATION | ||
| 1143 | |||
