mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-11-02 09:01:50 -05:00
Convert to an from pulse enum to id in the message layer so that we can always just deal with native spa types. Use the channelmap in stream-restore Parse the channelmap from properties.
855 lines
19 KiB
C
855 lines
19 KiB
C
/* PipeWire
|
|
*
|
|
* Copyright © 2020 Wim Taymans
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice (including the next
|
|
* paragraph) shall be included in all copies or substantial portions of the
|
|
* Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
#define VOLUME_MUTED ((uint32_t) 0U)
|
|
#define VOLUME_NORM ((uint32_t) 0x10000U)
|
|
#define VOLUME_MAX ((uint32_t) UINT32_MAX/2)
|
|
|
|
static inline uint32_t volume_from_linear(float vol)
|
|
{
|
|
uint32_t v;
|
|
if (vol <= 0.0f)
|
|
v = VOLUME_MUTED;
|
|
else
|
|
v = SPA_CLAMP((uint64_t) lround(cbrt(vol) * VOLUME_NORM),
|
|
VOLUME_MUTED, VOLUME_MAX);
|
|
return v;
|
|
}
|
|
|
|
static inline float volume_to_linear(uint32_t vol)
|
|
{
|
|
float v = ((float)vol) / VOLUME_NORM;
|
|
return v * v * v;
|
|
}
|
|
|
|
struct str_map {
|
|
const char *pw_str;
|
|
const char *pa_str;
|
|
const struct str_map *child;
|
|
};
|
|
|
|
const struct str_map media_role_map[] = {
|
|
{ "Movie", "video", },
|
|
{ "Music", "music", },
|
|
{ "Game", "game", },
|
|
{ "Notification", "event", },
|
|
{ "Communication", "phone", },
|
|
{ "Movie", "animation", },
|
|
{ "Production", "production", },
|
|
{ "Accessibility", "a11y", },
|
|
{ "Test", "test", },
|
|
{ NULL, NULL },
|
|
};
|
|
|
|
const struct str_map key_table[] = {
|
|
{ PW_KEY_DEVICE_BUS_PATH, "device.bus_path" },
|
|
{ PW_KEY_DEVICE_FORM_FACTOR, "device.form_factor" },
|
|
{ PW_KEY_DEVICE_ICON_NAME, "device.icon_name" },
|
|
{ PW_KEY_DEVICE_INTENDED_ROLES, "device.intended_roles" },
|
|
{ PW_KEY_MEDIA_ICON_NAME, "media.icon_name" },
|
|
{ PW_KEY_APP_ICON_NAME, "application.icon_name" },
|
|
{ PW_KEY_APP_PROCESS_MACHINE_ID, "application.process.machine_id" },
|
|
{ PW_KEY_APP_PROCESS_SESSION_ID, "application.process.session_id" },
|
|
{ PW_KEY_MEDIA_ROLE, "media.role", media_role_map },
|
|
{ NULL, NULL },
|
|
};
|
|
|
|
static inline const struct str_map *str_map_find(const struct str_map *map, const char *pw, const char *pa)
|
|
{
|
|
uint32_t i;
|
|
for (i = 0; map[i].pw_str; i++)
|
|
if ((pw && strcmp(map[i].pw_str, pw) == 0) ||
|
|
(pa && strcmp(map[i].pa_str, pa) == 0))
|
|
return &map[i];
|
|
return NULL;
|
|
}
|
|
|
|
struct descriptor {
|
|
uint32_t length;
|
|
uint32_t channel;
|
|
uint32_t offset_hi;
|
|
uint32_t offset_lo;
|
|
uint32_t flags;
|
|
};
|
|
|
|
enum {
|
|
TAG_INVALID = 0,
|
|
TAG_STRING = 't',
|
|
TAG_STRING_NULL = 'N',
|
|
TAG_U32 = 'L',
|
|
TAG_U8 = 'B',
|
|
TAG_U64 = 'R',
|
|
TAG_S64 = 'r',
|
|
TAG_SAMPLE_SPEC = 'a',
|
|
TAG_ARBITRARY = 'x',
|
|
TAG_BOOLEAN_TRUE = '1',
|
|
TAG_BOOLEAN_FALSE = '0',
|
|
TAG_BOOLEAN = TAG_BOOLEAN_TRUE,
|
|
TAG_TIMEVAL = 'T',
|
|
TAG_USEC = 'U' /* 64bit unsigned */,
|
|
TAG_CHANNEL_MAP = 'm',
|
|
TAG_CVOLUME = 'v',
|
|
TAG_PROPLIST = 'P',
|
|
TAG_VOLUME = 'V',
|
|
TAG_FORMAT_INFO = 'f',
|
|
};
|
|
|
|
struct message {
|
|
struct spa_list link;
|
|
uint32_t extra[4];
|
|
uint32_t channel;
|
|
uint32_t allocated;
|
|
uint32_t length;
|
|
uint32_t offset;
|
|
uint8_t *data;
|
|
};
|
|
|
|
static int message_get(struct message *m, ...);
|
|
|
|
static int read_u8(struct message *m, uint8_t *val)
|
|
{
|
|
if (m->offset + 1 > m->length)
|
|
return -ENOSPC;
|
|
*val = m->data[m->offset];
|
|
m->offset++;
|
|
return 0;
|
|
}
|
|
|
|
static int read_u32(struct message *m, uint32_t *val)
|
|
{
|
|
if (m->offset + 4 > m->length)
|
|
return -ENOSPC;
|
|
memcpy(val, &m->data[m->offset], 4);
|
|
*val = ntohl(*val);
|
|
m->offset += 4;
|
|
return 0;
|
|
}
|
|
static int read_u64(struct message *m, uint64_t *val)
|
|
{
|
|
uint32_t tmp;
|
|
int res;
|
|
if ((res = read_u32(m, &tmp)) < 0)
|
|
return res;
|
|
*val = ((uint64_t)tmp) << 32;
|
|
if ((res = read_u32(m, &tmp)) < 0)
|
|
return res;
|
|
*val |= tmp;
|
|
return 0;
|
|
}
|
|
|
|
static int read_sample_spec(struct message *m, struct sample_spec *ss)
|
|
{
|
|
int res;
|
|
uint8_t tmp;
|
|
if ((res = read_u8(m, &tmp)) < 0)
|
|
return res;
|
|
ss->format = format_pa2id(tmp);
|
|
if ((res = read_u8(m, &ss->channels)) < 0)
|
|
return res;
|
|
return read_u32(m, &ss->rate);
|
|
}
|
|
|
|
static int read_props(struct message *m, struct pw_properties *props, bool remap)
|
|
{
|
|
int res;
|
|
|
|
while (true) {
|
|
const char *key;
|
|
const void *data;
|
|
uint32_t length;
|
|
size_t size;
|
|
const struct str_map *map;
|
|
|
|
if ((res = message_get(m,
|
|
TAG_STRING, &key,
|
|
TAG_INVALID)) < 0)
|
|
return res;
|
|
|
|
if (key == NULL)
|
|
break;
|
|
|
|
if ((res = message_get(m,
|
|
TAG_U32, &length,
|
|
TAG_INVALID)) < 0)
|
|
return res;
|
|
if (length > MAX_TAG_SIZE)
|
|
return -EINVAL;
|
|
|
|
if ((res = message_get(m,
|
|
TAG_ARBITRARY, &data, &size,
|
|
TAG_INVALID)) < 0)
|
|
return res;
|
|
|
|
if (remap && (map = str_map_find(key_table, NULL, key)) != NULL) {
|
|
key = map->pw_str;
|
|
if (map->child != NULL &&
|
|
(map = str_map_find(map->child, NULL, data)) != NULL)
|
|
data = map->pw_str;
|
|
}
|
|
pw_properties_set(props, key, data);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int read_arbitrary(struct message *m, const void **val, size_t *length)
|
|
{
|
|
uint32_t len;
|
|
int res;
|
|
if ((res = read_u32(m, &len)) < 0)
|
|
return res;
|
|
if (m->offset + len > m->length)
|
|
return -ENOSPC;
|
|
*val = m->data + m->offset;
|
|
m->offset += len;
|
|
if (length)
|
|
*length = len;
|
|
return 0;
|
|
}
|
|
|
|
static int read_string(struct message *m, char **str)
|
|
{
|
|
uint32_t n, maxlen = m->length - m->offset;
|
|
n = strnlen(SPA_MEMBER(m->data, m->offset, char), maxlen);
|
|
if (n == maxlen)
|
|
return -EINVAL;
|
|
*str = SPA_MEMBER(m->data, m->offset, char);
|
|
m->offset += n + 1;
|
|
return 0;
|
|
}
|
|
|
|
static int read_timeval(struct message *m, struct timeval *tv)
|
|
{
|
|
int res;
|
|
uint32_t tmp;
|
|
|
|
if ((res = read_u32(m, &tmp)) < 0)
|
|
return res;
|
|
tv->tv_sec = tmp;
|
|
if ((res = read_u32(m, &tmp)) < 0)
|
|
return res;
|
|
tv->tv_usec = tmp;
|
|
return 0;
|
|
}
|
|
|
|
static int read_channel_map(struct message *m, struct channel_map *map)
|
|
{
|
|
int res;
|
|
uint8_t i, tmp;
|
|
|
|
if ((res = read_u8(m, &map->channels)) < 0)
|
|
return res;
|
|
if (map->channels > CHANNELS_MAX)
|
|
return -EINVAL;
|
|
for (i = 0; i < map->channels; i ++) {
|
|
if ((res = read_u8(m, &tmp)) < 0)
|
|
return res;
|
|
map->map[i] = channel_pa2id(tmp);
|
|
}
|
|
return 0;
|
|
}
|
|
static int read_volume(struct message *m, float *vol)
|
|
{
|
|
int res;
|
|
uint32_t v;
|
|
if ((res = read_u32(m, &v)) < 0)
|
|
return res;
|
|
*vol = volume_to_linear(v);
|
|
return 0;
|
|
}
|
|
|
|
static int read_cvolume(struct message *m, struct volume *vol)
|
|
{
|
|
int res;
|
|
uint8_t i;
|
|
|
|
if ((res = read_u8(m, &vol->channels)) < 0)
|
|
return res;
|
|
if (vol->channels > CHANNELS_MAX)
|
|
return -EINVAL;
|
|
for (i = 0; i < vol->channels; i ++) {
|
|
if ((res = read_volume(m, &vol->values[i])) < 0)
|
|
return res;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int read_format_info(struct message *m, struct format_info *info)
|
|
{
|
|
int res;
|
|
uint8_t tag, encoding;
|
|
|
|
spa_zero(*info);
|
|
if ((res = read_u8(m, &tag)) < 0)
|
|
return res;
|
|
if (tag != TAG_U8)
|
|
return -EPROTO;
|
|
if ((res = read_u8(m, &encoding)) < 0)
|
|
return res;
|
|
info->encoding = encoding;
|
|
|
|
if ((res = read_u8(m, &tag)) < 0)
|
|
return res;
|
|
if (tag != TAG_PROPLIST)
|
|
return -EPROTO;
|
|
|
|
info->props = pw_properties_new(NULL, NULL);
|
|
if (info->props == NULL)
|
|
return -errno;
|
|
if ((res = read_props(m, info->props, false)) < 0)
|
|
format_info_clear(info);
|
|
return res;
|
|
}
|
|
|
|
static int message_get(struct message *m, ...)
|
|
{
|
|
va_list va;
|
|
int res;
|
|
|
|
va_start(va, m);
|
|
|
|
while (true) {
|
|
int tag = va_arg(va, int);
|
|
uint8_t dtag;
|
|
if (tag == TAG_INVALID)
|
|
break;
|
|
|
|
if ((res = read_u8(m, &dtag)) < 0)
|
|
return res;
|
|
|
|
switch (dtag) {
|
|
case TAG_STRING:
|
|
if (tag != TAG_STRING)
|
|
return -EINVAL;
|
|
if ((res = read_string(m, va_arg(va, char**))) < 0)
|
|
return res;
|
|
break;
|
|
case TAG_STRING_NULL:
|
|
if (tag != TAG_STRING)
|
|
return -EINVAL;
|
|
*va_arg(va, char**) = NULL;
|
|
break;
|
|
case TAG_U8:
|
|
if (dtag != tag)
|
|
return -EINVAL;
|
|
if ((res = read_u8(m, va_arg(va, uint8_t*))) < 0)
|
|
return res;
|
|
break;
|
|
case TAG_U32:
|
|
if (dtag != tag)
|
|
return -EINVAL;
|
|
if ((res = read_u32(m, va_arg(va, uint32_t*))) < 0)
|
|
return res;
|
|
break;
|
|
case TAG_S64:
|
|
case TAG_U64:
|
|
case TAG_USEC:
|
|
if (dtag != tag)
|
|
return -EINVAL;
|
|
if ((res = read_u64(m, va_arg(va, uint64_t*))) < 0)
|
|
return res;
|
|
break;
|
|
case TAG_SAMPLE_SPEC:
|
|
if (dtag != tag)
|
|
return -EINVAL;
|
|
if ((res = read_sample_spec(m, va_arg(va, struct sample_spec*))) < 0)
|
|
return res;
|
|
break;
|
|
case TAG_ARBITRARY:
|
|
{
|
|
const void **val = va_arg(va, const void**);
|
|
size_t *len = va_arg(va, size_t*);
|
|
if (dtag != tag)
|
|
return -EINVAL;
|
|
if ((res = read_arbitrary(m, val, len)) < 0)
|
|
return res;
|
|
break;
|
|
}
|
|
case TAG_BOOLEAN_TRUE:
|
|
if (tag != TAG_BOOLEAN)
|
|
return -EINVAL;
|
|
*va_arg(va, bool*) = true;
|
|
break;
|
|
case TAG_BOOLEAN_FALSE:
|
|
if (tag != TAG_BOOLEAN)
|
|
return -EINVAL;
|
|
*va_arg(va, bool*) = false;
|
|
break;
|
|
case TAG_TIMEVAL:
|
|
if (dtag != tag)
|
|
return -EINVAL;
|
|
if ((res = read_timeval(m, va_arg(va, struct timeval*))) < 0)
|
|
return res;
|
|
break;
|
|
case TAG_CHANNEL_MAP:
|
|
if (dtag != tag)
|
|
return -EINVAL;
|
|
if ((res = read_channel_map(m, va_arg(va, struct channel_map*))) < 0)
|
|
return res;
|
|
break;
|
|
case TAG_CVOLUME:
|
|
if (dtag != tag)
|
|
return -EINVAL;
|
|
if ((res = read_cvolume(m, va_arg(va, struct volume*))) < 0)
|
|
return res;
|
|
break;
|
|
case TAG_PROPLIST:
|
|
if (dtag != tag)
|
|
return -EINVAL;
|
|
if ((res = read_props(m, va_arg(va, struct pw_properties*), true)) < 0)
|
|
return res;
|
|
break;
|
|
case TAG_VOLUME:
|
|
if (dtag != tag)
|
|
return -EINVAL;
|
|
if ((res = read_volume(m, va_arg(va, float*))) < 0)
|
|
return res;
|
|
break;
|
|
case TAG_FORMAT_INFO:
|
|
if (dtag != tag)
|
|
return -EINVAL;
|
|
if ((res = read_format_info(m, va_arg(va, struct format_info*))) < 0)
|
|
return res;
|
|
break;
|
|
}
|
|
}
|
|
va_end(va);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ensure_size(struct message *m, uint32_t size)
|
|
{
|
|
uint32_t alloc;
|
|
if (m->length + size <= m->allocated)
|
|
return size;
|
|
|
|
alloc = SPA_ROUND_UP_N(SPA_MAX(m->allocated + size, 4096u), 4096u);
|
|
if ((m->data = realloc(m->data, alloc)) == NULL)
|
|
return -errno;
|
|
m->allocated = alloc;
|
|
return size;
|
|
}
|
|
|
|
static void write_8(struct message *m, uint8_t val)
|
|
{
|
|
if (ensure_size(m, 1) > 0)
|
|
m->data[m->length] = val;
|
|
m->length++;
|
|
}
|
|
|
|
static void write_32(struct message *m, uint32_t val)
|
|
{
|
|
val = htonl(val);
|
|
if (ensure_size(m, 4) > 0)
|
|
memcpy(m->data + m->length, &val, 4);
|
|
m->length += 4;
|
|
}
|
|
|
|
static void write_string(struct message *m, const char *s)
|
|
{
|
|
write_8(m, s ? TAG_STRING : TAG_STRING_NULL);
|
|
if (s != NULL) {
|
|
int len = strlen(s) + 1;
|
|
if (ensure_size(m, len) > 0)
|
|
strcpy(SPA_MEMBER(m->data, m->length, char), s);
|
|
m->length += len;
|
|
}
|
|
}
|
|
static void write_u8(struct message *m, uint8_t val)
|
|
{
|
|
write_8(m, TAG_U8);
|
|
write_8(m, val);
|
|
}
|
|
|
|
static void write_u32(struct message *m, uint32_t val)
|
|
{
|
|
write_8(m, TAG_U32);
|
|
write_32(m, val);
|
|
}
|
|
|
|
static void write_64(struct message *m, uint8_t tag, uint64_t val)
|
|
{
|
|
write_8(m, tag);
|
|
write_32(m, val >> 32);
|
|
write_32(m, val);
|
|
}
|
|
|
|
static void write_sample_spec(struct message *m, struct sample_spec *ss)
|
|
{
|
|
write_8(m, TAG_SAMPLE_SPEC);
|
|
write_8(m, format_id2pa(ss->format));
|
|
write_8(m, ss->channels);
|
|
write_32(m, ss->rate);
|
|
}
|
|
|
|
static void write_arbitrary(struct message *m, const void *p, size_t length)
|
|
{
|
|
write_8(m, TAG_ARBITRARY);
|
|
write_32(m, length);
|
|
if (ensure_size(m, length) > 0)
|
|
memcpy(m->data + m->length, p, length);
|
|
m->length += length;
|
|
}
|
|
|
|
static void write_boolean(struct message *m, bool val)
|
|
{
|
|
write_8(m, val ? TAG_BOOLEAN_TRUE : TAG_BOOLEAN_FALSE);
|
|
}
|
|
|
|
static void write_timeval(struct message *m, struct timeval *tv)
|
|
{
|
|
write_8(m, TAG_TIMEVAL);
|
|
write_32(m, tv->tv_sec);
|
|
write_32(m, tv->tv_usec);
|
|
}
|
|
|
|
static void write_channel_map(struct message *m, struct channel_map *map)
|
|
{
|
|
uint8_t i;
|
|
uint32_t aux = 0;
|
|
write_8(m, TAG_CHANNEL_MAP);
|
|
write_8(m, map->channels);
|
|
for (i = 0; i < map->channels; i ++)
|
|
write_8(m, channel_id2pa(map->map[i], &aux));
|
|
}
|
|
|
|
static void write_volume(struct message *m, float vol)
|
|
{
|
|
write_8(m, TAG_VOLUME);
|
|
write_32(m, volume_from_linear(vol));
|
|
}
|
|
|
|
static void write_cvolume(struct message *m, struct volume *vol)
|
|
{
|
|
uint8_t i;
|
|
write_8(m, TAG_CVOLUME);
|
|
write_8(m, vol->channels);
|
|
for (i = 0; i < vol->channels; i ++)
|
|
write_32(m, volume_from_linear(vol->values[i]));
|
|
}
|
|
|
|
static void add_stream_group(struct message *m, struct spa_dict *dict, const char *key,
|
|
const char *media_class, const char *media_role)
|
|
{
|
|
const char *str, *fmt, *prefix;
|
|
char *b;
|
|
int l;
|
|
|
|
if (media_class == NULL)
|
|
return;
|
|
if (strcmp(media_class, "Stream/Output/Audio") == 0)
|
|
prefix = "sink-input";
|
|
else if (strcmp(media_class, "Stream/Input/Audio") == 0)
|
|
prefix = "source-output";
|
|
else
|
|
return;
|
|
|
|
if ((str = media_role) != NULL)
|
|
fmt = "%s-by-media-role:%s";
|
|
else if ((str = spa_dict_lookup(dict, PW_KEY_APP_ID)) != NULL)
|
|
fmt = "%s-by-application-id:%s";
|
|
else if ((str = spa_dict_lookup(dict, PW_KEY_APP_NAME)) != NULL)
|
|
fmt = "%s-by-application-name:%s";
|
|
else if ((str = spa_dict_lookup(dict, PW_KEY_MEDIA_NAME)) != NULL)
|
|
fmt = "%s-by-media-name:%s";
|
|
else
|
|
return;
|
|
|
|
write_string(m, key);
|
|
l = strlen(fmt) + strlen(prefix) + strlen(str) - 3;
|
|
b = alloca(l);
|
|
snprintf(b, l, fmt, prefix, str);
|
|
write_u32(m, l);
|
|
write_arbitrary(m, b, l);
|
|
}
|
|
|
|
static void write_dict(struct message *m, struct spa_dict *dict, bool remap)
|
|
{
|
|
const struct spa_dict_item *it;
|
|
|
|
write_8(m, TAG_PROPLIST);
|
|
if (dict != NULL) {
|
|
const char *media_class = NULL, *media_role = NULL;
|
|
spa_dict_for_each(it, dict) {
|
|
const char *key = it->key;
|
|
const char *val = it->value;
|
|
int l;
|
|
const struct str_map *map;
|
|
|
|
if (remap && (map = str_map_find(key_table, key, NULL)) != NULL) {
|
|
key = map->pa_str;
|
|
if (map->child != NULL &&
|
|
(map = str_map_find(map->child, val, NULL)) != NULL)
|
|
val = map->pa_str;
|
|
}
|
|
if (strcmp(key, "media.class") == 0)
|
|
media_class = val;
|
|
if (strcmp(key, "media.role") == 0)
|
|
media_role = val;
|
|
|
|
write_string(m, key);
|
|
l = strlen(val) + 1;
|
|
write_u32(m, l);
|
|
write_arbitrary(m, val, l);
|
|
|
|
}
|
|
if (remap)
|
|
add_stream_group(m, dict, "module-stream-restore.id",
|
|
media_class, media_role);
|
|
}
|
|
write_string(m, NULL);
|
|
}
|
|
|
|
static void write_format_info(struct message *m, struct format_info *info)
|
|
{
|
|
write_8(m, TAG_FORMAT_INFO);
|
|
write_u8(m, (uint8_t) info->encoding);
|
|
write_dict(m, info->props ? &info->props->dict : NULL, false);
|
|
}
|
|
|
|
static int message_put(struct message *m, ...)
|
|
{
|
|
va_list va;
|
|
|
|
if (m == NULL)
|
|
return -EINVAL;
|
|
|
|
va_start(va, m);
|
|
|
|
while (true) {
|
|
int tag = va_arg(va, int);
|
|
if (tag == TAG_INVALID)
|
|
break;
|
|
|
|
switch (tag) {
|
|
case TAG_STRING:
|
|
write_string(m, va_arg(va, const char *));
|
|
break;
|
|
case TAG_U8:
|
|
write_u8(m, (uint8_t)va_arg(va, int));
|
|
break;
|
|
case TAG_U32:
|
|
write_u32(m, (uint32_t)va_arg(va, uint32_t));
|
|
break;
|
|
case TAG_S64:
|
|
case TAG_U64:
|
|
case TAG_USEC:
|
|
write_64(m, tag, va_arg(va, uint64_t));
|
|
break;
|
|
case TAG_SAMPLE_SPEC:
|
|
write_sample_spec(m, va_arg(va, struct sample_spec*));
|
|
break;
|
|
case TAG_ARBITRARY:
|
|
{
|
|
const void *p = va_arg(va, const void*);
|
|
size_t length = va_arg(va, size_t);
|
|
write_arbitrary(m, p, length);
|
|
break;
|
|
}
|
|
case TAG_BOOLEAN:
|
|
write_boolean(m, va_arg(va, int));
|
|
break;
|
|
case TAG_TIMEVAL:
|
|
write_timeval(m, va_arg(va, struct timeval*));
|
|
break;
|
|
case TAG_CHANNEL_MAP:
|
|
write_channel_map(m, va_arg(va, struct channel_map*));
|
|
break;
|
|
case TAG_CVOLUME:
|
|
write_cvolume(m, va_arg(va, struct volume*));
|
|
break;
|
|
case TAG_PROPLIST:
|
|
write_dict(m, va_arg(va, struct spa_dict*), true);
|
|
break;
|
|
case TAG_VOLUME:
|
|
write_volume(m, va_arg(va, double));
|
|
break;
|
|
case TAG_FORMAT_INFO:
|
|
write_format_info(m, va_arg(va, struct format_info*));
|
|
break;
|
|
}
|
|
}
|
|
va_end(va);
|
|
|
|
if (m->length > m->allocated)
|
|
return -ENOMEM;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int message_dump(enum spa_log_level level, struct message *m)
|
|
{
|
|
int res;
|
|
uint32_t i, offset = m->offset, o;
|
|
|
|
pw_log(level, "message: len:%d alloc:%u", m->length, m->allocated);
|
|
while (true) {
|
|
uint8_t tag;
|
|
|
|
o = m->offset;
|
|
if (read_u8(m, &tag) < 0)
|
|
break;
|
|
|
|
switch (tag) {
|
|
case TAG_STRING:
|
|
{
|
|
char *val;
|
|
if ((res = read_string(m, &val)) < 0)
|
|
return res;
|
|
pw_log(level, "%u: string: '%s'", o, val);
|
|
break;
|
|
}
|
|
case TAG_STRING_NULL:
|
|
pw_log(level, "%u: string: NULL", o);
|
|
break;
|
|
case TAG_U8:
|
|
{
|
|
uint8_t val;
|
|
if ((res = read_u8(m, &val)) < 0)
|
|
return res;
|
|
pw_log(level, "%u: u8: %u", o, val);
|
|
break;
|
|
}
|
|
case TAG_U32:
|
|
{
|
|
uint32_t val;
|
|
if ((res = read_u32(m, &val)) < 0)
|
|
return res;
|
|
pw_log(level, "%u: u32: %u", o, val);
|
|
break;
|
|
}
|
|
case TAG_S64:
|
|
{
|
|
uint64_t val;
|
|
if ((res = read_u64(m, &val)) < 0)
|
|
return res;
|
|
pw_log(level, "%u: s64: %"PRIi64"", o, (int64_t)val);
|
|
break;
|
|
}
|
|
case TAG_U64:
|
|
{
|
|
uint64_t val;
|
|
if ((res = read_u64(m, &val)) < 0)
|
|
return res;
|
|
pw_log(level, "%u: u64: %"PRIu64"", o, val);
|
|
break;
|
|
}
|
|
case TAG_USEC:
|
|
{
|
|
uint64_t val;
|
|
if ((res = read_u64(m, &val)) < 0)
|
|
return res;
|
|
pw_log(level, "%u: u64: %"PRIu64"", o, val);
|
|
break;
|
|
}
|
|
case TAG_SAMPLE_SPEC:
|
|
{
|
|
struct sample_spec ss;
|
|
if ((res = read_sample_spec(m, &ss)) < 0)
|
|
return res;
|
|
pw_log(level, "%u: ss: format:%s rate:%d channels:%u", o,
|
|
format_id2name(ss.format), ss.rate,
|
|
ss.channels);
|
|
break;
|
|
}
|
|
case TAG_ARBITRARY:
|
|
{
|
|
const void *mem;
|
|
size_t len;
|
|
if ((res = read_arbitrary(m, &mem, &len)) < 0)
|
|
return res;
|
|
spa_debug_mem(0, mem, len);
|
|
break;
|
|
}
|
|
case TAG_BOOLEAN_TRUE:
|
|
pw_log(level, "%u: bool: true", o);
|
|
break;
|
|
case TAG_BOOLEAN_FALSE:
|
|
pw_log(level, "%u: bool: false", o);
|
|
break;
|
|
case TAG_TIMEVAL:
|
|
{
|
|
struct timeval tv;
|
|
if ((res = read_timeval(m, &tv)) < 0)
|
|
return res;
|
|
pw_log(level, "%u: timeval: %lu:%lu", o, tv.tv_sec, tv.tv_usec);
|
|
break;
|
|
}
|
|
case TAG_CHANNEL_MAP:
|
|
{
|
|
struct channel_map map;
|
|
if ((res = read_channel_map(m, &map)) < 0)
|
|
return res;
|
|
pw_log(level, "%u: channelmap: channels:%u", o, map.channels);
|
|
for (i = 0; i < map.channels; i++)
|
|
pw_log(level, " %d: %s", i, channel_id2name(map.map[i]));
|
|
break;
|
|
}
|
|
case TAG_CVOLUME:
|
|
{
|
|
struct volume vol;
|
|
if ((res = read_cvolume(m, &vol)) < 0)
|
|
return res;
|
|
pw_log(level, "%u: cvolume: channels:%u", o, vol.channels);
|
|
for (i = 0; i < vol.channels; i++)
|
|
pw_log(level, " %d: %f", i, vol.values[i]);
|
|
break;
|
|
}
|
|
case TAG_PROPLIST:
|
|
{
|
|
struct pw_properties *props = pw_properties_new(NULL, NULL);
|
|
const struct spa_dict_item *it;
|
|
if ((res = read_props(m, props, false)) < 0)
|
|
return res;
|
|
pw_log(level, "%u: props: n_items:%u", o, props->dict.n_items);
|
|
spa_dict_for_each(it, &props->dict)
|
|
pw_log(level, " '%s': '%s'", it->key, it->value);
|
|
pw_properties_free(props);
|
|
break;
|
|
}
|
|
case TAG_VOLUME:
|
|
{
|
|
float vol;
|
|
if ((res = read_volume(m, &vol)) < 0)
|
|
return res;
|
|
pw_log(level, "%u: volume: %f", o, vol);
|
|
break;
|
|
}
|
|
case TAG_FORMAT_INFO:
|
|
{
|
|
struct format_info info;
|
|
const struct spa_dict_item *it;
|
|
if ((res = read_format_info(m, &info)) < 0)
|
|
return res;
|
|
pw_log(level, "%u: format-info: n_items:%u", o, info.props->dict.n_items);
|
|
spa_dict_for_each(it, &info.props->dict)
|
|
pw_log(level, " '%s': '%s'", it->key, it->value);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
m->offset = offset;
|
|
|
|
return 0;
|
|
}
|