Merge branch 'wip/tweak-dev-id-negotiation-api' into 'master'

Tweak device ID negotiation API

See merge request pipewire/pipewire!2672
This commit is contained in:
Jonas Ådahl 2026-01-22 17:08:23 +01:00
commit c07bde51ff
5 changed files with 122 additions and 89 deletions

View file

@ -313,12 +313,13 @@ performed.
Device ID negotiation needs explicit support by both end points of a stream, thus, the Device ID negotiation needs explicit support by both end points of a stream, thus, the
first step of negotiation is discovering whether other peer has support for it. This is first step of negotiation is discovering whether other peer has support for it. This is
done by advertising a \ref SPA_PARAM_Capability with the key \ref done by advertising a \ref SPA_PARAM_Capability with the key \ref
PW_CAPABILITY_DEVICE_ID_NEGOTIATION and value `true` PW_CAPABILITY_DEVICE_ID_NEGOTIATION and value `1` which corresponds to the
current negotiation API version.
``` ```
spa_param_dict_build_dict(&b, SPA_PARAM_Capability, spa_param_dict_build_dict(&b, SPA_PARAM_Capability,
&SPA_DICT_ITEMS( &SPA_DICT_ITEMS(
SPA_DICT_ITEM(PW_CAPABILITY_DEVICE_ID_NEGOTIATION, "true"))); SPA_DICT_ITEM(PW_CAPABILITY_DEVICE_ID_NEGOTIATION, "1")));
``` ```
To do this, when connecting to the stream, the \ref PW_STREAM_FLAG_INACTIVE flag must be To do this, when connecting to the stream, the \ref PW_STREAM_FLAG_INACTIVE flag must be
@ -364,12 +365,15 @@ with. This can be used to reduce the amount of devices that are queried for form
metadata, which can be a time consuming task, if devices needs to be woken up. metadata, which can be a time consuming task, if devices needs to be woken up.
To achieve this, the consumer adds another \ref SPA_PARAM_PeerCapability item with the key To achieve this, the consumer adds another \ref SPA_PARAM_PeerCapability item with the key
\ref PW_CAPABILITY_DEVICE_IDS set to a string of base 64 encoded `dev_t` device IDs. \ref PW_CAPABILITY_DEVICE_IDS set to a JSON object describing what device IDs are supported.
This JSON object as of version 1 contains a single key "available-devices" that contain
a list of hexadecimal encoded `dev_t` device IDs.
``` ```
char *device_ids = ...; /* Base 64 encoding of a dev_t. */. char *device_ids = "{\"available-devices\": [\"6464000000000000\",\"c8c8000000000000\"]}";
&SPA_DICT_ITEMS( &SPA_DICT_ITEMS(
SPA_DICT_ITEM(PW_CAPABILITY_DEVICE_ID_NEGOTIATION, "true"), SPA_DICT_ITEM(PW_CAPABILITY_DEVICE_ID_NEGOTIATION, "1"),
SPA_DICT_ITEM(PW_CAPABILITY_DEVICE_IDS, device_ids))); SPA_DICT_ITEM(PW_CAPABILITY_DEVICE_IDS, device_ids)));
``` ```

View file

@ -1,46 +0,0 @@
/* 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;
}

60
src/examples/utils.h Normal file
View file

@ -0,0 +1,60 @@
/* PipeWire */
/* SPDX-FileCopyrightText: Copyright © 2026 Red Hat */
/* SPDX-License-Identifier: MIT */
static inline char *
encode_hex(const uint8_t *data, size_t size)
{
FILE *ms;
char *encoded = NULL;
size_t encoded_size = 0;
size_t i;
ms = open_memstream(&encoded, &encoded_size);
for (i = 0; i < size; i++) {
fprintf(ms, "%02x", data[i]);
}
fclose(ms);
return encoded;
}
static inline int8_t
ascii_hex_to_hex(uint8_t ascii_hex)
{
if (ascii_hex >= '0' && ascii_hex <= '9')
return ascii_hex - '0';
else if (ascii_hex >= 'a' && ascii_hex <= 'f')
return ascii_hex - 'a' + 10;
else if (ascii_hex >= 'A' && ascii_hex <= 'F')
return ascii_hex - 'A' + 10;
else
return -1;
}
static inline int
decode_hex(const char *encoded, uint8_t *data, size_t size)
{
size_t length;
size_t i;
length = strlen(encoded);
if (size < (length / 2) * sizeof(uint8_t))
return -1;
i = 0;
while (i < length) {
int8_t top = ascii_hex_to_hex(encoded[i]);
int8_t bottom = ascii_hex_to_hex(encoded[i + 1]);
if (top == -1 || bottom == -1)
return -1;
uint8_t el = top << 4 | bottom;
data[i / 2] = el;
i += 2;
}
return 1;
}

