SDL: camera: improve PipeWire version checks

From 2b9ac185cda840452e1aeddf6414ebbab28843df Mon Sep 17 00:00:00 2001
From: Wim Taymans <[EMAIL REDACTED]>
Date: Thu, 9 May 2024 12:36:35 +0200
Subject: [PATCH] camera: improve PipeWire version checks

Remove the custom server version check. We can easily do this as part of
starting the hotplug loop. Check that we are at least running against a
1.0.0 server.

Log the compiled, linked, server and required versions.

Check that we are compiled and linked with the right version before using
the time symbol of a struct.
---
 src/camera/pipewire/SDL_camera_pipewire.c | 165 +++++++++-------------
 1 file changed, 65 insertions(+), 100 deletions(-)

diff --git a/src/camera/pipewire/SDL_camera_pipewire.c b/src/camera/pipewire/SDL_camera_pipewire.c
index 559078134e8b0..dac5e3baf2c6a 100644
--- a/src/camera/pipewire/SDL_camera_pipewire.c
+++ b/src/camera/pipewire/SDL_camera_pipewire.c
@@ -41,6 +41,10 @@
 #define PW_THREAD_NAME_BUFFER_LENGTH 128
 #define PW_MAX_IDENTIFIER_LENGTH     256
 
+#define PW_REQUIRED_MAJOR       1
+#define PW_REQUIRED_MINOR       0
+#define PW_REQUIRED_PATCH       0
+
 enum PW_READY_FLAGS
 {
     PW_READY_FLAG_BUFFER_ADDED = 0x1,
@@ -55,6 +59,7 @@ static SDL_bool pipewire_initialized = SDL_FALSE;
 
 // Pipewire entry points
 static const char *(*PIPEWIRE_pw_get_library_version)(void);
+static bool (*PIPEWIRE_pw_check_library_version)(int major, int minor, int micro);
 static void (*PIPEWIRE_pw_init)(int *, char ***);
 static void (*PIPEWIRE_pw_deinit)(void);
 static struct pw_main_loop *(*PIPEWIRE_pw_main_loop_new)(struct pw_main_loop *loop);
@@ -149,6 +154,7 @@ static void unload_pipewire_library(void)
 static int load_pipewire_syms(void)
 {
     SDL_PIPEWIRE_SYM(pw_get_library_version);
+    SDL_PIPEWIRE_SYM(pw_check_library_version);
     SDL_PIPEWIRE_SYM(pw_init);
     SDL_PIPEWIRE_SYM(pw_deinit);
     SDL_PIPEWIRE_SYM(pw_main_loop_new);
@@ -190,107 +196,14 @@ static int load_pipewire_syms(void)
     return 0;
 }
 
-/* When in a container, the library version can differ from the underlying core version,
- * so make sure the underlying Pipewire implementation meets the version requirement.
- */
-struct version_data
-{
-    struct pw_main_loop *loop;
-    int major, minor, patch;
-    int seq;
-};
-
-static void version_check_core_info_callback(void *data, const struct pw_core_info *info)
-{
-    struct version_data *v = data;
-
-    if (SDL_sscanf(info->version, "%d.%d.%d", &v->major, &v->minor, &v->patch) < 3) {
-        v->major = 0;
-        v->minor = 0;
-        v->patch = 0;
-    }
-}
-
-static void version_check_core_done_callback(void *data, uint32_t id, int seq)
-{
-    struct version_data *v = data;
-
-    if (id == PW_ID_CORE && v->seq == seq) {
-        PIPEWIRE_pw_main_loop_quit(v->loop);
-    }
-}
-
-static const struct pw_core_events version_check_core_events =
-{
-    .version = PW_VERSION_CORE_EVENTS,
-    .info = version_check_core_info_callback,
-    .done = version_check_core_done_callback
-};
-
-static SDL_bool pipewire_core_version_at_least(int major, int minor, int patch)
-{
-    struct pw_main_loop *loop = NULL;
-    struct pw_context *context = NULL;
-    struct pw_core *core = NULL;
-    struct version_data version_data;
-    struct spa_hook core_listener;
-    SDL_bool ret = SDL_FALSE;
-
-    loop = PIPEWIRE_pw_main_loop_new(NULL);
-    if (!loop) {
-        goto done;
-    }
-
-    context = PIPEWIRE_pw_context_new(PIPEWIRE_pw_main_loop_get_loop(loop), NULL, 0);
-    if (!context) {
-        goto done;
-    }
-
-    core = PIPEWIRE_pw_context_connect(context, NULL, 0);
-    if (!core) {
-        goto done;
-    }
-
-    /* Attach a core listener and get the version. */
-    spa_zero(version_data);
-    version_data.loop = loop;
-    pw_core_add_listener(core, &core_listener, &version_check_core_events, &version_data);
-    version_data.seq = pw_core_sync(core, PW_ID_CORE, 0);
-
-    PIPEWIRE_pw_main_loop_run(loop);
-
-    spa_hook_remove(&core_listener);
-
-    ret = (version_data.major >= major) &&
-           (version_data.major > major || version_data.minor >= minor) &&
-           (version_data.major > major || version_data.minor > minor || version_data.patch >= patch);
-
-done:
-    if (core) {
-        PIPEWIRE_pw_core_disconnect(core);
-    }
-    if (context) {
-        PIPEWIRE_pw_context_destroy(context);
-    }
-    if (loop) {
-        PIPEWIRE_pw_main_loop_destroy(loop);
-    }
-
-    return ret;
-}
-
-static int init_pipewire_library(SDL_bool check_preferred_version)
+static int init_pipewire_library(void)
 {
     if (!load_pipewire_library()) {
         if (!load_pipewire_syms()) {
             PIPEWIRE_pw_init(NULL, NULL);
-
-            if (!check_preferred_version || pipewire_core_version_at_least(1, 0, 0)) {
-                return 0;
-            }
+            return 0;
         }
     }
-
     return -1;
 }
 
@@ -309,6 +222,9 @@ static struct
 
     struct pw_core *core;
     struct spa_hook core_listener;
+    int server_major;
+    int server_minor;
+    int server_patch;
     int last_seq;
     int pending_seq;
 
@@ -317,6 +233,7 @@ static struct
 
     struct spa_list global_list;
 
+    SDL_bool have_1_0_5;
     SDL_bool init_complete;
     SDL_bool events_enabled;
 } hotplug;
