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