View file

@ -29,7 +29,7 @@
#include <pipewire/pipewire.h> #include <pipewire/pipewire.h>
#include <pipewire/capabilities.h> #include <pipewire/capabilities.h>
#include "base64.h" #include "utils.h"
/* Comment out to test device ID negotation backward compatibility. */ /* Comment out to test device ID negotation backward compatibility. */
#define SUPPORT_DEVICE_ID_NEGOTIATION 1 #define SUPPORT_DEVICE_ID_NEGOTIATION 1
@ -372,46 +372,56 @@ collect_device_ids(struct data *data, const char *json)
int len; int len;
const char *value; const char *value;
struct spa_json sub; struct spa_json sub;
char key[1024];
if ((len = spa_json_begin(&it, json, strlen(json), &value)) <= 0) { if ((len = spa_json_begin(&it, json, strlen(json), &value)) <= 0) {
fprintf(stderr, "invalid device IDs value\n"); fprintf(stderr, "invalid device IDs value\n");
return; return;
} }
if (!spa_json_is_array(value, len)) { if (!spa_json_is_object(value, len)) {
fprintf(stderr, "device IDs not array\n"); fprintf(stderr, "device IDs not object\n");
return; return;
} }
spa_json_enter(&it, &sub); spa_json_enter(&it, &sub);
while ((len = spa_json_next(&sub, &value)) > 0) { while ((len = spa_json_object_next(&sub, key, sizeof(key), &value)) > 0) {
char *string; struct spa_json devices_sub;
union {
dev_t device_id;
uint8_t buffer[1024];
} dec;
string = alloca(len + 1); if (!spa_json_is_array(value, len)) {
fprintf(stderr, "available-devices not array\n");
if (!spa_json_is_string(value, len)) {
fprintf(stderr, "device ID not string\n");
return; return;
} }
if (spa_json_parse_string(value, len, string) <= 0) { spa_json_enter(&sub, &devices_sub);
fprintf(stderr, "invalid device ID string\n"); while ((len = spa_json_next(&devices_sub, &value)) > 0) {
return; 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 (decode_hex(string, dec.buffer, sizeof (dec.buffer)) < 0) {
fprintf(stderr, "invalid device ID string\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;
} }
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;
} }
} }
@ -438,8 +448,9 @@ discover_capabilities(struct data *data, const struct spa_pod *param)
return; return;
spa_dict_for_each(it, &dict) { spa_dict_for_each(it, &dict) {
if (spa_streq(it->key, PW_CAPABILITY_DEVICE_ID_NEGOTIATION) && if (spa_streq(it->key, PW_CAPABILITY_DEVICE_ID_NEGOTIATION)) {
spa_streq(it->value, "true")) { int version = atoi(it->value);
if (version >= 1)
data->device_negotiation_supported = true; data->device_negotiation_supported = true;
} else if (spa_streq(it->key, PW_CAPABILITY_DEVICE_IDS)) { } else if (spa_streq(it->key, PW_CAPABILITY_DEVICE_IDS)) {
collect_device_ids(data, it->value); collect_device_ids(data, it->value);
@ -787,7 +798,7 @@ int main(int argc, char *argv[])
params[n_params++] = params[n_params++] =
spa_param_dict_build_dict(&b, SPA_PARAM_Capability, spa_param_dict_build_dict(&b, SPA_PARAM_Capability,
&SPA_DICT_ITEMS( &SPA_DICT_ITEMS(
SPA_DICT_ITEM(PW_CAPABILITY_DEVICE_ID_NEGOTIATION, "true"))); SPA_DICT_ITEM(PW_CAPABILITY_DEVICE_ID_NEGOTIATION, "1")));
#endif #endif
/* now connect the stream, we need a direction (input/output), /* now connect the stream, we need a direction (input/output),

View file

@ -30,7 +30,7 @@
#include <pipewire/pipewire.h> #include <pipewire/pipewire.h>
#include <pipewire/capabilities.h> #include <pipewire/capabilities.h>
#include "base64.h" #include "utils.h"
/* Comment out to test device ID negotation backward compatibility. */ /* Comment out to test device ID negotation backward compatibility. */
#define SUPPORT_DEVICE_ID_NEGOTIATION 1 #define SUPPORT_DEVICE_ID_NEGOTIATION 1
@ -450,8 +450,9 @@ discover_capabilities(struct data *data, const struct spa_pod *param)
return; return;
spa_dict_for_each(it, &dict) { spa_dict_for_each(it, &dict) {
if (spa_streq(it->key, PW_CAPABILITY_DEVICE_ID_NEGOTIATION) && if (spa_streq(it->key, PW_CAPABILITY_DEVICE_ID_NEGOTIATION)) {
spa_streq(it->value, "true")) { int version = atoi(it->value);
if (version >= 1)
data->device_negotiation_supported = true; data->device_negotiation_supported = true;
} }
} }
@ -783,23 +784,26 @@ int main(int argc, char *argv[])
size_t i; size_t i;
ms = open_memstream(&device_ids, &device_ids_size); ms = open_memstream(&device_ids, &device_ids_size);
fprintf(ms, "["); fprintf(ms, "{\"available-devices\": [");
for (i = 0; i < SPA_N_ELEMENTS(devices); i++) { for (i = 0; i < SPA_N_ELEMENTS(devices); i++) {
dev_t device_id = makedev(devices[i].major, devices[i].minor); dev_t device_id = makedev(devices[i].major, devices[i].minor);
char device_id_encoded[256]; char *device_id_encoded;
device_id_encoded = encode_hex((const uint8_t *) &device_id, sizeof (device_id));
base64_encode((const uint8_t *) &device_id, sizeof (device_id), device_id_encoded, '\0');
if (i > 0) if (i > 0)
fprintf(ms, ","); fprintf(ms, ",");
fprintf(ms, "\"%s\"", device_id_encoded); fprintf(ms, "\"%s\"", device_id_encoded);
free(device_id_encoded);
} }
fprintf(ms, "]"); fprintf(ms, "]}");
fclose(ms); fclose(ms);
#endif /* SUPPORT_DEVICE_IDS_LIST */ #endif /* SUPPORT_DEVICE_IDS_LIST */
params[n_params++] = params[n_params++] =
spa_param_dict_build_dict(&b, SPA_PARAM_Capability, spa_param_dict_build_dict(&b, SPA_PARAM_Capability,
&SPA_DICT_ITEMS(SPA_DICT_ITEM(PW_CAPABILITY_DEVICE_ID_NEGOTIATION, "true"), &SPA_DICT_ITEMS(SPA_DICT_ITEM(PW_CAPABILITY_DEVICE_ID_NEGOTIATION, "1"),
#ifdef SUPPORT_DEVICE_IDS_LIST #ifdef SUPPORT_DEVICE_IDS_LIST
SPA_DICT_ITEM(PW_CAPABILITY_DEVICE_IDS, device_ids) SPA_DICT_ITEM(PW_CAPABILITY_DEVICE_IDS, device_ids)
#endif /* SUPPORT_DEVICE_IDS_LIST */ #endif /* SUPPORT_DEVICE_IDS_LIST */