#include "SDL_internal.h"
#ifdef SDL_CAMERA_DRIVER_MEDIAFOUNDATION
#define COBJMACROS
#define __IMFVideoProcessorControl3_INTERFACE_DEFINED__
#include "../../core/windows/SDL_windows.h"
#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
#include "../SDL_syscamera.h"
#include "../SDL_camera_c.h"
static const IID SDL_IID_IMFMediaSource = { 0x279a808d, 0xaec7, 0x40c8, { 0x9c, 0x6b, 0xa6, 0xb4, 0x92, 0xc7, 0x8a, 0x66 } };
static const IID SDL_IID_IMF2DBuffer = { 0x7dc9d5f9, 0x9ed9, 0x44ec, { 0x9b, 0xbf, 0x06, 0x00, 0xbb, 0x58, 0x9f, 0xbb } };
static const IID SDL_IID_IMF2DBuffer2 = { 0x33ae5ea6, 0x4316, 0x436f, { 0x8d, 0xdd, 0xd7, 0x3d, 0x22, 0xf8, 0x29, 0xec } };
static const GUID SDL_MF_MT_DEFAULT_STRIDE = { 0x644b4e48, 0x1e02, 0x4516, { 0xb0, 0xeb, 0xc0, 0x1c, 0xa9, 0xd4, 0x9a, 0xc6 } };
static const GUID SDL_MF_MT_MAJOR_TYPE = { 0x48eba18e, 0xf8c9, 0x4687, { 0xbf, 0x11, 0x0a, 0x74, 0xc9, 0xf9, 0x6a, 0x8f } };
static const GUID SDL_MF_MT_SUBTYPE = { 0xf7e34c9a, 0x42e8, 0x4714, { 0xb7, 0x4b, 0xcb, 0x29, 0xd7, 0x2c, 0x35, 0xe5 } };
static const GUID SDL_MF_MT_VIDEO_NOMINAL_RANGE = { 0xc21b8ee5, 0xb956, 0x4071, { 0x8d, 0xaf, 0x32, 0x5e, 0xdf, 0x5c, 0xab, 0x11 } };
static const GUID SDL_MF_MT_VIDEO_PRIMARIES = { 0xdbfbe4d7, 0x0740, 0x4ee0, { 0x81, 0x92, 0x85, 0x0a, 0xb0, 0xe2, 0x19, 0x35 } };
static const GUID SDL_MF_MT_TRANSFER_FUNCTION = { 0x5fb0fce9, 0xbe5c, 0x4935, { 0xa8, 0x11, 0xec, 0x83, 0x8f, 0x8e, 0xed, 0x93 } };
static const GUID SDL_MF_MT_YUV_MATRIX = { 0x3e23d450, 0x2c75, 0x4d25, { 0xa0, 0x0e, 0xb9, 0x16, 0x70, 0xd1, 0x23, 0x27 } };
static const GUID SDL_MF_MT_VIDEO_CHROMA_SITING = { 0x65df2370, 0xc773, 0x4c33, { 0xaa, 0x64, 0x84, 0x3e, 0x06, 0x8e, 0xfb, 0x0c } };
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 } };
static const IID SDL_MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME = { 0x60d0e559, 0x52f8, 0x4fa2, { 0xbb, 0xce, 0xac, 0xdb, 0x34, 0xa8, 0xec, 0x1 } };
static const IID SDL_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE = { 0xc60ac5fe, 0x252a, 0x478f, { 0xa0, 0xef, 0xbc, 0x8f, 0xa5, 0xf7, 0xca, 0xd3 } };
static const IID SDL_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK = { 0x58f0aad8, 0x22bf, 0x4f8a, { 0xbb, 0x3d, 0xd2, 0xc4, 0x97, 0x8c, 0x6e, 0x2f } };
static const IID SDL_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID = { 0x8ac3587a, 0x4ae7, 0x42d8, { 0x99, 0xe0, 0x0a, 0x60, 0x13, 0xee, 0xf9, 0x0f } };
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmultichar"
#endif
#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'));
SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_MJPG, FCC('MJPG'));
#undef SDL_DEFINE_MEDIATYPE_GUID
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
static const struct
{
const GUID *guid;
SDL_PixelFormat format;
SDL_Colorspace colorspace;
} fmtmappings[] = {
{ &SDL_MFVideoFormat_RGB555, SDL_PIXELFORMAT_XRGB1555, SDL_COLORSPACE_SRGB },
{ &SDL_MFVideoFormat_RGB565, SDL_PIXELFORMAT_RGB565, SDL_COLORSPACE_SRGB },
{ &SDL_MFVideoFormat_RGB24, SDL_PIXELFORMAT_RGB24, SDL_COLORSPACE_SRGB },
{ &SDL_MFVideoFormat_RGB32, SDL_PIXELFORMAT_XRGB8888, SDL_COLORSPACE_SRGB },
{ &SDL_MFVideoFormat_ARGB32, SDL_PIXELFORMAT_ARGB8888, SDL_COLORSPACE_SRGB },
{ &SDL_MFVideoFormat_A2R10G10B10, SDL_PIXELFORMAT_ARGB2101010, SDL_COLORSPACE_SRGB },
{ &SDL_MFVideoFormat_YV12, SDL_PIXELFORMAT_YV12, SDL_COLORSPACE_BT709_LIMITED },
{ &SDL_MFVideoFormat_IYUV, SDL_PIXELFORMAT_IYUV, SDL_COLORSPACE_BT709_LIMITED },
{ &SDL_MFVideoFormat_YUY2, SDL_PIXELFORMAT_YUY2, SDL_COLORSPACE_BT709_LIMITED },
{ &SDL_MFVideoFormat_UYVY, SDL_PIXELFORMAT_UYVY, SDL_COLORSPACE_BT709_LIMITED },
{ &SDL_MFVideoFormat_YVYU, SDL_PIXELFORMAT_YVYU, SDL_COLORSPACE_BT709_LIMITED },
{ &SDL_MFVideoFormat_NV12, SDL_PIXELFORMAT_NV12, SDL_COLORSPACE_BT709_LIMITED },
{ &SDL_MFVideoFormat_NV21, SDL_PIXELFORMAT_NV21, SDL_COLORSPACE_BT709_LIMITED },
{ &SDL_MFVideoFormat_MJPG, SDL_PIXELFORMAT_MJPG, SDL_COLORSPACE_SRGB }
};
static SDL_Colorspace GetMediaTypeColorspace(IMFMediaType *mediatype, SDL_Colorspace default_colorspace)
{
SDL_Colorspace colorspace = default_colorspace;
if (SDL_COLORSPACETYPE(colorspace) == SDL_COLOR_TYPE_YCBCR) {
HRESULT ret;
UINT32 range = 0, primaries = 0, transfer = 0, matrix = 0, chroma = 0;
ret = IMFMediaType_GetUINT32(mediatype, &SDL_MF_MT_VIDEO_NOMINAL_RANGE, &range);
if (SUCCEEDED(ret)) {
switch (range) {
case MFNominalRange_0_255:
range = SDL_COLOR_RANGE_FULL;
break;
case MFNominalRange_16_235:
range = SDL_COLOR_RANGE_LIMITED;
break;
default:
range = (UINT32)SDL_COLORSPACERANGE(default_colorspace);
break;
}
} else {
range = (UINT32)SDL_COLORSPACERANGE(default_colorspace);
}
ret = IMFMediaType_GetUINT32(mediatype, &SDL_MF_MT_VIDEO_PRIMARIES, &primaries);
if (SUCCEEDED(ret)) {
switch (primaries) {
case MFVideoPrimaries_BT709:
primaries = SDL_COLOR_PRIMARIES_BT709;
break;
case MFVideoPrimaries_BT470_2_SysM:
primaries = SDL_COLOR_PRIMARIES_BT470M;
break;
case MFVideoPrimaries_BT470_2_SysBG:
primaries = SDL_COLOR_PRIMARIES_BT470BG;
break;
case MFVideoPrimaries_SMPTE170M:
primaries = SDL_COLOR_PRIMARIES_BT601;
break;
case MFVideoPrimaries_SMPTE240M:
primaries = SDL_COLOR_PRIMARIES_SMPTE240;
break;
case MFVideoPrimaries_EBU3213:
primaries = SDL_COLOR_PRIMARIES_EBU3213;
break;
case MFVideoPrimaries_BT2020:
primaries = SDL_COLOR_PRIMARIES_BT2020;
break;
case MFVideoPrimaries_XYZ:
primaries = SDL_COLOR_PRIMARIES_XYZ;
break;
case MFVideoPrimaries_DCI_P3:
primaries = SDL_COLOR_PRIMARIES_SMPTE432;
break;
default:
primaries = (UINT32)SDL_COLORSPACEPRIMARIES(default_colorspace);
break;
}
} else {
primaries = (UINT32)SDL_COLORSPACEPRIMARIES(default_colorspace);
}
ret = IMFMediaType_GetUINT32(mediatype, &SDL_MF_MT_TRANSFER_FUNCTION, &transfer);
if (SUCCEEDED(ret)) {
switch (transfer) {
case MFVideoTransFunc_10:
transfer = SDL_TRANSFER_CHARACTERISTICS_LINEAR;
break;
case MFVideoTransFunc_22:
transfer = SDL_TRANSFER_CHARACTERISTICS_GAMMA22;
break;
case MFVideoTransFunc_709:
transfer = SDL_TRANSFER_CHARACTERISTICS_BT709;
break;
case MFVideoTransFunc_240M:
transfer = SDL_TRANSFER_CHARACTERISTICS_SMPTE240;
break;
case MFVideoTransFunc_sRGB:
transfer = SDL_TRANSFER_CHARACTERISTICS_SRGB;
break;
case MFVideoTransFunc_28:
transfer = SDL_TRANSFER_CHARACTERISTICS_GAMMA28;
break;
case MFVideoTransFunc_Log_100:
transfer = SDL_TRANSFER_CHARACTERISTICS_LOG100;
break;
case MFVideoTransFunc_2084:
transfer = SDL_TRANSFER_CHARACTERISTICS_PQ;
break;
case MFVideoTransFunc_HLG:
transfer = SDL_TRANSFER_CHARACTERISTICS_HLG;
break;
case 18 :
transfer = SDL_TRANSFER_CHARACTERISTICS_BT1361;
break;
case 19 :
transfer = SDL_TRANSFER_CHARACTERISTICS_SMPTE428;
break;
default:
transfer = (UINT32)SDL_COLORSPACETRANSFER(default_colorspace);
break;
}
} else {
transfer = (UINT32)SDL_COLORSPACETRANSFER(default_colorspace);
}
ret = IMFMediaType_GetUINT32(mediatype, &SDL_MF_MT_YUV_MATRIX, &matrix);
if (SUCCEEDED(ret)) {
switch (matrix) {
case MFVideoTransferMatrix_BT709:
matrix = SDL_MATRIX_COEFFICIENTS_BT709;
break;
case MFVideoTransferMatrix_BT601:
matrix = SDL_MATRIX_COEFFICIENTS_BT601;
break;
case MFVideoTransferMatrix_SMPTE240M:
matrix = SDL_MATRIX_COEFFICIENTS_SMPTE240;
break;
case MFVideoTransferMatrix_BT2020_10:
matrix = SDL_MATRIX_COEFFICIENTS_BT2020_NCL;
break;
case 6 :
matrix = SDL_MATRIX_COEFFICIENTS_IDENTITY;
break;
case 7 :
matrix = SDL_MATRIX_COEFFICIENTS_FCC;
break;
case 8 :
matrix = SDL_MATRIX_COEFFICIENTS_YCGCO;
break;
case 9 :
matrix = SDL_MATRIX_COEFFICIENTS_SMPTE2085;
break;
case 10 :
matrix = SDL_MATRIX_COEFFICIENTS_CHROMA_DERIVED_NCL;
break;
case 11 :
matrix = SDL_MATRIX_COEFFICIENTS_CHROMA_DERIVED_CL;
break;
case 12 :
matrix = SDL_MATRIX_COEFFICIENTS_ICTCP;
break;
default:
matrix = (UINT32)SDL_COLORSPACEMATRIX(default_colorspace);
break;
}
} else {
matrix = (UINT32)SDL_COLORSPACEMATRIX(default_colorspace);
}
ret = IMFMediaType_GetUINT32(mediatype, &SDL_MF_MT_VIDEO_CHROMA_SITING, &chroma);
if (SUCCEEDED(ret)) {
switch (chroma) {
case MFVideoChromaSubsampling_MPEG2:
chroma = SDL_CHROMA_LOCATION_LEFT;
break;
case MFVideoChromaSubsampling_MPEG1:
chroma = SDL_CHROMA_LOCATION_CENTER;
break;
case MFVideoChromaSubsampling_DV_PAL:
chroma = SDL_CHROMA_LOCATION_TOPLEFT;
break;
default:
chroma = (UINT32)SDL_COLORSPACECHROMA(default_colorspace);
break;
}
} else {
chroma = (UINT32)SDL_COLORSPACECHROMA(default_colorspace);
}
colorspace = SDL_DEFINE_COLORSPACE(SDL_COLOR_TYPE_YCBCR, range, primaries, transfer, matrix, chroma);
}
return colorspace;
}
static void MediaTypeToSDLFmt(IMFMediaType *mediatype, SDL_PixelFormat *format, SDL_Colorspace *colorspace)
{
HRESULT ret;
GUID type;
ret = IMFMediaType_GetGUID(mediatype, &SDL_MF_MT_SUBTYPE, &type);
if (SUCCEEDED(ret)) {
for (size_t i = 0; i < SDL_arraysize(fmtmappings); i++) {
if (WIN_IsEqualGUID(&type, fmtmappings[i].guid)) {
*format = fmtmappings[i].format;
*colorspace = GetMediaTypeColorspace(mediatype, fmtmappings[i].colorspace);
return;
}
}
}
#if DEBUG_CAMERA
SDL_Log("Unknown media type: 0x%x (%c%c%c%c)", type.Data1,
(char)(Uint8)(type.Data1 >> 0),
(char)(Uint8)(type.Data1 >> 8),
(char)(Uint8)(type.Data1 >> 16),
(char)(Uint8)(type.Data1 >> 24));
#endif
*format = SDL_PIXELFORMAT_UNKNOWN;
*colorspace = SDL_COLORSPACE_UNKNOWN;
}
static const GUID *SDLFmtToMFVidFmtGuid(SDL_PixelFormat format)
{
for (size_t i = 0; i < SDL_arraysize(fmtmappings); i++) {
if (fmtmappings[i].format == format) {
return fmtmappings[i].guid;
}
}
return NULL;
}
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;
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;
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 bool MEDIAFOUNDATION_WaitDevice(SDL_Camera *device)
{
SDL_assert(device->hidden->current_sample == NULL);
IMFSourceReader *srcreader = device->hidden->srcreader;
IMFSample *sample = NULL;
while (!SDL_GetAtomicInt(&device->shutdown)) {
DWORD stream_flags = 0;
const HRESULT ret = IMFSourceReader_ReadSample(srcreader, (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, NULL, &stream_flags, NULL, &sample);
if (FAILED(ret)) {
return false; }
if (sample != NULL) {
break;
} else if (stream_flags & (MF_SOURCE_READERF_ERROR | MF_SOURCE_READERF_ENDOFSTREAM)) {
return false; }
}
device->hidden->current_sample = sample;
return true;
}
#ifdef 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 SDL_CameraFrameResult MEDIAFOUNDATION_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation)
{
SDL_assert(device->hidden->current_sample != NULL);
SDL_CameraFrameResult result = SDL_CAMERA_FRAME_READY;
HRESULT ret;
LONGLONG timestamp100NS = 0;
SDL_IMFObjects *objs = (SDL_IMFObjects *) SDL_calloc(1, sizeof (SDL_IMFObjects));
if (objs == NULL) {
return SDL_CAMERA_FRAME_ERROR;
}
objs->sample = device->hidden->current_sample;
device->hidden->current_sample = NULL;
const SDL_PropertiesID surfprops = SDL_GetSurfaceProperties(frame);
if (!surfprops) {
result = SDL_CAMERA_FRAME_ERROR;
} else {
ret = IMFSample_GetSampleTime(objs->sample, ×tamp100NS);
if (FAILED(ret)) {
result = SDL_CAMERA_FRAME_ERROR;
}
*timestampNS = timestamp100NS * 100; }
ret = (result == SDL_CAMERA_FRAME_ERROR) ? E_FAIL : IMFSample_ConvertToContiguousBuffer(objs->sample, &objs->buffer);
if (FAILED(ret)) {
SDL_free(objs);
result = SDL_CAMERA_FRAME_ERROR;
} else {
BYTE *pixels = NULL;
LONG pitch = 0;
DWORD buflen = 0;
if (SUCCEEDED(IMFMediaBuffer_QueryInterface(objs->buffer, &SDL_IID_IMF2DBuffer2, (void **)&objs->buffer2d2))) {
BYTE *bufstart = NULL;
ret = IMF2DBuffer2_Lock2DSize(objs->buffer2d2, MF2DBuffer_LockFlags_Read, &pixels, &pitch, &bufstart, &buflen);
if (FAILED(ret)) {
result = SDL_CAMERA_FRAME_ERROR;
CleanupIMF2DBuffer2(NULL, objs);
} else {
if (frame->format == SDL_PIXELFORMAT_MJPG) {
pitch = (LONG)buflen;
}
if (pitch < 0) { pixels += -pitch * (frame->h - 1);
}
frame->pixels = pixels;
frame->pitch = (int)pitch;
if (!SDL_SetPointerPropertyWithCleanup(surfprops, PROP_SURFACE_IMFOBJS_POINTER, objs, CleanupIMF2DBuffer2, NULL)) {
result = SDL_CAMERA_FRAME_ERROR;
}
}
} else if (frame->format != SDL_PIXELFORMAT_MJPG &&
SUCCEEDED(IMFMediaBuffer_QueryInterface(objs->buffer, &SDL_IID_IMF2DBuffer, (void **)&objs->buffer2d))) {
ret = IMF2DBuffer_Lock2D(objs->buffer2d, &pixels, &pitch);
if (FAILED(ret)) {
CleanupIMF2DBuffer(NULL, objs);
result = SDL_CAMERA_FRAME_ERROR;
} else {
if (pitch < 0) { pixels += -pitch * (frame->h - 1);
}
frame->pixels = pixels;
frame->pitch = (int)pitch;
if (!SDL_SetPointerPropertyWithCleanup(surfprops, PROP_SURFACE_IMFOBJS_POINTER, objs, CleanupIMF2DBuffer, NULL)) {
result = SDL_CAMERA_FRAME_ERROR;
}
}
} else {
DWORD maxlen = 0;
ret = IMFMediaBuffer_Lock(objs->buffer, &pixels, &maxlen, &buflen);
if (FAILED(ret)) {
CleanupIMFMediaBuffer(NULL, objs);
result = SDL_CAMERA_FRAME_ERROR;
} else {
if (frame->format == SDL_PIXELFORMAT_MJPG) {
pitch = (LONG)buflen;
} else {
pitch = (LONG)device->hidden->pitch;
}
if (pitch < 0) { pixels += -pitch * (frame->h - 1);
}
frame->pixels = pixels;
frame->pitch = (int)pitch;
if (!SDL_SetPointerPropertyWithCleanup(surfprops, PROP_SURFACE_IMFOBJS_POINTER, objs, CleanupIMFMediaBuffer, NULL)) {
result = SDL_CAMERA_FRAME_ERROR;
}
}
}
}
if (result != SDL_CAMERA_FRAME_READY) {
*timestampNS = 0;
}
return result;
}
static void MEDIAFOUNDATION_ReleaseFrame(SDL_Camera *device, SDL_Surface *frame)
{
const SDL_PropertiesID surfprops = SDL_GetSurfaceProperties(frame);
if (surfprops) {
SDL_ClearProperty(surfprops, PROP_SURFACE_IMFOBJS_POINTER);
}
}
#else
static SDL_CameraFrameResult MEDIAFOUNDATION_CopyFrame(SDL_Surface *frame, const BYTE *pixels, LONG pitch, DWORD buflen)
{
frame->pixels = SDL_aligned_alloc(SDL_GetSIMDAlignment(), buflen);
if (!frame->pixels) {
return SDL_CAMERA_FRAME_ERROR;
}
const BYTE *start = pixels;
if (pitch < 0) { start += -pitch * (frame->h - 1);
}
SDL_memcpy(frame->pixels, start, buflen);
frame->pitch = (int)pitch;
return SDL_CAMERA_FRAME_READY;
}
static SDL_CameraFrameResult MEDIAFOUNDATION_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation)
{
SDL_assert(device->hidden->current_sample != NULL);
SDL_CameraFrameResult result = SDL_CAMERA_FRAME_READY;
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) {
result = SDL_CAMERA_FRAME_ERROR;
} else {
ret = IMFSample_GetSampleTime(sample, ×tamp100NS);
if (FAILED(ret)) {
result = SDL_CAMERA_FRAME_ERROR;
}
*timestampNS = timestamp100NS * 100; }
IMFMediaBuffer *buffer = NULL;
ret = (result < 0) ? E_FAIL : IMFSample_ConvertToContiguousBuffer(sample, &buffer);
if (FAILED(ret)) {
result = SDL_CAMERA_FRAME_ERROR;
} else {
IMF2DBuffer *buffer2d = NULL;
IMF2DBuffer2 *buffer2d2 = NULL;
BYTE *pixels = NULL;
LONG pitch = 0;
DWORD buflen = 0;
if (SUCCEEDED(IMFMediaBuffer_QueryInterface(buffer, &SDL_IID_IMF2DBuffer2, (void **)&buffer2d2))) {
BYTE *bufstart = NULL;
ret = IMF2DBuffer2_Lock2DSize(buffer2d2, MF2DBuffer_LockFlags_Read, &pixels, &pitch, &bufstart, &buflen);
if (FAILED(ret)) {
result = SDL_CAMERA_FRAME_ERROR;
} else {
if (frame->format == SDL_PIXELFORMAT_MJPG) {
pitch = (LONG)buflen;
}
result = MEDIAFOUNDATION_CopyFrame(frame, pixels, pitch, buflen);
IMF2DBuffer2_Unlock2D(buffer2d2);
}
IMF2DBuffer2_Release(buffer2d2);
} else if (frame->format != SDL_PIXELFORMAT_MJPG &&
SUCCEEDED(IMFMediaBuffer_QueryInterface(buffer, &SDL_IID_IMF2DBuffer, (void **)&buffer2d))) {
ret = IMF2DBuffer_Lock2D(buffer2d, &pixels, &pitch);
if (FAILED(ret)) {
result = SDL_CAMERA_FRAME_ERROR;
} else {
buflen = SDL_abs((int)pitch) * frame->h;
result = MEDIAFOUNDATION_CopyFrame(frame, pixels, pitch, buflen);
IMF2DBuffer_Unlock2D(buffer2d);
}
IMF2DBuffer_Release(buffer2d);
} else {
DWORD maxlen = 0;
ret = IMFMediaBuffer_Lock(buffer, &pixels, &maxlen, &buflen);
if (FAILED(ret)) {
result = SDL_CAMERA_FRAME_ERROR;
} else {
if (frame->format == SDL_PIXELFORMAT_MJPG) {
pitch = (LONG)buflen;
} else {
pitch = (LONG)device->hidden->pitch;
}
result = MEDIAFOUNDATION_CopyFrame(frame, pixels, pitch, buflen);
IMFMediaBuffer_Unlock(buffer);
}
}
IMFMediaBuffer_Release(buffer);
}
IMFSample_Release(sample);
if (result != SDL_CAMERA_FRAME_READY) {
*timestampNS = 0;
}
return result;
}
static void MEDIAFOUNDATION_ReleaseFrame(SDL_Camera *device, SDL_Surface *frame)
{
SDL_aligned_free(frame->pixels);
}
#endif
static void MEDIAFOUNDATION_CloseDevice(SDL_Camera *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;
}
}
static HRESULT GetDefaultStride(IMFMediaType *pType, LONG *plStride)
{
LONG lStride = 0;
HRESULT ret = IMFMediaType_GetUINT32(pType, &SDL_MF_MT_DEFAULT_STRIDE, (UINT32 *)&lStride);
if (FAILED(ret)) {
GUID subtype = GUID_NULL;
UINT32 width = 0;
UINT64 val = 0;
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);
ret = pMFGetStrideForBitmapInfoHeader(subtype.Data1, width, &lStride);
if (FAILED(ret)) {
goto done;
}
IMFMediaType_SetUINT32(pType, &SDL_MF_MT_DEFAULT_STRIDE, (UINT32) lStride);
}
if (SUCCEEDED(ret)) {
*plStride = lStride;
}
done:
return ret;
}
static bool MEDIAFOUNDATION_OpenDevice(SDL_Camera *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;
#if 0#endif
LONG lstride = 0;
HRESULT ret;
#if 0 #endif
#if DEBUG_CAMERA
SDL_Log("CAMERA: opening device with symlink of '%s'", utf8symlink);
#endif
wstrsymlink = WIN_UTF8ToStringW(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, &SDL_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, &SDL_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
CHECK_HRESULT("IMFAttributes_SetGUID(srctype)", ret);
ret = IMFAttributes_SetString(attrs, &SDL_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;
ret = pMFCreateSourceReaderFromMediaSource(source, NULL, &srcreader);
CHECK_HRESULT("MFCreateSourceReaderFromMediaSource", ret);
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->framerate_numerator) << 32) | ((UINT64)spec->framerate_denominator));
CHECK_HRESULT("MFSetAttributeRatio(frame_rate)", ret);
ret = IMFSourceReader_SetCurrentMediaType(srcreader, (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL, mediatype);
CHECK_HRESULT("IMFSourceReader_SetCurrentMediaType", ret);
#if 0 #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);
SDL_CameraPermissionOutcome(device, true);
#undef CHECK_HRESULT
return true;
failed:
if (srcreader) {
IMFSourceReader_Release(srcreader);
}
#if 0 #endif
if (source) {
IMFMediaSource_Shutdown(source);
IMFMediaSource_Release(source);
}
if (mediatype) {
IMFMediaType_Release(mediatype);
}
if (attrs) {
IMFAttributes_Release(attrs);
}
SDL_free(wstrsymlink);
return false;
}
static void MEDIAFOUNDATION_FreeDeviceHandle(SDL_Camera *device)
{
if (device) {
SDL_free(device->handle); }
}
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_StringToUTF8W(wstr);
CoTaskMemFree(wstr);
return utf8str;
}
static void GatherCameraSpecs(IMFMediaSource *source, CameraFormatAddData *add_data)
{
HRESULT ret;
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)) {
SDL_PixelFormat sdlfmt = SDL_PIXELFORMAT_UNKNOWN;
SDL_Colorspace colorspace = SDL_COLORSPACE_UNKNOWN;
MediaTypeToSDLFmt(mediatype, &sdlfmt, &colorspace);
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 framerate_numerator = 0, framerate_denominator = 0;
ret = IMFMediaType_GetUINT64(mediatype, &SDL_MF_MT_FRAME_RATE, &val);
framerate_numerator = (UINT32)(val >> 32);
framerate_denominator = (UINT32)val;
if (SUCCEEDED(ret) && framerate_numerator && framerate_denominator) {
SDL_AddCameraFormat(add_data, sdlfmt, colorspace, (int) w, (int) h, (int)framerate_numerator, (int)framerate_denominator);
}
}
}
}
IMFMediaType_Release(mediatype);
}
}
IMFMediaTypeHandler_Release(handler);
}
}
IMFStreamDescriptor_Release(streamdesc);
}
IMFPresentationDescriptor_Release(presentdesc);
}
static bool FindMediaFoundationCameraBySymlink(SDL_Camera *device, void *userdata)
{
return (SDL_strcmp((const char *) device->handle, (const char *) userdata) == 0);
}
static void MaybeAddDevice(IMFActivate *activation)
{
char *symlink = QueryActivationObjectString(activation, &SDL_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK);
if (SDL_FindPhysicalCameraByCallback(FindMediaFoundationCameraBySymlink, symlink)) {
SDL_free(symlink);
return; }
char *name = QueryActivationObjectString(activation, &SDL_MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME);
if (name && symlink) {
IMFMediaSource *source = NULL;
HRESULT ret = IMFActivate_ActivateObject(activation, &SDL_IID_IMFMediaSource, (void **)&source);
if (SUCCEEDED(ret) && source) {
CameraFormatAddData add_data;
GatherCameraSpecs(source, &add_data);
if (add_data.num_specs > 0) {
SDL_AddCamera(name, SDL_CAMERA_POSITION_UNKNOWN, 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)
{
HRESULT ret;
IMFAttributes *attrs = NULL;
ret = pMFCreateAttributes(&attrs, 1);
if (FAILED(ret)) {
return; }
ret = IMFAttributes_SetGUID(attrs, &SDL_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, &SDL_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
if (FAILED(ret)) {
IMFAttributes_Release(attrs);
return; }
IMFActivate **activations = NULL;
UINT32 total = 0;
ret = pMFEnumDeviceSources(attrs, &activations, &total);
IMFAttributes_Release(attrs);
if (FAILED(ret)) {
return; }
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 bool MEDIAFOUNDATION_Init(SDL_CameraDriverImpl *impl)
{
HMODULE mf = LoadLibrary(TEXT("Mf.dll")); if (!mf) {
return false;
}
HMODULE mfplat = LoadLibrary(TEXT("Mfplat.dll")); if (!mfplat) {
FreeLibrary(mf);
return false;
}
HMODULE mfreadwrite = LoadLibrary(TEXT("Mfreadwrite.dll")); if (!mfreadwrite) {
FreeLibrary(mfplat);
FreeLibrary(mf);
return false;
}
bool okay = true;
#define LOADSYM(lib, fn) if (okay) { p##fn = (pfn##fn) GetProcAddress(lib, #fn); if (!p##fn) { okay = 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) {
const HRESULT ret = pMFStartup(MF_VERSION, MFSTARTUP_LITE);
if (FAILED(ret)) {
okay = false;
}
}
if (!okay) {
FreeLibrary(mfreadwrite);
FreeLibrary(mfplat);
FreeLibrary(mf);
return false;
}
libmf = mf;
libmfplat = mfplat;
libmfreadwrite = mfreadwrite;
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 true;
}
CameraBootStrap MEDIAFOUNDATION_bootstrap = {
"mediafoundation", "SDL Windows Media Foundation camera driver", MEDIAFOUNDATION_Init, false
};
#endif