Added support for Motion JPEG camera capture

Fixes https://github.com/libsdl-org/SDL/issues/12183
This commit is contained in:
Sam Lantinga
2025-02-07 08:27:37 -08:00
parent 3bc53b9ade
commit 84b0c13c44
16 changed files with 8267 additions and 59 deletions

View File

@@ -76,6 +76,7 @@ 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__
@@ -102,7 +103,8 @@ static const struct
{ &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_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)
@@ -296,6 +298,13 @@ static void MediaTypeToSDLFmt(IMFMediaType *mediatype, SDL_PixelFormat *format,
}
}
}
#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;
}
@@ -467,7 +476,11 @@ static SDL_CameraFrameResult MEDIAFOUNDATION_AcquireFrame(SDL_Camera *device, SD
CleanupIMF2DBuffer2(NULL, objs);
} else {
frame->pixels = pixels;
frame->pitch = (int) pitch;
if (frame->format == SDL_PIXELFORMAT_MJPG) {
frame->pitch = (int)buflen;
} else {
frame->pitch = (int)pitch;
}
if (!SDL_SetPointerPropertyWithCleanup(surfprops, PROP_SURFACE_IMFOBJS_POINTER, objs, CleanupIMF2DBuffer2, NULL)) {
result = SDL_CAMERA_FRAME_ERROR;
}
@@ -491,9 +504,13 @@ static SDL_CameraFrameResult MEDIAFOUNDATION_AcquireFrame(SDL_Camera *device, SD
CleanupIMFMediaBuffer(NULL, objs);
result = SDL_CAMERA_FRAME_ERROR;
} else {
pitch = (LONG) device->hidden->pitch;
if (pitch < 0) { // image rows are reversed.
pixels += -pitch * (frame->h - 1);
if (frame->format == SDL_PIXELFORMAT_MJPG) {
pitch = (LONG)currentlen;
} 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;
@@ -522,6 +539,18 @@ static void MEDIAFOUNDATION_ReleaseFrame(SDL_Camera *device, SDL_Surface *frame)
#else
static SDL_CameraFrameResult MEDIAFOUNDATION_CopyFrame(SDL_Surface *frame, const void *pixels, LONG pitch, DWORD buflen)
{
frame->pixels = SDL_aligned_alloc(SDL_GetSIMDAlignment(), buflen);
if (!frame->pixels) {
return SDL_CAMERA_FRAME_ERROR;
}
SDL_memcpy(frame->pixels, pixels, buflen);
frame->pitch = (int)pitch;
return SDL_CAMERA_FRAME_READY;
}
static SDL_CameraFrameResult MEDIAFOUNDATION_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS)
{
SDL_assert(device->hidden->current_sample != NULL);
@@ -563,12 +592,10 @@ static SDL_CameraFrameResult MEDIAFOUNDATION_AcquireFrame(SDL_Camera *device, SD
if (FAILED(ret)) {
result = SDL_CAMERA_FRAME_ERROR;
} else {
frame->pixels = SDL_aligned_alloc(SDL_GetSIMDAlignment(), buflen);
if (frame->pixels == NULL) {
result = SDL_CAMERA_FRAME_ERROR;
if (frame->format == SDL_PIXELFORMAT_MJPG) {
result = MEDIAFOUNDATION_CopyFrame(frame, pixels, (LONG)buflen, buflen);
} else {
SDL_memcpy(frame->pixels, pixels, buflen);
frame->pitch = (int)pitch;
result = MEDIAFOUNDATION_CopyFrame(frame, pixels, pitch, buflen);
}
IMF2DBuffer2_Unlock2D(buffer2d2);
}
@@ -578,17 +605,17 @@ static SDL_CameraFrameResult MEDIAFOUNDATION_AcquireFrame(SDL_Camera *device, SD
if (FAILED(ret)) {
result = SDL_CAMERA_FRAME_ERROR;
} 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_GetSIMDAlignment(), buflen);
if (frame->pixels == NULL) {
result = SDL_CAMERA_FRAME_ERROR;
if (frame->format == SDL_PIXELFORMAT_MJPG) {
// FIXME: How big is this frame?
const DWORD buflen = 0;
result = MEDIAFOUNDATION_CopyFrame(frame, pixels, pitch, buflen);
} else {
SDL_memcpy(frame->pixels, bufstart, buflen);
frame->pitch = (int)pitch;
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);
}
result = MEDIAFOUNDATION_CopyFrame(frame, bufstart, pitch, buflen);
}
IMF2DBuffer_Unlock2D(buffer2d);
}
@@ -599,18 +626,16 @@ static SDL_CameraFrameResult MEDIAFOUNDATION_AcquireFrame(SDL_Camera *device, SD
if (FAILED(ret)) {
result = SDL_CAMERA_FRAME_ERROR;
} 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_GetSIMDAlignment(), buflen);
if (frame->pixels == NULL) {
result = SDL_CAMERA_FRAME_ERROR;
if (frame->format == SDL_PIXELFORMAT_MJPG) {
result = MEDIAFOUNDATION_CopyFrame(frame, pixels, (LONG)currentlen, currentlen);
} else {
SDL_memcpy(frame->pixels, bufstart, buflen);
frame->pitch = (int)pitch;
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);
}
result = MEDIAFOUNDATION_CopyFrame(frame, bufstart, pitch, buflen);
}
IMFMediaBuffer_Unlock(buffer);
}

View File

@@ -128,10 +128,11 @@ static SDL_CameraFrameResult V4L2_AcquireFrame(SDL_Camera *device, SDL_Surface *
const io_method io = device->hidden->io;
size_t size = device->hidden->buffers[0].length;
struct v4l2_buffer buf;
ssize_t amount;
switch (io) {
case IO_METHOD_READ:
if (read(fd, device->hidden->buffers[0].start, size) == -1) {
if ((amount = read(fd, device->hidden->buffers[0].start, size)) == -1) {
switch (errno) {
case EAGAIN:
return SDL_CAMERA_FRAME_SKIP;
@@ -148,7 +149,11 @@ static SDL_CameraFrameResult V4L2_AcquireFrame(SDL_Camera *device, SDL_Surface *
*timestampNS = SDL_GetTicksNS(); // oh well, close enough.
frame->pixels = device->hidden->buffers[0].start;
frame->pitch = device->hidden->driver_pitch;
if (device->hidden->driver_pitch) {
frame->pitch = device->hidden->driver_pitch;
} else {
frame->pitch = (int)amount;
}
break;
case IO_METHOD_MMAP:
@@ -178,7 +183,11 @@ static SDL_CameraFrameResult V4L2_AcquireFrame(SDL_Camera *device, SDL_Surface *
}
frame->pixels = device->hidden->buffers[buf.index].start;
frame->pitch = device->hidden->driver_pitch;
if (device->hidden->driver_pitch) {
frame->pitch = device->hidden->driver_pitch;
} else {
frame->pitch = buf.bytesused;
}
device->hidden->buffers[buf.index].available = 1;
*timestampNS = (((Uint64) buf.timestamp.tv_sec) * SDL_NS_PER_SECOND) + SDL_US_TO_NS(buf.timestamp.tv_usec);
@@ -222,7 +231,11 @@ static SDL_CameraFrameResult V4L2_AcquireFrame(SDL_Camera *device, SDL_Surface *
}
frame->pixels = (void*)buf.m.userptr;
frame->pitch = device->hidden->driver_pitch;
if (device->hidden->driver_pitch) {
frame->pitch = device->hidden->driver_pitch;
} else {
frame->pitch = buf.bytesused;
}
device->hidden->buffers[i].available = 1;
*timestampNS = (((Uint64) buf.timestamp.tv_sec) * SDL_NS_PER_SECOND) + SDL_US_TO_NS(buf.timestamp.tv_usec);
@@ -404,10 +417,15 @@ static void format_v4l2_to_sdl(Uint32 fmt, SDL_PixelFormat *format, SDL_Colorspa
switch (fmt) {
#define CASE(x, y, z) case x: *format = y; *colorspace = z; return
CASE(V4L2_PIX_FMT_YUYV, SDL_PIXELFORMAT_YUY2, SDL_COLORSPACE_BT709_LIMITED);
CASE(V4L2_PIX_FMT_MJPEG, SDL_PIXELFORMAT_MJPG, SDL_COLORSPACE_SRGB);
#undef CASE
default:
#if DEBUG_CAMERA
SDL_Log("CAMERA: Unknown format V4L2_PIX_FORMAT '%d'", fmt);
SDL_Log("CAMERA: Unknown format V4L2_PIX_FORMAT '%c%c%c%c' (0x%x)",
(char)(Uint8)(fmt >> 0),
(char)(Uint8)(fmt >> 8),
(char)(Uint8)(fmt >> 16),
(char)(Uint8)(fmt >> 24), fmt);
#endif
break;
}