SDL: Added support for Motion JPEG pipewire camera capture

From 75bbcbf87b0701b03195e3c3dd6a869fd9f9d560 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sat, 22 Feb 2025 00:07:33 -0800
Subject: [PATCH] Added support for Motion JPEG pipewire camera capture

---
 src/camera/pipewire/SDL_camera_pipewire.c | 80 +++++++++++++++++++----
 1 file changed, 66 insertions(+), 14 deletions(-)

diff --git a/src/camera/pipewire/SDL_camera_pipewire.c b/src/camera/pipewire/SDL_camera_pipewire.c
index 6133578a30d13..4de2f2346cadc 100644
--- a/src/camera/pipewire/SDL_camera_pipewire.c
+++ b/src/camera/pipewire/SDL_camera_pipewire.c
@@ -374,10 +374,8 @@ static struct sdl_video_format {
     { SDL_PIXELFORMAT_YUY2, SDL_COLORSPACE_BT709_LIMITED, SPA_VIDEO_FORMAT_YUY2 },
     { SDL_PIXELFORMAT_UYVY, SDL_COLORSPACE_BT709_LIMITED, SPA_VIDEO_FORMAT_UYVY },
     { SDL_PIXELFORMAT_YVYU, SDL_COLORSPACE_BT709_LIMITED, SPA_VIDEO_FORMAT_YVYU },
-#if SDL_VERSION_ATLEAST(2,0,4)
     { SDL_PIXELFORMAT_NV12, SDL_COLORSPACE_BT709_LIMITED, SPA_VIDEO_FORMAT_NV12 },
-    { SDL_PIXELFORMAT_NV21, SDL_COLORSPACE_BT709_LIMITED, SPA_VIDEO_FORMAT_NV21 },
-#endif
+    { SDL_PIXELFORMAT_NV21, SDL_COLORSPACE_BT709_LIMITED, SPA_VIDEO_FORMAT_NV21 }
 };
 
 static uint32_t sdl_format_to_id(SDL_PixelFormat format)
@@ -500,14 +498,24 @@ static bool PIPEWIRECAMERA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *
                     &device->hidden->stream_listener,
                     &stream_events, device);
 
-    params[n_params++] = spa_pod_builder_add_object(&b,
-		    SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
-		    SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video),
-                    SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
-                    SPA_FORMAT_VIDEO_format, SPA_POD_Id(sdl_format_to_id(spec->format)),
-                    SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&SPA_RECTANGLE(spec->width, spec->height)),
-                    SPA_FORMAT_VIDEO_framerate,
-		        SPA_POD_Fraction(&SPA_FRACTION(spec->framerate_numerator, spec->framerate_denominator)));
+    if (spec->format == SDL_PIXELFORMAT_MJPG) {
+        params[n_params++] = spa_pod_builder_add_object(&b,
+                SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
+                SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video),
+                        SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_mjpg),
+                        SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&SPA_RECTANGLE(spec->width, spec->height)),
+                        SPA_FORMAT_VIDEO_framerate,
+                    SPA_POD_Fraction(&SPA_FRACTION(spec->framerate_numerator, spec->framerate_denominator)));
+    } else {
+        params[n_params++] = spa_pod_builder_add_object(&b,
+                SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
+                SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video),
+                        SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
+                        SPA_FORMAT_VIDEO_format, SPA_POD_Id(sdl_format_to_id(spec->format)),
+                        SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&SPA_RECTANGLE(spec->width, spec->height)),
+                        SPA_FORMAT_VIDEO_framerate,
+                    SPA_POD_Fraction(&SPA_FRACTION(spec->framerate_numerator, spec->framerate_denominator)));
+    }
 
     if ((res = PIPEWIRE_pw_stream_connect(device->hidden->stream,
                                     PW_DIRECTION_INPUT,
@@ -573,7 +581,11 @@ static SDL_CameraFrameResult PIPEWIRECAMERA_AcquireFrame(SDL_Camera *device, SDL
     *timestampNS = SDL_GetTicksNS();
 #endif
     frame->pixels = b->buffer->datas[0].data;
-    frame->pitch = b->buffer->datas[0].chunk->stride;
+    if (frame->format == SDL_PIXELFORMAT_MJPG) {
+        frame->pitch = b->buffer->datas[0].chunk->size;
+    } else {
+        frame->pitch = b->buffer->datas[0].chunk->stride;
+    }
 
     PIPEWIRE_pw_thread_loop_unlock(hotplug.loop);
 
@@ -657,7 +669,7 @@ static void collect_size(CameraFormatAddData *data, struct param *p, SDL_PixelFo
     }
 }
 
-static void collect_format(CameraFormatAddData *data, struct param *p)
+static void collect_raw(CameraFormatAddData *data, struct param *p)
 {
     const struct spa_pod_prop *prop;
     SDL_PixelFormat sdlfmt;
@@ -688,7 +700,47 @@ static void collect_format(CameraFormatAddData *data, struct param *p)
         }
         break;
     default:
-        SDL_Log("CAMERA: unimplemented choice:%d", choice);
+        SDL_Log("CAMERA: unimplemented choice: %d", choice);
+        break;
+    }
+}
+
+static void collect_format(CameraFormatAddData *data, struct param *p)
+{
+    const struct spa_pod_prop *prop;
+    struct spa_pod * values;
+    uint32_t i, n_vals, choice, *ids;
+
+    prop = spa_pod_find_prop(p->param, NULL, SPA_FORMAT_mediaSubtype);
+    if (prop == NULL)
+        return;
+
+    values = spa_pod_get_values(&prop->value, &n_vals, &choice);
+    if (values->type != SPA_TYPE_Id || n_vals == 0)
+        return;
+
+    ids = SPA_POD_BODY(values);
+    switch (choice) {
+    case SPA_CHOICE_None:
+        n_vals = 1;
+    SDL_FALLTHROUGH;
+    case SPA_CHOICE_Enum:
+        for (i = 0; i < n_vals; i++) {
+            switch (ids[i]) {
+            case SPA_MEDIA_SUBTYPE_raw:
+                collect_raw(data, p);
+                break;
+            case SPA_MEDIA_SUBTYPE_mjpg:
+                collect_size(data, p, SDL_PIXELFORMAT_MJPG, SDL_COLORSPACE_JPEG);
+                break;
+            default:
+                // Unsupported format
+                break;
+            }
+        }
+        break;
+    default:
+        SDL_Log("CAMERA: unimplemented choice: %d", choice);
         break;
     }
 }