@@ -648,7 +565,11 @@ static int PIPEWIRECAMERA_AcquireFrame(SDL_CameraDevice *device, SDL_Surface *fr
         return 0;
     }
 
-    *timestampNS = b->time;
+#if PW_CHECK_VERSION(1,0,5)
+    *timestampNS = hotplug.have_1_0_5 ? b->time : SDL_GetTicksNS();
+#else
+    *timestampNS = SDL_GetTicksNS();
+#endif
     frame->pixels = b->buffer->datas[0].data;
     frame->pitch = b->buffer->datas[0].chunk->stride;
 
@@ -777,8 +698,6 @@ static void add_device(struct global *g)
 
     SDL_zero(data);
 
-    SDL_Log("CAMERA: found %d", g->id);
-
     spa_list_for_each(p, &g->param_list, link) {
         if (p->id != SPA_PARAM_EnumFormat)
             continue;
@@ -989,6 +908,21 @@ static const struct pw_registry_events hotplug_registry_events =
     .global_remove = hotplug_registry_global_remove_callback
 };
 
+static void parse_version(const char *str, int *major, int *minor, int *patch)
+{
+    if (SDL_sscanf(str, "%d.%d.%d", major, minor, patch) < 3) {
+        *major = 0;
+        *minor = 0;
+        *patch = 0;
+    }
+}
+
+// Core info, called with thread_loop lock
+static void hotplug_core_info_callback(void *data, const struct pw_core_info *info)
+{
+    parse_version(info->version, &hotplug.server_major, &hotplug.server_minor, &hotplug.server_patch);
+}
+
 // Core sync points, called with thread_loop lock
 static void hotplug_core_done_callback(void *object, uint32_t id, int seq)
 {
@@ -1015,9 +949,20 @@ static void hotplug_core_done_callback(void *object, uint32_t id, int seq)
 static const struct pw_core_events hotplug_core_events =
 {
     .version = PW_VERSION_CORE_EVENTS,
+    .info = hotplug_core_info_callback,
     .done = hotplug_core_done_callback
 };
 
+/* When in a container, the library version can differ from the underlying core version,
+ * so make sure the underlying Pipewire implementation meets the version requirement.
+ */
+static SDL_bool pipewire_server_version_at_least(int major, int minor, int patch)
+{
+    return (hotplug.server_major >= major) &&
+           (hotplug.server_major > major || hotplug.server_minor >= minor) &&
+           (hotplug.server_major > major || hotplug.server_minor > minor || hotplug.server_patch >= patch);
+}
+
 // The hotplug thread
 static int hotplug_loop_init(void)
 {
@@ -1025,6 +970,8 @@ static int hotplug_loop_init(void)
 
     spa_list_init(&hotplug.global_list);
 
+    hotplug.have_1_0_5 = PIPEWIRE_pw_check_library_version(1,0,5);
+
     hotplug.loop = PIPEWIRE_pw_thread_loop_new("SDLAudioHotplug", NULL);
     if (!hotplug.loop) {
         return SDL_SetError("Pipewire: Failed to create hotplug detection loop (%i)", errno);
@@ -1050,13 +997,31 @@ static int hotplug_loop_init(void)
     spa_zero(hotplug.registry_listener);
     pw_registry_add_listener(hotplug.registry, &hotplug.registry_listener, &hotplug_registry_events, NULL);
 
-    hotplug.pending_seq = pw_core_sync(hotplug.core, PW_ID_CORE, 0);
+    do_resync();
 
     res = PIPEWIRE_pw_thread_loop_start(hotplug.loop);
     if (res != 0) {
         return SDL_SetError("Pipewire: Failed to start hotplug detection loop");
     }
 
+    PIPEWIRE_pw_thread_loop_lock(hotplug.loop);
+    while (!hotplug.init_complete) {
+        PIPEWIRE_pw_thread_loop_wait(hotplug.loop);
+    }
+    PIPEWIRE_pw_thread_loop_unlock(hotplug.loop);
+
+    SDL_Log("CAMERA: PipeWire compiled:%s library:%s server:%d.%d.%d required:%d.%d.%d",
+		    pw_get_headers_version(),
+		    PIPEWIRE_pw_get_library_version(),
+		    hotplug.server_major, hotplug.server_minor, hotplug.server_patch,
+                    PW_REQUIRED_MAJOR, PW_REQUIRED_MINOR, PW_REQUIRED_PATCH);
+
+    if (!pipewire_server_version_at_least(PW_REQUIRED_MAJOR, PW_REQUIRED_MINOR, PW_REQUIRED_PATCH)) {
+        return SDL_SetError("Pipewire: server version is too old %d.%d.%d < %d.%d.%d",
+			hotplug.server_major, hotplug.server_minor, hotplug.server_patch,
+                    PW_REQUIRED_MAJOR, PW_REQUIRED_MINOR, PW_REQUIRED_PATCH);
+    }
+
     return 0;
 }
 
@@ -1092,7 +1057,7 @@ static SDL_bool PIPEWIRECAMERA_Init(SDL_CameraDriverImpl *impl)
 {
     if (!pipewire_initialized) {
 
-        if (init_pipewire_library(true) < 0) {
+        if (init_pipewire_library() < 0) {
             return SDL_FALSE;
         }