summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/camera/pipewire/SDL_camera_pipewire.c
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
committer3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
commit5a079a2d114f96d4847d1ee305d5b7c16eeec50e (patch)
tree8926ab44f168acf787d8e19608857b3af0f82758 /contrib/SDL-3.2.8/src/camera/pipewire/SDL_camera_pipewire.c
Initial commit
Diffstat (limited to 'contrib/SDL-3.2.8/src/camera/pipewire/SDL_camera_pipewire.c')
-rw-r--r--contrib/SDL-3.2.8/src/camera/pipewire/SDL_camera_pipewire.c1144
1 files changed, 1144 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/camera/pipewire/SDL_camera_pipewire.c b/contrib/SDL-3.2.8/src/camera/pipewire/SDL_camera_pipewire.c
new file mode 100644
index 0000000..5d868bf
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/camera/pipewire/SDL_camera_pipewire.c
@@ -0,0 +1,1144 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4 Copyright (C) 2024 Wim Taymans <wtaymans@redhat.com>
5
6 This software is provided 'as-is', without any express or implied
7 warranty. In no event will the authors be held liable for any damages
8 arising from the use of this software.
9
10 Permission is granted to anyone to use this software for any purpose,
11 including commercial applications, and to alter it and redistribute it
12 freely, subject to the following restrictions:
13
14 1. The origin of this software must not be misrepresented; you must not
15 claim that you wrote the original software. If you use this software
16 in a product, an acknowledgment in the product documentation would be
17 appreciated but is not required.
18 2. Altered source versions must be plainly marked as such, and must not be
19 misrepresented as being the original software.
20 3. This notice may not be removed or altered from any source distribution.
21*/
22#include "SDL_internal.h"
23
24#ifdef SDL_CAMERA_DRIVER_PIPEWIRE
25
26#include "../SDL_syscamera.h"
27
28#include <spa/utils/type.h>
29#include <spa/pod/builder.h>
30#include <spa/pod/iter.h>
31#include <spa/param/video/raw.h>
32#include <spa/param/video/format.h>
33#include <spa/utils/result.h>
34#include <spa/utils/json.h>
35
36#include <pipewire/pipewire.h>
37#include <pipewire/extensions/metadata.h>
38
39#define PW_POD_BUFFER_LENGTH 1024
40#define PW_THREAD_NAME_BUFFER_LENGTH 128
41#define PW_MAX_IDENTIFIER_LENGTH 256
42
43#define PW_REQUIRED_MAJOR 1
44#define PW_REQUIRED_MINOR 0
45#define PW_REQUIRED_PATCH 0
46
47enum PW_READY_FLAGS
48{
49 PW_READY_FLAG_BUFFER_ADDED = 0x1,
50 PW_READY_FLAG_STREAM_READY = 0x2,
51 PW_READY_FLAG_ALL_BITS = 0x3
52};
53
54#define PW_ID_TO_HANDLE(x) (void *)((uintptr_t)x)
55#define PW_HANDLE_TO_ID(x) (uint32_t)((uintptr_t)x)
56
57static bool pipewire_initialized = false;
58
59// Pipewire entry points
60static const char *(*PIPEWIRE_pw_get_library_version)(void);
61#if PW_CHECK_VERSION(0, 3, 75)
62static bool (*PIPEWIRE_pw_check_library_version)(int major, int minor, int micro);
63#endif
64static void (*PIPEWIRE_pw_init)(int *, char ***);
65static void (*PIPEWIRE_pw_deinit)(void);
66static struct pw_main_loop *(*PIPEWIRE_pw_main_loop_new)(const struct spa_dict *loop);
67static struct pw_loop *(*PIPEWIRE_pw_main_loop_get_loop)(struct pw_main_loop *loop);
68static int (*PIPEWIRE_pw_main_loop_run)(struct pw_main_loop *loop);
69static int (*PIPEWIRE_pw_main_loop_quit)(struct pw_main_loop *loop);
70static void(*PIPEWIRE_pw_main_loop_destroy)(struct pw_main_loop *loop);
71static struct pw_thread_loop *(*PIPEWIRE_pw_thread_loop_new)(const char *, const struct spa_dict *);
72static void (*PIPEWIRE_pw_thread_loop_destroy)(struct pw_thread_loop *);
73static void (*PIPEWIRE_pw_thread_loop_stop)(struct pw_thread_loop *);
74static struct pw_loop *(*PIPEWIRE_pw_thread_loop_get_loop)(struct pw_thread_loop *);
75static void (*PIPEWIRE_pw_thread_loop_lock)(struct pw_thread_loop *);
76static void (*PIPEWIRE_pw_thread_loop_unlock)(struct pw_thread_loop *);
77static void (*PIPEWIRE_pw_thread_loop_signal)(struct pw_thread_loop *, bool);
78static void (*PIPEWIRE_pw_thread_loop_wait)(struct pw_thread_loop *);
79static int (*PIPEWIRE_pw_thread_loop_start)(struct pw_thread_loop *);
80static struct pw_context *(*PIPEWIRE_pw_context_new)(struct pw_loop *, struct pw_properties *, size_t);
81static void (*PIPEWIRE_pw_context_destroy)(struct pw_context *);
82static struct pw_core *(*PIPEWIRE_pw_context_connect)(struct pw_context *, struct pw_properties *, size_t);
83static void (*PIPEWIRE_pw_proxy_add_object_listener)(struct pw_proxy *, struct spa_hook *, const void *, void *);
84static void (*PIPEWIRE_pw_proxy_add_listener)(struct pw_proxy *, struct spa_hook *, const struct pw_proxy_events *, void *);
85static void *(*PIPEWIRE_pw_proxy_get_user_data)(struct pw_proxy *);
86static void (*PIPEWIRE_pw_proxy_destroy)(struct pw_proxy *);
87static int (*PIPEWIRE_pw_core_disconnect)(struct pw_core *);
88static struct pw_node_info * (*PIPEWIRE_pw_node_info_merge)(struct pw_node_info *info, const struct pw_node_info *update, bool reset);
89static void (*PIPEWIRE_pw_node_info_free)(struct pw_node_info *info);
90static struct pw_stream *(*PIPEWIRE_pw_stream_new)(struct pw_core *, const char *, struct pw_properties *);
91static void (*PIPEWIRE_pw_stream_add_listener)(struct pw_stream *stream, struct spa_hook *listener, const struct pw_stream_events *events, void *data);
92static void (*PIPEWIRE_pw_stream_destroy)(struct pw_stream *);
93static int (*PIPEWIRE_pw_stream_connect)(struct pw_stream *, enum pw_direction, uint32_t, enum pw_stream_flags,
94 const struct spa_pod **, uint32_t);
95static enum pw_stream_state (*PIPEWIRE_pw_stream_get_state)(struct pw_stream *stream, const char **error);
96static struct pw_buffer *(*PIPEWIRE_pw_stream_dequeue_buffer)(struct pw_stream *);
97static int (*PIPEWIRE_pw_stream_queue_buffer)(struct pw_stream *, struct pw_buffer *);
98static struct pw_properties *(*PIPEWIRE_pw_properties_new)(const char *, ...)SPA_SENTINEL;
99static struct pw_properties *(*PIPEWIRE_pw_properties_new_dict)(const struct spa_dict *dict);
100static int (*PIPEWIRE_pw_properties_set)(struct pw_properties *, const char *, const char *);
101static int (*PIPEWIRE_pw_properties_setf)(struct pw_properties *, const char *, const char *, ...) SPA_PRINTF_FUNC(3, 4);
102
103#ifdef SDL_CAMERA_DRIVER_PIPEWIRE_DYNAMIC
104
105static const char *pipewire_library = SDL_CAMERA_DRIVER_PIPEWIRE_DYNAMIC;
106static SDL_SharedObject *pipewire_handle = NULL;
107
108static bool pipewire_dlsym(const char *fn, void **addr)
109{
110 *addr = SDL_LoadFunction(pipewire_handle, fn);
111 if (!*addr) {
112 // Don't call SDL_SetError(): SDL_LoadFunction already did.
113 return false;
114 }
115
116 return true;
117}
118
119#define SDL_PIPEWIRE_SYM(x) \
120 if (!pipewire_dlsym(#x, (void **)(char *)&PIPEWIRE_##x)) \
121 return false
122
123static bool load_pipewire_library(void)
124{
125 pipewire_handle = SDL_LoadObject(pipewire_library);
126 return pipewire_handle ? true : false;
127}
128
129static void unload_pipewire_library(void)
130{
131 if (pipewire_handle) {
132 SDL_UnloadObject(pipewire_handle);
133 pipewire_handle = NULL;
134 }
135}
136
137#else
138
139#define SDL_PIPEWIRE_SYM(x) PIPEWIRE_##x = x
140
141static bool load_pipewire_library(void)
142{
143 return true;
144}
145
146static void unload_pipewire_library(void)
147{
148 // Nothing to do
149}
150
151#endif // SDL_CAMERA_DRIVER_PIPEWIRE_DYNAMIC
152
153static bool load_pipewire_syms(void)
154{
155 SDL_PIPEWIRE_SYM(pw_get_library_version);
156#if PW_CHECK_VERSION(0, 3, 75)
157 SDL_PIPEWIRE_SYM(pw_check_library_version);
158#endif
159 SDL_PIPEWIRE_SYM(pw_init);
160 SDL_PIPEWIRE_SYM(pw_deinit);
161 SDL_PIPEWIRE_SYM(pw_main_loop_new);
162 SDL_PIPEWIRE_SYM(pw_main_loop_get_loop);
163 SDL_PIPEWIRE_SYM(pw_main_loop_run);
164 SDL_PIPEWIRE_SYM(pw_main_loop_quit);
165 SDL_PIPEWIRE_SYM(pw_main_loop_destroy);
166 SDL_PIPEWIRE_SYM(pw_thread_loop_new);
167 SDL_PIPEWIRE_SYM(pw_thread_loop_destroy);
168 SDL_PIPEWIRE_SYM(pw_thread_loop_stop);
169 SDL_PIPEWIRE_SYM(pw_thread_loop_get_loop);
170 SDL_PIPEWIRE_SYM(pw_thread_loop_lock);
171 SDL_PIPEWIRE_SYM(pw_thread_loop_unlock);
172 SDL_PIPEWIRE_SYM(pw_thread_loop_signal);
173 SDL_PIPEWIRE_SYM(pw_thread_loop_wait);
174 SDL_PIPEWIRE_SYM(pw_thread_loop_start);
175 SDL_PIPEWIRE_SYM(pw_context_new);
176 SDL_PIPEWIRE_SYM(pw_context_destroy);
177 SDL_PIPEWIRE_SYM(pw_context_connect);
178 SDL_PIPEWIRE_SYM(pw_proxy_add_listener);
179 SDL_PIPEWIRE_SYM(pw_proxy_add_object_listener);
180 SDL_PIPEWIRE_SYM(pw_proxy_get_user_data);
181 SDL_PIPEWIRE_SYM(pw_proxy_destroy);
182 SDL_PIPEWIRE_SYM(pw_core_disconnect);
183 SDL_PIPEWIRE_SYM(pw_node_info_merge);
184 SDL_PIPEWIRE_SYM(pw_node_info_free);
185 SDL_PIPEWIRE_SYM(pw_stream_new);
186 SDL_PIPEWIRE_SYM(pw_stream_add_listener);
187 SDL_PIPEWIRE_SYM(pw_stream_destroy);
188 SDL_PIPEWIRE_SYM(pw_stream_connect);
189 SDL_PIPEWIRE_SYM(pw_stream_get_state);
190 SDL_PIPEWIRE_SYM(pw_stream_dequeue_buffer);
191 SDL_PIPEWIRE_SYM(pw_stream_queue_buffer);
192 SDL_PIPEWIRE_SYM(pw_properties_new);
193 SDL_PIPEWIRE_SYM(pw_properties_new_dict);
194 SDL_PIPEWIRE_SYM(pw_properties_set);
195 SDL_PIPEWIRE_SYM(pw_properties_setf);
196
197 return true;
198}
199
200static bool init_pipewire_library(void)
201{
202 if (load_pipewire_library()) {
203 if (load_pipewire_syms()) {
204 PIPEWIRE_pw_init(NULL, NULL);
205 return true;
206 }
207 }
208 return false;
209}
210
211static void deinit_pipewire_library(void)
212{
213 PIPEWIRE_pw_deinit();
214 unload_pipewire_library();
215}
216
217// The global hotplug thread and associated objects.
218static struct
219{
220 struct pw_thread_loop *loop;
221
222 struct pw_context *context;
223
224 struct pw_core *core;
225 struct spa_hook core_listener;
226 int server_major;
227 int server_minor;
228 int server_patch;
229 int last_seq;
230 int pending_seq;
231
232 struct pw_registry *registry;
233 struct spa_hook registry_listener;
234
235 struct spa_list global_list;
236
237 bool have_1_0_5;
238 bool init_complete;
239 bool events_enabled;
240} hotplug;
241
242struct global
243{
244 struct spa_list link;
245
246 const struct global_class *class;
247
248 uint32_t id;
249 uint32_t permissions;
250 struct pw_properties *props;
251
252 char *name;
253
254 struct pw_proxy *proxy;
255 struct spa_hook proxy_listener;
256 struct spa_hook object_listener;
257
258 int changed;
259 void *info;
260 struct spa_list pending_list;
261 struct spa_list param_list;
262
263 bool added;
264};
265
266struct global_class
267{
268 const char *type;
269 uint32_t version;
270 const void *events;
271 int (*init) (struct global *g);
272 void (*destroy) (struct global *g);
273};
274
275struct param {
276 uint32_t id;
277 int32_t seq;
278 struct spa_list link;
279 struct spa_pod *param;
280};
281
282static uint32_t param_clear(struct spa_list *param_list, uint32_t id)
283{
284 struct param *p, *t;
285 uint32_t count = 0;
286
287 spa_list_for_each_safe(p, t, param_list, link) {
288 if (id == SPA_ID_INVALID || p->id == id) {
289 spa_list_remove(&p->link);
290 free(p); // This should NOT be SDL_free()
291 count++;
292 }
293 }
294 return count;
295}
296
297#if PW_CHECK_VERSION(0,3,60)
298#define SPA_PARAMS_INFO_SEQ(p) ((p).seq)
299#else
300#define SPA_PARAMS_INFO_SEQ(p) ((p).padding[0])
301#endif
302
303static struct param *param_add(struct spa_list *params,
304 int seq, uint32_t id, const struct spa_pod *param)
305{
306 struct param *p;
307
308 if (id == SPA_ID_INVALID) {
309 if (param == NULL || !spa_pod_is_object(param)) {
310 errno = EINVAL;
311 return NULL;
312 }
313 id = SPA_POD_OBJECT_ID(param);
314 }
315
316 p = malloc(sizeof(*p) + (param != NULL ? SPA_POD_SIZE(param) : 0));
317 if (p == NULL)
318 return NULL;
319
320 p->id = id;
321 p->seq = seq;
322 if (param != NULL) {
323 p->param = SPA_PTROFF(p, sizeof(*p), struct spa_pod);
324 SDL_memcpy(p->param, param, SPA_POD_SIZE(param));
325 } else {
326 param_clear(params, id);
327 p->param = NULL;
328 }
329 spa_list_append(params, &p->link);
330
331 return p;
332}
333
334static void param_update(struct spa_list *param_list, struct spa_list *pending_list,
335 uint32_t n_params, struct spa_param_info *params)
336{
337 struct param *p, *t;
338 uint32_t i;
339
340 for (i = 0; i < n_params; i++) {
341 spa_list_for_each_safe(p, t, pending_list, link) {
342 if (p->id == params[i].id &&
343 p->seq != SPA_PARAMS_INFO_SEQ(params[i]) &&
344 p->param != NULL) {
345 spa_list_remove(&p->link);
346 free(p); // This should NOT be SDL_free()
347 }
348 }
349 }
350 spa_list_consume(p, pending_list, link) {
351 spa_list_remove(&p->link);
352 if (p->param == NULL) {
353 param_clear(param_list, p->id);
354 free(p); // This should NOT be SDL_free()
355 } else {
356 spa_list_append(param_list, &p->link);
357 }
358 }
359}
360
361static struct sdl_video_format {
362 SDL_PixelFormat format;
363 SDL_Colorspace colorspace;
364 uint32_t id;
365} sdl_video_formats[] = {
366 { SDL_PIXELFORMAT_RGBX32, SDL_COLORSPACE_SRGB, SPA_VIDEO_FORMAT_RGBx },
367 { SDL_PIXELFORMAT_XRGB32, SDL_COLORSPACE_SRGB, SPA_VIDEO_FORMAT_xRGB },
368 { SDL_PIXELFORMAT_BGRX32, SDL_COLORSPACE_SRGB, SPA_VIDEO_FORMAT_BGRx },
369 { SDL_PIXELFORMAT_XBGR32, SDL_COLORSPACE_SRGB, SPA_VIDEO_FORMAT_xBGR },
370 { SDL_PIXELFORMAT_RGBA32, SDL_COLORSPACE_SRGB, SPA_VIDEO_FORMAT_RGBA },
371 { SDL_PIXELFORMAT_ARGB32, SDL_COLORSPACE_SRGB, SPA_VIDEO_FORMAT_ARGB },
372 { SDL_PIXELFORMAT_BGRA32, SDL_COLORSPACE_SRGB, SPA_VIDEO_FORMAT_BGRA },
373 { SDL_PIXELFORMAT_ABGR32, SDL_COLORSPACE_SRGB, SPA_VIDEO_FORMAT_ABGR },
374 { SDL_PIXELFORMAT_RGB24, SDL_COLORSPACE_SRGB, SPA_VIDEO_FORMAT_RGB },
375 { SDL_PIXELFORMAT_BGR24, SDL_COLORSPACE_SRGB, SPA_VIDEO_FORMAT_BGR },
376 { SDL_PIXELFORMAT_YV12, SDL_COLORSPACE_BT709_LIMITED, SPA_VIDEO_FORMAT_YV12 },
377 { SDL_PIXELFORMAT_IYUV, SDL_COLORSPACE_BT709_LIMITED, SPA_VIDEO_FORMAT_I420 },
378 { SDL_PIXELFORMAT_YUY2, SDL_COLORSPACE_BT709_LIMITED, SPA_VIDEO_FORMAT_YUY2 },
379 { SDL_PIXELFORMAT_UYVY, SDL_COLORSPACE_BT709_LIMITED, SPA_VIDEO_FORMAT_UYVY },
380 { SDL_PIXELFORMAT_YVYU, SDL_COLORSPACE_BT709_LIMITED, SPA_VIDEO_FORMAT_YVYU },
381 { SDL_PIXELFORMAT_NV12, SDL_COLORSPACE_BT709_LIMITED, SPA_VIDEO_FORMAT_NV12 },
382 { SDL_PIXELFORMAT_NV21, SDL_COLORSPACE_BT709_LIMITED, SPA_VIDEO_FORMAT_NV21 }
383};
384
385static uint32_t sdl_format_to_id(SDL_PixelFormat format)
386{
387 struct sdl_video_format *f;
388 SPA_FOR_EACH_ELEMENT(sdl_video_formats, f) {
389 if (f->format == format)
390 return f->id;
391 }
392 return SPA_VIDEO_FORMAT_UNKNOWN;
393}
394
395static void id_to_sdl_format(uint32_t id, SDL_PixelFormat *format, SDL_Colorspace *colorspace)
396{
397 struct sdl_video_format *f;
398 SPA_FOR_EACH_ELEMENT(sdl_video_formats, f) {
399 if (f->id == id) {
400 *format = f->format;
401 *colorspace = f->colorspace;
402 return;
403 }
404 }
405 *format = SDL_PIXELFORMAT_UNKNOWN;
406 *colorspace = SDL_COLORSPACE_UNKNOWN;
407}
408
409struct SDL_PrivateCameraData
410{
411 struct pw_stream *stream;
412 struct spa_hook stream_listener;
413
414 struct pw_array buffers;
415};
416
417static void on_process(void *data)
418{
419 PIPEWIRE_pw_thread_loop_signal(hotplug.loop, false);
420}
421
422static void on_stream_state_changed(void *data, enum pw_stream_state old,
423 enum pw_stream_state state, const char *error)
424{
425 SDL_Camera *device = data;
426 switch (state) {
427 case PW_STREAM_STATE_UNCONNECTED:
428 break;
429 case PW_STREAM_STATE_STREAMING:
430 SDL_CameraPermissionOutcome(device, true);
431 break;
432 default:
433 break;
434 }
435}
436
437static void on_stream_param_changed(void *data, uint32_t id, const struct spa_pod *param)
438{
439}
440
441static void on_add_buffer(void *data, struct pw_buffer *buffer)
442{
443 SDL_Camera *device = data;
444 pw_array_add_ptr(&device->hidden->buffers, buffer);
445}
446
447static void on_remove_buffer(void *data, struct pw_buffer *buffer)
448{
449 SDL_Camera *device = data;
450 struct pw_buffer **p;
451 pw_array_for_each(p, &device->hidden->buffers) {
452 if (*p == buffer) {
453 pw_array_remove(&device->hidden->buffers, p);
454 return;
455 }
456 }
457}
458
459static const struct pw_stream_events stream_events = {
460 .version = PW_VERSION_STREAM_EVENTS,
461 .add_buffer = on_add_buffer,
462 .remove_buffer = on_remove_buffer,
463 .state_changed = on_stream_state_changed,
464 .param_changed = on_stream_param_changed,
465 .process = on_process,
466};
467
468static bool PIPEWIRECAMERA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec)
469{
470 struct pw_properties *props;
471 const struct spa_pod *params[3];
472 int res, n_params = 0;
473 uint8_t buffer[1024];
474 struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
475
476 if (!device) {
477 return false;
478 }
479 device->hidden = (struct SDL_PrivateCameraData *) SDL_calloc(1, sizeof (struct SDL_PrivateCameraData));
480 if (device->hidden == NULL) {
481 return false;
482 }
483 pw_array_init(&device->hidden->buffers, 64);
484
485 PIPEWIRE_pw_thread_loop_lock(hotplug.loop);
486
487 props = PIPEWIRE_pw_properties_new(PW_KEY_MEDIA_TYPE, "Video",
488 PW_KEY_MEDIA_CATEGORY, "Capture",
489 PW_KEY_MEDIA_ROLE, "Camera",
490 PW_KEY_TARGET_OBJECT, device->name,
491 NULL);
492 if (props == NULL) {
493 return false;
494 }
495
496 device->hidden->stream = PIPEWIRE_pw_stream_new(hotplug.core, "SDL PipeWire Camera", props);
497 if (device->hidden->stream == NULL) {
498 return false;
499 }
500
501 PIPEWIRE_pw_stream_add_listener(device->hidden->stream,
502 &device->hidden->stream_listener,
503 &stream_events, device);
504
505 if (spec->format == SDL_PIXELFORMAT_MJPG) {
506 params[n_params++] = spa_pod_builder_add_object(&b,
507 SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
508 SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video),
509 SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_mjpg),
510 SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&SPA_RECTANGLE(spec->width, spec->height)),
511 SPA_FORMAT_VIDEO_framerate,
512 SPA_POD_Fraction(&SPA_FRACTION(spec->framerate_numerator, spec->framerate_denominator)));
513 } else {
514 params[n_params++] = spa_pod_builder_add_object(&b,
515 SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
516 SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video),
517 SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
518 SPA_FORMAT_VIDEO_format, SPA_POD_Id(sdl_format_to_id(spec->format)),
519 SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&SPA_RECTANGLE(spec->width, spec->height)),
520 SPA_FORMAT_VIDEO_framerate,
521 SPA_POD_Fraction(&SPA_FRACTION(spec->framerate_numerator, spec->framerate_denominator)));
522 }
523
524 if ((res = PIPEWIRE_pw_stream_connect(device->hidden->stream,
525 PW_DIRECTION_INPUT,
526 PW_ID_ANY,
527 PW_STREAM_FLAG_AUTOCONNECT |
528 PW_STREAM_FLAG_MAP_BUFFERS,
529 params, n_params)) < 0) {
530 return false;
531 }
532
533 PIPEWIRE_pw_thread_loop_unlock(hotplug.loop);
534
535 return true;
536}
537
538static void PIPEWIRECAMERA_CloseDevice(SDL_Camera *device)
539{
540 if (!device) {
541 return;
542 }
543
544 PIPEWIRE_pw_thread_loop_lock(hotplug.loop);
545 if (device->hidden) {
546 if (device->hidden->stream)
547 PIPEWIRE_pw_stream_destroy(device->hidden->stream);
548 pw_array_clear(&device->hidden->buffers);
549 SDL_free(device->hidden);
550 device->hidden = NULL;
551 }
552 PIPEWIRE_pw_thread_loop_unlock(hotplug.loop);
553}
554
555static bool PIPEWIRECAMERA_WaitDevice(SDL_Camera *device)
556{
557 PIPEWIRE_pw_thread_loop_lock(hotplug.loop);
558 PIPEWIRE_pw_thread_loop_wait(hotplug.loop);
559 PIPEWIRE_pw_thread_loop_unlock(hotplug.loop);
560 return true;
561}
562
563static SDL_CameraFrameResult PIPEWIRECAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS)
564{
565 struct pw_buffer *b;
566
567 PIPEWIRE_pw_thread_loop_lock(hotplug.loop);
568 b = NULL;
569 while (true) {
570 struct pw_buffer *t;
571 if ((t = PIPEWIRE_pw_stream_dequeue_buffer(device->hidden->stream)) == NULL)
572 break;
573 if (b)
574 PIPEWIRE_pw_stream_queue_buffer(device->hidden->stream, b);
575 b = t;
576 }
577 if (b == NULL) {
578 PIPEWIRE_pw_thread_loop_unlock(hotplug.loop);
579 return SDL_CAMERA_FRAME_SKIP;
580 }
581
582#if PW_CHECK_VERSION(1,0,5)
583 *timestampNS = hotplug.have_1_0_5 ? b->time : SDL_GetTicksNS();
584#else
585 *timestampNS = SDL_GetTicksNS();
586#endif
587 frame->pixels = b->buffer->datas[0].data;
588 if (frame->format == SDL_PIXELFORMAT_MJPG) {
589 frame->pitch = b->buffer->datas[0].chunk->size;
590 } else {
591 frame->pitch = b->buffer->datas[0].chunk->stride;
592 }
593
594 PIPEWIRE_pw_thread_loop_unlock(hotplug.loop);
595
596 return SDL_CAMERA_FRAME_READY;
597}
598
599static void PIPEWIRECAMERA_ReleaseFrame(SDL_Camera *device, SDL_Surface *frame)
600{
601 struct pw_buffer **p;
602 PIPEWIRE_pw_thread_loop_lock(hotplug.loop);
603 pw_array_for_each(p, &device->hidden->buffers) {
604 if ((*p)->buffer->datas[0].data == frame->pixels) {
605 PIPEWIRE_pw_stream_queue_buffer(device->hidden->stream, (*p));
606 break;
607 }
608 }
609 PIPEWIRE_pw_thread_loop_unlock(hotplug.loop);
610}
611
612static void collect_rates(CameraFormatAddData *data, struct param *p, SDL_PixelFormat sdlfmt, SDL_Colorspace colorspace, const struct spa_rectangle *size)
613{
614 const struct spa_pod_prop *prop;
615 struct spa_pod * values;
616 uint32_t i, n_vals, choice;
617 struct spa_fraction *rates;
618
619 prop = spa_pod_find_prop(p->param, NULL, SPA_FORMAT_VIDEO_framerate);
620 if (prop == NULL)
621 return;
622
623 values = spa_pod_get_values(&prop->value, &n_vals, &choice);
624 if (values->type != SPA_TYPE_Fraction || n_vals == 0)
625 return;
626
627 rates = SPA_POD_BODY(values);
628 switch (choice) {
629 case SPA_CHOICE_None:
630 n_vals = 1;
631 SDL_FALLTHROUGH;
632 case SPA_CHOICE_Enum:
633 for (i = 0; i < n_vals; i++) {
634 if (!SDL_AddCameraFormat(data, sdlfmt, colorspace, size->width, size->height, rates[i].num, rates[i].denom)) {
635 return; // Probably out of memory; we'll go with what we have, if anything.
636 }
637 }
638 break;
639 default:
640 SDL_Log("CAMERA: unimplemented choice:%d", choice);
641 break;
642 }
643}
644
645static void collect_size(CameraFormatAddData *data, struct param *p, SDL_PixelFormat sdlfmt, SDL_Colorspace colorspace)
646{
647 const struct spa_pod_prop *prop;
648 struct spa_pod * values;
649 uint32_t i, n_vals, choice;
650 struct spa_rectangle *rectangles;
651
652 prop = spa_pod_find_prop(p->param, NULL, SPA_FORMAT_VIDEO_size);
653 if (prop == NULL)
654 return;
655
656 values = spa_pod_get_values(&prop->value, &n_vals, &choice);
657 if (values->type != SPA_TYPE_Rectangle || n_vals == 0)
658 return;
659
660 rectangles = SPA_POD_BODY(values);
661 switch (choice) {
662 case SPA_CHOICE_None:
663 n_vals = 1;
664 SDL_FALLTHROUGH;
665 case SPA_CHOICE_Enum:
666 for (i = 0; i < n_vals; i++) {
667 collect_rates(data, p, sdlfmt, colorspace, &rectangles[i]);
668 }
669 break;
670 default:
671 SDL_Log("CAMERA: unimplemented choice:%d", choice);
672 break;
673 }
674}
675
676static void collect_raw(CameraFormatAddData *data, struct param *p)
677{
678 const struct spa_pod_prop *prop;
679 SDL_PixelFormat sdlfmt;
680 SDL_Colorspace colorspace;
681 struct spa_pod * values;
682 uint32_t i, n_vals, choice, *ids;
683
684 prop = spa_pod_find_prop(p->param, NULL, SPA_FORMAT_VIDEO_format);
685 if (prop == NULL)
686 return;
687
688 values = spa_pod_get_values(&prop->value, &n_vals, &choice);
689 if (values->type != SPA_TYPE_Id || n_vals == 0)
690 return;
691
692 ids = SPA_POD_BODY(values);
693 switch (choice) {
694 case SPA_CHOICE_None:
695 n_vals = 1;
696 SDL_FALLTHROUGH;
697 case SPA_CHOICE_Enum:
698 for (i = 0; i < n_vals; i++) {
699 id_to_sdl_format(ids[i], &sdlfmt, &colorspace);
700 if (sdlfmt == SDL_PIXELFORMAT_UNKNOWN) {
701 continue;
702 }
703 collect_size(data, p, sdlfmt, colorspace);
704 }
705 break;
706 default:
707 SDL_Log("CAMERA: unimplemented choice: %d", choice);
708 break;
709 }
710}
711
712static void collect_format(CameraFormatAddData *data, struct param *p)
713{
714 const struct spa_pod_prop *prop;
715 struct spa_pod * values;
716 uint32_t i, n_vals, choice, *ids;
717
718 prop = spa_pod_find_prop(p->param, NULL, SPA_FORMAT_mediaSubtype);
719 if (prop == NULL)
720 return;
721
722 values = spa_pod_get_values(&prop->value, &n_vals, &choice);
723 if (values->type != SPA_TYPE_Id || n_vals == 0)
724 return;
725
726 ids = SPA_POD_BODY(values);
727 switch (choice) {
728 case SPA_CHOICE_None:
729 n_vals = 1;
730 SDL_FALLTHROUGH;
731 case SPA_CHOICE_Enum:
732 for (i = 0; i < n_vals; i++) {
733 switch (ids[i]) {
734 case SPA_MEDIA_SUBTYPE_raw:
735 collect_raw(data, p);
736 break;
737 case SPA_MEDIA_SUBTYPE_mjpg:
738 collect_size(data, p, SDL_PIXELFORMAT_MJPG, SDL_COLORSPACE_JPEG);
739 break;
740 default:
741 // Unsupported format
742 break;
743 }
744 }
745 break;
746 default:
747 SDL_Log("CAMERA: unimplemented choice: %d", choice);
748 break;
749 }
750}
751
752static void add_device(struct global *g)
753{
754 struct param *p;
755 CameraFormatAddData data;
756
757 SDL_zero(data);
758
759 spa_list_for_each(p, &g->param_list, link) {
760 if (p->id != SPA_PARAM_EnumFormat)
761 continue;
762
763 collect_format(&data, p);
764 }
765 if (data.num_specs > 0) {
766 SDL_AddCamera(g->name, SDL_CAMERA_POSITION_UNKNOWN,
767 data.num_specs, data.specs, g);
768 }
769 SDL_free(data.specs);
770
771 g->added = true;
772}
773
774static void PIPEWIRECAMERA_DetectDevices(void)
775{
776 struct global *g;
777
778 PIPEWIRE_pw_thread_loop_lock(hotplug.loop);
779
780 // Wait until the initial registry enumeration is complete
781 while (!hotplug.init_complete) {
782 PIPEWIRE_pw_thread_loop_wait(hotplug.loop);
783 }
784
785 spa_list_for_each (g, &hotplug.global_list, link) {
786 if (!g->added) {
787 add_device(g);
788 }
789 }
790
791 hotplug.events_enabled = true;
792
793 PIPEWIRE_pw_thread_loop_unlock(hotplug.loop);
794}
795
796static void PIPEWIRECAMERA_FreeDeviceHandle(SDL_Camera *device)
797{
798}
799
800static void do_resync(void)
801{
802 hotplug.pending_seq = pw_core_sync(hotplug.core, PW_ID_CORE, 0);
803}
804
805/** node */
806static void node_event_info(void *object, const struct pw_node_info *info)
807{
808 struct global *g = object;
809 uint32_t i;
810
811 info = g->info = PIPEWIRE_pw_node_info_merge(g->info, info, g->changed == 0);
812 if (info == NULL)
813 return;
814
815 if (info->change_mask & PW_NODE_CHANGE_MASK_PARAMS) {
816 for (i = 0; i < info->n_params; i++) {
817 uint32_t id = info->params[i].id;
818 int res;
819
820 if (info->params[i].user == 0)
821 continue;
822 info->params[i].user = 0;
823
824 if (id != SPA_PARAM_EnumFormat)
825 continue;
826
827 param_add(&g->pending_list, SPA_PARAMS_INFO_SEQ(info->params[i]), id, NULL);
828 if (!(info->params[i].flags & SPA_PARAM_INFO_READ))
829 continue;
830
831 res = pw_node_enum_params((struct pw_node*)g->proxy,
832 ++SPA_PARAMS_INFO_SEQ(info->params[i]), id, 0, -1, NULL);
833 if (SPA_RESULT_IS_ASYNC(res))
834 SPA_PARAMS_INFO_SEQ(info->params[i]) = res;
835
836 g->changed++;
837 }
838 }
839 do_resync();
840}
841
842static void node_event_param(void *object, int seq,
843 uint32_t id, uint32_t index, uint32_t next,
844 const struct spa_pod *param)
845{
846 struct global *g = object;
847 param_add(&g->pending_list, seq, id, param);
848}
849
850static const struct pw_node_events node_events = {
851 .version = PW_VERSION_NODE_EVENTS,
852 .info = node_event_info,
853 .param = node_event_param,
854};
855
856static void node_destroy(struct global *g)
857{
858 if (g->info) {
859 PIPEWIRE_pw_node_info_free(g->info);
860 g->info = NULL;
861 }
862}
863
864
865static const struct global_class node_class = {
866 .type = PW_TYPE_INTERFACE_Node,
867 .version = PW_VERSION_NODE,
868 .events = &node_events,
869 .destroy = node_destroy,
870};
871
872/** proxy */
873static void proxy_removed(void *data)
874{
875 struct global *g = data;
876 PIPEWIRE_pw_proxy_destroy(g->proxy);
877}
878
879static void proxy_destroy(void *data)
880{
881 struct global *g = data;
882 spa_list_remove(&g->link);
883 g->proxy = NULL;
884 if (g->class) {
885 if (g->class->events)
886 spa_hook_remove(&g->object_listener);
887 if (g->class->destroy)
888 g->class->destroy(g);
889 }
890 param_clear(&g->param_list, SPA_ID_INVALID);
891 param_clear(&g->pending_list, SPA_ID_INVALID);
892 free(g->name); // This should NOT be SDL_free()
893}
894
895static const struct pw_proxy_events proxy_events = {
896 .version = PW_VERSION_PROXY_EVENTS,
897 .removed = proxy_removed,
898 .destroy = proxy_destroy
899};
900
901// called with thread_loop lock
902static void hotplug_registry_global_callback(void *object, uint32_t id,
903 uint32_t permissions, const char *type, uint32_t version,
904 const struct spa_dict *props)
905{
906 const struct global_class *class = NULL;
907 struct pw_proxy *proxy;
908 const char *str, *name = NULL;
909
910 if (spa_streq(type, PW_TYPE_INTERFACE_Node)) {
911 if (props == NULL)
912 return;
913 if (((str = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS)) == NULL) ||
914 (!spa_streq(str, "Video/Source")))
915 return;
916
917 if ((name = spa_dict_lookup(props, PW_KEY_NODE_DESCRIPTION)) == NULL &&
918 (name = spa_dict_lookup(props, PW_KEY_NODE_NAME)) == NULL)
919 name = "unnamed camera";
920
921 class = &node_class;
922 }
923 if (class) {
924 struct global *g;
925
926 proxy = pw_registry_bind(hotplug.registry,
927 id, class->type, class->version,
928 sizeof(struct global));
929
930 g = PIPEWIRE_pw_proxy_get_user_data(proxy);
931 g->class = class;
932 g->id = id;
933 g->permissions = permissions;
934 g->props = props ? PIPEWIRE_pw_properties_new_dict(props) : NULL;
935 g->proxy = proxy;
936 g->name = strdup(name);
937 spa_list_init(&g->pending_list);
938 spa_list_init(&g->param_list);
939 spa_list_append(&hotplug.global_list, &g->link);
940
941 PIPEWIRE_pw_proxy_add_listener(proxy,
942 &g->proxy_listener,
943 &proxy_events, g);
944
945 if (class->events) {
946 PIPEWIRE_pw_proxy_add_object_listener(proxy,
947 &g->object_listener,
948 class->events, g);
949 }
950 if (class->init)
951 class->init(g);
952
953 do_resync();
954 }
955}
956
957// called with thread_loop lock
958static void hotplug_registry_global_remove_callback(void *object, uint32_t id)
959{
960}
961
962static const struct pw_registry_events hotplug_registry_events =
963{
964 .version = PW_VERSION_REGISTRY_EVENTS,
965 .global = hotplug_registry_global_callback,
966 .global_remove = hotplug_registry_global_remove_callback
967};
968
969static void parse_version(const char *str, int *major, int *minor, int *patch)
970{
971 if (SDL_sscanf(str, "%d.%d.%d", major, minor, patch) < 3) {
972 *major = 0;
973 *minor = 0;
974 *patch = 0;
975 }
976}
977
978// Core info, called with thread_loop lock
979static void hotplug_core_info_callback(void *data, const struct pw_core_info *info)
980{
981 parse_version(info->version, &hotplug.server_major, &hotplug.server_minor, &hotplug.server_patch);
982}
983
984// Core sync points, called with thread_loop lock
985static void hotplug_core_done_callback(void *object, uint32_t id, int seq)
986{
987 hotplug.last_seq = seq;
988 if (id == PW_ID_CORE && seq == hotplug.pending_seq) {
989 struct global *g;
990 struct pw_node_info *info;
991
992 spa_list_for_each(g, &hotplug.global_list, link) {
993 if (!g->changed)
994 continue;
995
996 info = g->info;
997 param_update(&g->param_list, &g->pending_list, info->n_params, info->params);
998
999 if (!g->added && hotplug.events_enabled) {
1000 add_device(g);
1001 }
1002 }
1003 hotplug.init_complete = true;
1004 PIPEWIRE_pw_thread_loop_signal(hotplug.loop, false);
1005 }
1006}
1007static const struct pw_core_events hotplug_core_events =
1008{
1009 .version = PW_VERSION_CORE_EVENTS,
1010 .info = hotplug_core_info_callback,
1011 .done = hotplug_core_done_callback
1012};
1013
1014/* When in a container, the library version can differ from the underlying core version,
1015 * so make sure the underlying Pipewire implementation meets the version requirement.
1016 */
1017static bool pipewire_server_version_at_least(int major, int minor, int patch)
1018{
1019 return (hotplug.server_major >= major) &&
1020 (hotplug.server_major > major || hotplug.server_minor >= minor) &&
1021 (hotplug.server_major > major || hotplug.server_minor > minor || hotplug.server_patch >= patch);
1022}
1023
1024// The hotplug thread
1025static bool hotplug_loop_init(void)
1026{
1027 int res;
1028
1029 spa_list_init(&hotplug.global_list);
1030
1031#if PW_CHECK_VERSION(0, 3, 75)
1032 hotplug.have_1_0_5 = PIPEWIRE_pw_check_library_version(1,0,5);
1033#else
1034 hotplug.have_1_0_5 = false;
1035#endif
1036
1037 hotplug.loop = PIPEWIRE_pw_thread_loop_new("SDLPwCameraPlug", NULL);
1038 if (!hotplug.loop) {
1039 return SDL_SetError("Pipewire: Failed to create hotplug detection loop (%i)", errno);
1040 }
1041
1042 hotplug.context = PIPEWIRE_pw_context_new(PIPEWIRE_pw_thread_loop_get_loop(hotplug.loop), NULL, 0);
1043 if (!hotplug.context) {
1044 return SDL_SetError("Pipewire: Failed to create hotplug detection context (%i)", errno);
1045 }
1046
1047 hotplug.core = PIPEWIRE_pw_context_connect(hotplug.context, NULL, 0);
1048 if (!hotplug.core) {
1049 return SDL_SetError("Pipewire: Failed to connect hotplug detection context (%i)", errno);
1050 }
1051 spa_zero(hotplug.core_listener);
1052 pw_core_add_listener(hotplug.core, &hotplug.core_listener, &hotplug_core_events, NULL);
1053
1054 hotplug.registry = pw_core_get_registry(hotplug.core, PW_VERSION_REGISTRY, 0);
1055 if (!hotplug.registry) {
1056 return SDL_SetError("Pipewire: Failed to acquire hotplug detection registry (%i)", errno);
1057 }
1058
1059 spa_zero(hotplug.registry_listener);
1060 pw_registry_add_listener(hotplug.registry, &hotplug.registry_listener, &hotplug_registry_events, NULL);
1061
1062 do_resync();
1063
1064 res = PIPEWIRE_pw_thread_loop_start(hotplug.loop);
1065 if (res != 0) {
1066 return SDL_SetError("Pipewire: Failed to start hotplug detection loop");
1067 }
1068
1069 PIPEWIRE_pw_thread_loop_lock(hotplug.loop);
1070 while (!hotplug.init_complete) {
1071 PIPEWIRE_pw_thread_loop_wait(hotplug.loop);
1072 }
1073 PIPEWIRE_pw_thread_loop_unlock(hotplug.loop);
1074
1075 if (!pipewire_server_version_at_least(PW_REQUIRED_MAJOR, PW_REQUIRED_MINOR, PW_REQUIRED_PATCH)) {
1076 return SDL_SetError("Pipewire: server version is too old %d.%d.%d < %d.%d.%d",
1077 hotplug.server_major, hotplug.server_minor, hotplug.server_patch,
1078 PW_REQUIRED_MAJOR, PW_REQUIRED_MINOR, PW_REQUIRED_PATCH);
1079 }
1080
1081 return true;
1082}
1083
1084
1085static void PIPEWIRECAMERA_Deinitialize(void)
1086{
1087 if (pipewire_initialized) {
1088 if (hotplug.loop) {
1089 PIPEWIRE_pw_thread_loop_lock(hotplug.loop);
1090 }
1091 if (hotplug.registry) {
1092 spa_hook_remove(&hotplug.registry_listener);
1093 PIPEWIRE_pw_proxy_destroy((struct pw_proxy *)hotplug.registry);
1094 }
1095 if (hotplug.core) {
1096 spa_hook_remove(&hotplug.core_listener);
1097 PIPEWIRE_pw_core_disconnect(hotplug.core);
1098 }
1099 if (hotplug.context) {
1100 PIPEWIRE_pw_context_destroy(hotplug.context);
1101 }
1102 if (hotplug.loop) {
1103 PIPEWIRE_pw_thread_loop_unlock(hotplug.loop);
1104 PIPEWIRE_pw_thread_loop_destroy(hotplug.loop);
1105 }
1106 deinit_pipewire_library();
1107 spa_zero(hotplug);
1108 pipewire_initialized = false;
1109 }
1110}
1111
1112static bool PIPEWIRECAMERA_Init(SDL_CameraDriverImpl *impl)
1113{
1114 if (!pipewire_initialized) {
1115
1116 if (!init_pipewire_library()) {
1117 return false;
1118 }
1119
1120 pipewire_initialized = true;
1121
1122 if (!hotplug_loop_init()) {
1123 PIPEWIRECAMERA_Deinitialize();
1124 return false;
1125 }
1126 }
1127
1128 impl->DetectDevices = PIPEWIRECAMERA_DetectDevices;
1129 impl->OpenDevice = PIPEWIRECAMERA_OpenDevice;
1130 impl->CloseDevice = PIPEWIRECAMERA_CloseDevice;
1131 impl->WaitDevice = PIPEWIRECAMERA_WaitDevice;
1132 impl->AcquireFrame = PIPEWIRECAMERA_AcquireFrame;
1133 impl->ReleaseFrame = PIPEWIRECAMERA_ReleaseFrame;
1134 impl->FreeDeviceHandle = PIPEWIRECAMERA_FreeDeviceHandle;
1135 impl->Deinitialize = PIPEWIRECAMERA_Deinitialize;
1136
1137 return true;
1138}
1139
1140CameraBootStrap PIPEWIRECAMERA_bootstrap = {
1141 "pipewire", "SDL PipeWire camera driver", PIPEWIRECAMERA_Init, false
1142};
1143
1144#endif // SDL_CAMERA_DRIVER_PIPEWIRE