From f055cf398db513c7364fb7e121fd02bfdb17dfcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Tue, 11 Nov 2025 21:08:56 +0100 Subject: [PATCH] examples: Make fixate examples support device ID negotation Things are structured in a way allowing testing falling back to implicitly assumed device by defining/not defining macros. --- src/examples/base64.h | 46 +++++ src/examples/video-play-fixate.c | 318 +++++++++++++++++++++++++++---- src/examples/video-src-fixate.c | 244 +++++++++++++++++++++--- 3 files changed, 544 insertions(+), 64 deletions(-) create mode 100644 src/examples/base64.h diff --git a/src/examples/base64.h b/src/examples/base64.h new file mode 100644 index 000000000..50e6d64b2 --- /dev/null +++ b/src/examples/base64.h @@ -0,0 +1,46 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +static inline void base64_encode(const uint8_t *data, size_t len, char *enc, char pad) +{ + static const char tab[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + size_t i; + for (i = 0; i < len; i += 3) { + uint32_t v; + v = data[i+0] << 16; + v |= (i+1 < len ? data[i+1] : 0) << 8; + v |= (i+2 < len ? data[i+2] : 0); + *enc++ = tab[(v >> (3*6)) & 0x3f]; + *enc++ = tab[(v >> (2*6)) & 0x3f]; + *enc++ = i+1 < len ? tab[(v >> (1*6)) & 0x3f] : pad; + *enc++ = i+2 < len ? tab[(v >> (0*6)) & 0x3f] : pad; + } + *enc = '\0'; +} + +static inline size_t base64_decode(const char *data, size_t len, uint8_t *dec) +{ + uint8_t tab[] = { + 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, + 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, + -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, + -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, + 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 }; + size_t i, j; + for (i = 0, j = 0; i < len; i += 4) { + uint32_t v; + v = tab[data[i+0]-43] << (3*6); + v |= tab[data[i+1]-43] << (2*6); + v |= (data[i+2] == '=' ? 0 : tab[data[i+2]-43]) << (1*6); + v |= (data[i+3] == '=' ? 0 : tab[data[i+3]-43]); + dec[j++] = (v >> 16) & 0xff; + if (data[i+2] != '=') dec[j++] = (v >> 8) & 0xff; + if (data[i+3] != '=') dec[j++] = v & 0xff; + } + return j; +} diff --git a/src/examples/video-play-fixate.c b/src/examples/video-play-fixate.c index 3c6875a15..6fdc561e6 100644 --- a/src/examples/video-play-fixate.c +++ b/src/examples/video-play-fixate.c @@ -14,15 +14,25 @@ #include #include #include +#include +#include +#include #include +#include +#include #include #include #include #include #include +#include +#include "base64.h" + +/* Comment out to test device ID negotation backward compatibility. */ +#define SUPPORT_DEVICE_ID_NEGOTIATION 1 /* If defined, emulate failing to import DMA buffer. */ #define EMULATE_DMA_BUF_IMPORT_FAIL 1 @@ -32,6 +42,8 @@ #define MAX_BUFFERS 64 #define MAX_MOD 8 +#define MAX_PARAMS 16 +#define MAX_DEVICE_IDS 16 #include "sdl.h" @@ -51,6 +63,35 @@ struct modifier_info { uint64_t modifiers[MAX_MOD]; }; +struct { + unsigned int major; + unsigned int minor; + struct modifier_info mod_info; +} devices[] = { +#if 1 + { + .major = 100, + .minor = 100, + .mod_info = { + .spa_format = SPA_VIDEO_FORMAT_RGBA, + .modifiers = { DRM_FORMAT_MOD_LINEAR, DRM_FORMAT_MOD_INVALID }, + .n_modifiers = 2, + }, + }, +#endif +#if 1 + { + .major = 200, + .minor = 200, + .mod_info = { + .spa_format = SPA_VIDEO_FORMAT_RGBA, + .modifiers = {DRM_FORMAT_MOD_LINEAR, DRM_FORMAT_MOD_GENERIC_16_16_TILE}, + .n_modifiers = 2, + }, + }, +#endif +}; + struct data { const char *path; @@ -69,16 +110,20 @@ struct data { int32_t stride; struct spa_rectangle size; - uint32_t n_mod_info; - struct modifier_info mod_info[2]; - int counter; bool capabilities_known; + bool device_negotiation_supported; + + dev_t device_ids[MAX_DEVICE_IDS]; + size_t n_device_ids; + + int used_device_index; }; static int build_formats(struct data *data, struct spa_pod_builder *b, const struct spa_pod **params); +#ifdef EMULATE_DMA_BUF_IMPORT_FAIL static struct pw_version parse_pw_version(const char* version) { struct pw_version pw_version; sscanf(version, "%d.%d.%d", &pw_version.major, &pw_version.minor, @@ -93,41 +138,38 @@ static bool has_pw_version(int major, int minor, int micro) { return major <= pw_version.major && minor <= pw_version.minor && micro <= pw_version.micro; } -static void init_modifiers(struct data *data) -{ - data->n_mod_info = 1; - data->mod_info[0].spa_format = SPA_VIDEO_FORMAT_RGBA; - data->mod_info[0].n_modifiers = 2; - data->mod_info[0].modifiers[0] = DRM_FORMAT_MOD_LINEAR; - data->mod_info[0].modifiers[1] = DRM_FORMAT_MOD_INVALID; -} - -static void destroy_modifiers(struct data *data) -{ - data->mod_info[0].n_modifiers = 0; -} - static void strip_modifier(struct data *data, uint32_t spa_format, uint64_t modifier) { - if (data->mod_info[0].spa_format != spa_format) + struct modifier_info *mod_info; + + assert(data->used_device_index >= 0); + + mod_info = &devices[data->used_device_index].mod_info; + + if (mod_info->spa_format != spa_format) return; - struct modifier_info *mod_info = &data->mod_info[0]; + uint32_t counter = 0; // Dropping of single modifiers is only supported on PipeWire 0.3.40 and newer. // On older PipeWire just dropping all modifiers might work on Versions newer then 0.3.33/35 if (has_pw_version(0,3,40)) { - printf("Dropping a single modifier\n"); + printf("Dropping a single modifier from device %u:%u\n", + devices[data->used_device_index].major, + devices[data->used_device_index].minor); for (uint32_t i = 0; i < mod_info->n_modifiers; i++) { if (mod_info->modifiers[i] == modifier) continue; mod_info->modifiers[counter++] = mod_info->modifiers[i]; } } else { - printf("Dropping all modifiers\n"); + printf("Dropping all modifiers from device %u:%u\n", + devices[data->used_device_index].major, + devices[data->used_device_index].minor); counter = 0; } mod_info->n_modifiers = counter; } +#endif /* EMULATE_DMA_BUF_IMPORT_FAIL */ static void handle_events(struct data *data) { @@ -141,15 +183,36 @@ static void handle_events(struct data *data) } } -static struct spa_pod *build_format(struct spa_pod_builder *b, SDL_RendererInfo *info, enum spa_video_format format, - uint64_t *modifiers, int modifier_count) +static struct spa_pod *build_format(struct data *data, struct spa_pod_builder *b, + SDL_RendererInfo *info, enum spa_video_format format, int device_index) { struct spa_pod_frame f[2]; int i, c; + uint64_t *modifiers; + int modifier_count; + + if (device_index == -1) { + modifiers = NULL; + modifier_count = 0; + } else { + struct modifier_info *mod_info = &devices[device_index].mod_info; + + modifiers = mod_info->modifiers; + modifier_count = mod_info->n_modifiers; + } spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), 0); spa_pod_builder_add(b, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0); + /* device */ + if (data->device_negotiation_supported && device_index >= 0) { + dev_t device_id = makedev(devices[device_index].major, + devices[device_index].minor); + + spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_deviceId, + SPA_POD_PROP_FLAG_MANDATORY); + spa_pod_builder_bytes(b, &device_id, sizeof device_id); + } /* format */ spa_pod_builder_add(b, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0); /* modifiers */ @@ -303,25 +366,118 @@ static void on_stream_state_changed(void *_data, enum pw_stream_state old, } static void -on_stream_tag_changed(struct data *data, const struct spa_pod *param) +collect_device_ids(struct data *data, const char *json) +{ + struct spa_json it; + int len; + const char *value; + struct spa_json sub; + + if ((len = spa_json_begin(&it, json, strlen(json), &value)) <= 0) { + fprintf(stderr, "invalid device IDs value\n"); + return; + } + if (!spa_json_is_array(value, len)) { + fprintf(stderr, "device IDs not array\n"); + return; + } + + spa_json_enter(&it, &sub); + while ((len = spa_json_next(&sub, &value)) > 0) { + char *string; + union { + dev_t device_id; + uint8_t buffer[1024]; + } dec; + + string = alloca(len + 1); + + if (!spa_json_is_string(value, len)) { + fprintf(stderr, "device ID not string\n"); + return; + } + + if (spa_json_parse_string(value, len, string) <= 0) { + fprintf(stderr, "invalid device ID string\n"); + return; + } + + if (base64_decode(string, strlen(string), + (uint8_t *)&dec.device_id) < sizeof(dev_t)) { + fprintf(stderr, "invalid device ID\n"); + return; + } + + fprintf(stderr, "discovered device ID %u:%u\n", + major(dec.device_id), minor(dec.device_id)); + + data->device_ids[data->n_device_ids++] = dec.device_id; + } +} + +static void +discover_capabilities(struct data *data, const struct spa_pod *param) +{ +#ifdef SUPPORT_DEVICE_ID_NEGOTIATION + struct spa_peer_param_info info; + void *state = NULL; + + while (spa_peer_param_parse(param, &info, sizeof(info), &state) == 1) { + struct spa_param_dict_info di; + + if (spa_param_dict_parse(info.param, &di, sizeof(di)) > 0) { + struct spa_dict dict; + struct spa_dict_item *items; + const struct spa_dict_item *it; + + if (spa_param_dict_info_parse(&di, sizeof(di), &dict, NULL) < 0) + return; + + items = alloca(sizeof(struct spa_dict_item) * dict.n_items); + if (spa_param_dict_info_parse(&di, sizeof(di), &dict, items) < 0) + return; + + spa_dict_for_each(it, &dict) { + if (spa_streq(it->key, PW_CAPABILITY_DEVICE_ID_NEGOTIATION) && + spa_streq(it->value, "true")) { + data->device_negotiation_supported = true; + } else if (spa_streq(it->key, PW_CAPABILITY_DEVICE_IDS)) { + collect_device_ids(data, it->value); + } + } + } + } +#endif /* SUPPORT_DEVICE_ID_NEGOTIATION */ +} + +static void +on_stream_peer_capability_changed(struct data *data, const struct spa_pod *param) { struct pw_stream *stream = data->stream; - printf("tag param changed: \n"); + printf("peer capability param changed: \n"); spa_debug_pod(4, NULL, param); + discover_capabilities(data, param); + if (!data->capabilities_known) { - const struct spa_pod *params[2]; + const struct spa_pod *params[MAX_PARAMS]; uint32_t n_params = 0; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); data->capabilities_known = true; + if (data->device_negotiation_supported) + printf("stream supports device negotiation\n"); + else + printf("stream does not support device negotiation\n"); + /* build the extra parameters to connect with. To connect, we can provide * a list of supported formats. We use a builder that writes the param * object to the stack. */ printf("supported formats:\n"); + n_params = build_formats(data, &b, params); pw_stream_update_params(data->stream, params, n_params); @@ -330,6 +486,29 @@ on_stream_tag_changed(struct data *data, const struct spa_pod *param) } } +static int +find_device_id_from_param(const struct spa_pod *format, dev_t *device_id) +{ + const struct spa_pod_prop *device_prop; + const void *bytes; + uint32_t size; + + device_prop = spa_pod_find_prop(format, NULL, SPA_FORMAT_VIDEO_deviceId); + if (device_prop == NULL) + return -ENOENT; + + if (spa_pod_parse_object (format, SPA_TYPE_OBJECT_Format, NULL, + SPA_FORMAT_VIDEO_deviceId, SPA_POD_OPT_Bytes(&bytes, &size)) < 0) + return -EINVAL; + + if (size != sizeof(dev_t)) + return -EINVAL; + + *device_id = *(dev_t *)bytes; + + return 0; +} + /* Be notified when the stream format param changes. * * We are now supposed to call pw_stream_finish_format() with success or @@ -346,7 +525,7 @@ on_stream_format_changed(struct data *data, const struct spa_pod *param) struct pw_stream *stream = data->stream; uint8_t params_buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer)); - const struct spa_pod *params[1]; + const struct spa_pod *params[MAX_PARAMS]; uint32_t n_params = 0; Uint32 sdl_format; void *d; @@ -366,6 +545,28 @@ on_stream_format_changed(struct data *data, const struct spa_pod *param) sdl_format = id_to_sdl_format(data->format.info.raw.format); data->size = data->format.info.raw.size; + if (data->device_negotiation_supported) { + dev_t device_id; + + if (find_device_id_from_param(param, &device_id) == 0) { + size_t i; + + for (i = 0; i < SPA_N_ELEMENTS(devices); i++) { + if (major(device_id) == devices[i].major && + minor(device_id) == devices[i].minor) { + data->used_device_index = i; + printf("using negotiated device %u:%u\n", + devices[i].major, devices[i].minor); + break; + } + } + } + } else { + data->used_device_index = 0; + printf("using implicitly assumed device %u:%u\n", + devices[0].major, devices[0].minor); + } + if (sdl_format == SDL_PIXELFORMAT_UNKNOWN) { pw_stream_set_error(stream, -EINVAL, "unknown pixel format"); return; @@ -409,8 +610,8 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) return; switch (id) { - case SPA_PARAM_Tag: - on_stream_tag_changed(data, param); + case SPA_PARAM_PeerCapability: + on_stream_peer_capability_changed(data, param); break; case SPA_PARAM_Format: on_stream_format_changed(data, param); @@ -426,6 +627,22 @@ static const struct pw_stream_events stream_events = { .process = on_process, }; +static bool +has_device_id(struct data *data, dev_t device_id) +{ + size_t i; + + if (data->n_device_ids == 0) + return true; + + for (i = 0; i < data->n_device_ids; i++) { + if (data->device_ids[i] == device_id) + return true; + } + + return false; +} + static int build_formats(struct data *data, struct spa_pod_builder *b, const struct spa_pod **params) { SDL_RendererInfo info; @@ -433,13 +650,28 @@ static int build_formats(struct data *data, struct spa_pod_builder *b, const str SDL_GetRendererInfo(data->renderer, &info); - if (data->mod_info[0].n_modifiers > 0) { - params[n_params++] = build_format(b, - &info, SPA_VIDEO_FORMAT_RGBA, - data->mod_info[0].modifiers, - data->mod_info[0].n_modifiers); + if (data->device_negotiation_supported) { + size_t i; + + for (i = 0; i < SPA_N_ELEMENTS(devices); i++) { + dev_t device_id; + + device_id = makedev(devices[i].major, devices[i].minor); + if (!has_device_id(data, device_id)) { + fprintf(stderr, "filtered out %u:%u\n", + devices[i].major, devices[i].minor); + continue; + } + + params[n_params++] = build_format(data, b, + &info, SPA_VIDEO_FORMAT_RGBA, i); + } + } else { + params[n_params++] = build_format(data, b, + &info, SPA_VIDEO_FORMAT_RGBA, 0); } - params[n_params++] = build_format(b, &info, SPA_VIDEO_FORMAT_RGBA, NULL, 0); + + params[n_params++] = build_format(data, b, &info, SPA_VIDEO_FORMAT_RGBA, -1); for (int i=0; i < n_params; i++) { spa_debug_format(2, NULL, params[i]); @@ -452,7 +684,7 @@ static void reneg_format(void *_data, uint64_t expiration) struct data *data = (struct data*) _data; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); - const struct spa_pod *params[2]; + const struct spa_pod *params[MAX_PARAMS]; uint32_t n_params; if (data->format.info.raw.format == 0) @@ -473,7 +705,7 @@ static void do_quit(void *userdata, int signal_number) int main(int argc, char *argv[]) { struct data data = { 0, }; - const struct spa_pod *params[2]; + const struct spa_pod *params[MAX_PARAMS]; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); struct spa_pod_frame f; @@ -509,6 +741,7 @@ int main(int argc, char *argv[]) /* Set stream target if given on command line */ pw_properties_set(props, PW_KEY_TARGET_OBJECT, data.path); + data.used_device_index = -1; data.stream = pw_stream_new_simple( pw_main_loop_get_loop(data.loop), "video-play-fixate", @@ -521,8 +754,6 @@ int main(int argc, char *argv[]) return -1; } - init_modifiers(&data); - if (SDL_CreateWindowAndRenderer (WIDTH, HEIGHT, SDL_WINDOW_RESIZABLE, &data.window, &data.renderer)) { fprintf(stderr, "can't create window: %s\n", SDL_GetError()); @@ -552,6 +783,13 @@ int main(int argc, char *argv[]) 0); params[n_params++] = spa_pod_builder_pop(&b, &f); +#ifdef SUPPORT_DEVICE_ID_NEGOTIATION + params[n_params++] = + spa_param_dict_build_dict(&b, SPA_PARAM_Capability, + &SPA_DICT_ITEMS( + SPA_DICT_ITEM(PW_CAPABILITY_DEVICE_ID_NEGOTIATION, "true"))); +#endif + /* now connect the stream, we need a direction (input/output), * an optional target node to connect to, some flags and parameters */ @@ -574,8 +812,6 @@ int main(int argc, char *argv[]) pw_stream_destroy(data.stream); pw_main_loop_destroy(data.loop); - destroy_modifiers(&data); - SDL_DestroyTexture(data.texture); if (data.cursor) SDL_DestroyTexture(data.cursor); diff --git a/src/examples/video-src-fixate.c b/src/examples/video-src-fixate.c index 64eb32895..0671ab1be 100644 --- a/src/examples/video-src-fixate.c +++ b/src/examples/video-src-fixate.c @@ -18,13 +18,24 @@ #include #include #include +#include +#include -#include +#include +#include #include #include #include #include +#include + +#include "base64.h" + +/* Comment out to test device ID negotation backward compatibility. */ +#define SUPPORT_DEVICE_ID_NEGOTIATION 1 +/* Comment out to disable device IDs listing */ +#define SUPPORT_DEVICE_IDS_LIST 1 #define BPP 4 #define CURSOR_WIDTH 64 @@ -32,10 +43,34 @@ #define CURSOR_BPP 4 #define MAX_BUFFERS 64 +#define MAX_PARAMS 16 +#define MAX_MOD 10 #define M_PI_M2 ( M_PI + M_PI ) -uint64_t supported_modifiers[] = {DRM_FORMAT_MOD_INVALID, DRM_FORMAT_MOD_LINEAR}; +struct { + unsigned int major; + unsigned int minor; + uint64_t supported_modifiers[MAX_MOD]; + size_t n_supported_modifiers; +} devices[] = { +#if 1 + { + .major = 100, + .minor = 100, + .supported_modifiers = {DRM_FORMAT_MOD_INVALID, DRM_FORMAT_MOD_LINEAR}, + .n_supported_modifiers = 2, + }, +#endif +#if 1 + { + .major = 200, + .minor = 200, + .supported_modifiers = {DRM_FORMAT_MOD_LINEAR, DRM_FORMAT_MOD_GENERIC_16_16_TILE}, + .n_supported_modifiers = 2, + }, +#endif +}; struct data { struct pw_thread_loop *loop; @@ -54,6 +89,7 @@ struct data { double accumulator; bool capabilities_known; + bool device_negotiation_supported; }; static void draw_elipse(uint32_t *dst, int width, int height, uint32_t color) @@ -74,14 +110,24 @@ static void draw_elipse(uint32_t *dst, int width, int height, uint32_t color) } } -static struct spa_pod *fixate_format(struct spa_pod_builder *b, enum spa_video_format format, - uint64_t *modifier) +static struct spa_pod *fixate_format(struct data *data, struct spa_pod_builder *b, + int device_index, enum spa_video_format format, uint64_t *modifier) { struct spa_pod_frame f[1]; spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), 0); spa_pod_builder_add(b, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0); + + /* device */ + if (data->device_negotiation_supported) { + dev_t device_id = makedev(devices[device_index].major, + devices[device_index].minor); + + spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_deviceId, + SPA_POD_PROP_FLAG_MANDATORY); + spa_pod_builder_bytes(b, &device_id, sizeof device_id); + } /* format */ spa_pod_builder_add(b, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0); /* modifiers */ @@ -102,15 +148,36 @@ static struct spa_pod *fixate_format(struct spa_pod_builder *b, enum spa_video_f return spa_pod_builder_pop(b, &f[0]); } -static struct spa_pod *build_format(struct spa_pod_builder *b, enum spa_video_format format, - uint64_t *modifiers, int modifier_count) +static struct spa_pod *build_format(struct data *data, struct spa_pod_builder *b, + int device_index, enum spa_video_format format) { + uint64_t *modifiers; + int modifier_count; struct spa_pod_frame f[2]; int i, c; + if (device_index == -1) { + modifiers = NULL; + modifier_count = 0; + } else { + modifiers = devices[device_index].supported_modifiers; + modifier_count = devices[device_index].n_supported_modifiers; + } + spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), 0); spa_pod_builder_add(b, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0); + + /* device */ + if (data->device_negotiation_supported && device_index >= 0) { + dev_t device_id = makedev(devices[device_index].major, + devices[device_index].minor); + + spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_deviceId, + SPA_POD_PROP_FLAG_MANDATORY); + spa_pod_builder_bytes(b, &device_id, sizeof device_id); + } + /* format */ spa_pod_builder_add(b, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0); /* modifiers */ @@ -361,15 +428,50 @@ static void on_stream_remove_buffer(void *_data, struct pw_buffer *buffer) } static void -on_stream_tag_changed(struct data *data, const struct spa_pod *param) +discover_capabilities(struct data *data, const struct spa_pod *param) +{ +#ifdef SUPPORT_DEVICE_ID_NEGOTIATION + struct spa_peer_param_info info; + void *state = NULL; + + while (spa_peer_param_parse(param, &info, sizeof(info), &state) == 1) { + struct spa_param_dict_info di; + + if (spa_param_dict_parse(info.param, &di, sizeof(di)) > 0) { + struct spa_dict dict; + struct spa_dict_item *items; + const struct spa_dict_item *it; + + if (spa_param_dict_info_parse(&di, sizeof(di), &dict, NULL) < 0) + return; + + items = alloca(sizeof(struct spa_dict_item) * dict.n_items); + if (spa_param_dict_info_parse(&di, sizeof(di), &dict, items) < 0) + return; + + spa_dict_for_each(it, &dict) { + if (spa_streq(it->key, PW_CAPABILITY_DEVICE_ID_NEGOTIATION) && + spa_streq(it->value, "true")) { + data->device_negotiation_supported = true; + } + } + } + } +#endif /* SUPPORT_DEVICE_ID_NEGOTIATION */ +} + +static void +on_stream_peer_capability_changed(struct data *data, const struct spa_pod *param) { struct pw_stream *stream = data->stream; - printf("tag param changed: \n"); + printf("peer capability param changed: \n"); spa_debug_pod(4, NULL, param); + discover_capabilities(data, param); + if (!data->capabilities_known) { - const struct spa_pod *params[2]; + const struct spa_pod *params[MAX_PARAMS]; uint32_t n_params = 0; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); @@ -381,9 +483,21 @@ on_stream_tag_changed(struct data *data, const struct spa_pod *param) * The server will select a format that matches and informs us about this * in the stream param_changed event. */ - params[n_params++] = build_format(&b, SPA_VIDEO_FORMAT_RGBA, - supported_modifiers, SPA_N_ELEMENTS(supported_modifiers)); - params[n_params++] = build_format(&b, SPA_VIDEO_FORMAT_RGBA, NULL, 0); + + if (data->device_negotiation_supported) { + size_t i; + + printf("stream supports device negotiation\n"); + + for (i = 0; i < SPA_N_ELEMENTS(devices); i++) + params[n_params++] = build_format(data, &b, i, SPA_VIDEO_FORMAT_RGBA); + params[n_params++] = build_format(data, &b, -1, SPA_VIDEO_FORMAT_RGBA); + } else { + printf("stream does not support device negotiation\n"); + + params[n_params++] = build_format(data, &b, 0, SPA_VIDEO_FORMAT_RGBA); + params[n_params++] = build_format(data, &b, -1, SPA_VIDEO_FORMAT_RGBA); + } printf("announcing starting EnumFormats\n"); for (unsigned int i=0; i < n_params; i++) { @@ -397,6 +511,29 @@ on_stream_tag_changed(struct data *data, const struct spa_pod *param) } } +static int +find_device_id_from_param(const struct spa_pod *format, dev_t *device_id) +{ + const struct spa_pod_prop *device_prop; + const void *bytes; + uint32_t size; + + device_prop = spa_pod_find_prop(format, NULL, SPA_FORMAT_VIDEO_deviceId); + if (device_prop == NULL) + return -ENOENT; + + if (spa_pod_parse_object (format, SPA_TYPE_OBJECT_Format, NULL, + SPA_FORMAT_VIDEO_deviceId, SPA_POD_OPT_Bytes(&bytes, &size)) < 0) + return -EINVAL; + + if (size != sizeof(dev_t)) + return -EINVAL; + + *device_id = *(dev_t *)bytes; + + return 0; +} + /* Be notified when the stream format param changes. * * We are now supposed to call pw_stream_update_params() with success or @@ -413,7 +550,7 @@ on_stream_format_changed(struct data *data, const struct spa_pod *param) struct pw_stream *stream = data->stream; uint8_t params_buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer)); - const struct spa_pod *params[5]; + const struct spa_pod *params[MAX_PARAMS]; uint32_t n_params = 0; int blocks, size, stride, buffertypes; @@ -438,7 +575,31 @@ on_stream_format_changed(struct data *data, const struct spa_pod *param) // check if the modifier is fixated if ((prop_modifier->flags & SPA_POD_PROP_FLAG_DONT_FIXATE) > 0) { const struct spa_pod *pod_modifier = &prop_modifier->value; - printf("fixating format\n"); + int device_index = -1; + size_t i; + if (data->device_negotiation_supported) { + dev_t device_id; + + if (find_device_id_from_param(param, &device_id) == 0) { + for (i = 0; i < SPA_N_ELEMENTS(devices); i++) { + if (major(device_id) == devices[i].major && + minor(device_id) == devices[i].minor) + device_index = i; + } + } + + assert(device_index >= 0); + + printf("fixating format using negotiated device %u:%u\n", + devices[device_index].major, + devices[device_index].minor); + } else { + device_index = 0; + + printf("fixating format using implicitly assumed device %u:%u\n", + devices[device_index].major, + devices[device_index].minor); + } uint32_t n_modifiers = SPA_POD_CHOICE_N_VALUES(pod_modifier); uint64_t *modifiers = SPA_POD_CHOICE_VALUES(pod_modifier); @@ -451,14 +612,17 @@ on_stream_format_changed(struct data *data, const struct spa_pod *param) modifier = modifiers[rand()%n_modifiers]; } - params[n_params++] = fixate_format(&b, SPA_VIDEO_FORMAT_RGBA, &modifier); - params[n_params++] = build_format(&b, SPA_VIDEO_FORMAT_RGBA, - supported_modifiers, SPA_N_ELEMENTS(supported_modifiers)); - params[n_params++] = build_format(&b, SPA_VIDEO_FORMAT_RGBA, - NULL, 0); + params[n_params++] = fixate_format(data, &b, device_index, + SPA_VIDEO_FORMAT_RGBA, &modifier); + + for (i = 0; i < SPA_N_ELEMENTS(devices); i++) { + params[n_params++] = build_format(data, &b, i, + SPA_VIDEO_FORMAT_RGBA); + } + params[n_params++] = build_format(data, &b, -1, SPA_VIDEO_FORMAT_RGBA); printf("announcing fixated EnumFormats\n"); - for (unsigned int i=0; i < 3; i++) { + for (unsigned int i=0; i < n_params; i++) { spa_debug_format(4, NULL, params[i]); } @@ -516,8 +680,8 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) return; switch (id) { - case SPA_PARAM_Tag: - on_stream_tag_changed(data, param); + case SPA_PARAM_PeerCapability: + on_stream_peer_capability_changed(data, param); break; case SPA_PARAM_Format: on_stream_format_changed(data, param); @@ -543,7 +707,7 @@ static void do_quit(void *userdata, int signal_number) int main(int argc, char *argv[]) { struct data data = { 0, }; - const struct spa_pod *params[2]; + const struct spa_pod *params[MAX_PARAMS]; uint32_t n_params = 0; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); @@ -611,6 +775,40 @@ int main(int argc, char *argv[]) 0); params[n_params++] = spa_pod_builder_pop(&b, &f); +#ifdef SUPPORT_DEVICE_ID_NEGOTIATION +#ifdef SUPPORT_DEVICE_IDS_LIST + char *device_ids = NULL; + size_t device_ids_size = 0; + FILE *ms; + size_t i; + + ms = open_memstream(&device_ids, &device_ids_size); + fprintf(ms, "["); + for (i = 0; i < SPA_N_ELEMENTS(devices); i++) { + dev_t device_id = makedev(devices[i].major, devices[i].minor); + char device_id_encoded[256]; + + base64_encode((const uint8_t *) &device_id, sizeof (device_id), device_id_encoded, '\0'); + if (i > 0) + fprintf(ms, ","); + fprintf(ms, "\"%s\"", device_id_encoded); + } + fprintf(ms, "]"); + fclose(ms); +#endif /* SUPPORT_DEVICE_IDS_LIST */ + + params[n_params++] = + spa_param_dict_build_dict(&b, SPA_PARAM_Capability, + &SPA_DICT_ITEMS(SPA_DICT_ITEM(PW_CAPABILITY_DEVICE_ID_NEGOTIATION, "true"), +#ifdef SUPPORT_DEVICE_IDS_LIST + SPA_DICT_ITEM(PW_CAPABILITY_DEVICE_IDS, device_ids) +#endif /* SUPPORT_DEVICE_IDS_LIST */ + )); +#ifdef SUPPORT_DEVICE_IDS_LIST + free(device_ids); +#endif /* SUPPORT_DEVICE_IDS_LIST */ +#endif /* SUPPORT_DEVICE_ID_NEGOTIATION */ + /* now connect the stream, we need a direction (input/output), * an optional target node to connect to, some flags and parameters. *