/* 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. */ #include #include #include #include #include #include #include "defs.h" #include "format.h" #include "internal.h" #include "message.h" #include "remap.h" #include "volume.h" #define MAX_SIZE (256*1024) #define MAX_ALLOCATED (16*1024 *1024) #define VOLUME_MUTED ((uint32_t) 0U) #define VOLUME_NORM ((uint32_t) 0x10000U) #define VOLUME_MAX ((uint32_t) UINT32_MAX/2) #define PA_CHANNELS_MAX (32u) PW_LOG_TOPIC_EXTERN(pulse_conn); #define PW_LOG_TOPIC_DEFAULT pulse_conn 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; } 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(props_key_map, 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; if (m->offset + 1 > m->length) return -ENOSPC; maxlen = m->length - m->offset; n = strnlen(SPA_PTROFF(m->data, m->offset, char), maxlen); if (n == maxlen) return -EINVAL; *str = SPA_PTROFF(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; } int message_get(struct message *m, ...) { va_list va; int res = 0; 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) goto done; switch (dtag) { case TAG_STRING: if (tag != TAG_STRING) goto invalid; if ((res = read_string(m, va_arg(va, char**))) < 0) goto done; break; case TAG_STRING_NULL: if (tag != TAG_STRING) goto invalid; *va_arg(va, char**) = NULL; break; case TAG_U8: if (dtag != tag) goto invalid; if ((res = read_u8(m, va_arg(va, uint8_t*))) < 0) goto done; break; case TAG_U32: if (dtag != tag) goto invalid; if ((res = read_u32(m, va_arg(va, uint32_t*))) < 0) goto done; break; case TAG_S64: case TAG_U64: case TAG_USEC: if (dtag != tag) goto invalid; if ((res = read_u64(m, va_arg(va, uint64_t*))) < 0) goto done; break; case TAG_SAMPLE_SPEC: if (dtag != tag) goto invalid; if ((res = read_sample_spec(m, va_arg(va, struct sample_spec*))) < 0) goto done; break; case TAG_ARBITRARY: { const void **val = va_arg(va, const void**); size_t *len = va_arg(va, size_t*); if (dtag != tag) goto invalid; if ((res = read_arbitrary(m, val, len)) < 0) goto done; break; } case TAG_BOOLEAN_TRUE: if (tag != TAG_BOOLEAN) goto invalid; *va_arg(va, bool*) = true; break; case TAG_BOOLEAN_FALSE: if (tag != TAG_BOOLEAN) goto invalid; *va_arg(va, bool*) = false; break; case TAG_TIMEVAL: if (dtag != tag) goto invalid; if ((res = read_timeval(m, va_arg(va, struct timeval*))) < 0) goto done; break; case TAG_CHANNEL_MAP: if (dtag != tag) goto invalid; if ((res = read_channel_map(m, va_arg(va, struct channel_map*))) < 0) goto done; break; case TAG_CVOLUME: if (dtag != tag) goto invalid; if ((res = read_cvolume(m, va_arg(va, struct volume*))) < 0) goto done; break; case TAG_PROPLIST: if (dtag != tag) goto invalid; if ((res = read_props(m, va_arg(va, struct pw_properties*), true)) < 0) goto done; break; case TAG_VOLUME: if (dtag != tag) goto invalid; if ((res = read_volume(m, va_arg(va, float*))) < 0) goto done; break; case TAG_FORMAT_INFO: if (dtag != tag) goto invalid; if ((res = read_format_info(m, va_arg(va, struct format_info*))) < 0) goto done; break; } } res = 0; goto done; invalid: res = -EINVAL; done: va_end(va); return res; } static int ensure_size(struct message *m, uint32_t size) { uint32_t alloc, diff; void *data; if (m->length > m->allocated) return -ENOMEM; if (m->length + size <= m->allocated) return size; alloc = SPA_ROUND_UP_N(SPA_MAX(m->allocated + size, 4096u), 4096u); diff = alloc - m->allocated; if ((data = realloc(m->data, alloc)) == NULL) { free(m->data); m->data = NULL; m->impl->stat.allocated -= m->allocated; m->allocated = 0; return -errno; } m->impl->stat.allocated += diff; m->impl->stat.accumulated += diff; m->data = data; 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_PTROFF(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) { uint32_t channels = SPA_MIN(ss->channels, PA_CHANNELS_MAX); write_8(m, TAG_SAMPLE_SPEC); write_8(m, format_id2pa(ss->format)); write_8(m, 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, channels = SPA_MIN(map->channels, PA_CHANNELS_MAX); write_8(m, TAG_CHANNEL_MAP); write_8(m, channels); for (i = 0; i < 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; uint32_t channels = SPA_MIN(vol->channels, PA_CHANNELS_MAX); write_8(m, TAG_CVOLUME); write_8(m, channels); for (i = 0; i < 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, *id, *prefix; char *b; int l; if (media_class == NULL) return; if (spa_streq(media_class, "Stream/Output/Audio")) prefix = "sink-input"; else if (spa_streq(media_class, "Stream/Input/Audio")) prefix = "source-output"; else return; if ((str = media_role) != NULL) id = "media-role"; else if ((str = spa_dict_lookup(dict, PW_KEY_APP_ID)) != NULL) id = "application-id"; else if ((str = spa_dict_lookup(dict, PW_KEY_APP_NAME)) != NULL) id = "application-name"; else if ((str = spa_dict_lookup(dict, PW_KEY_MEDIA_NAME)) != NULL) id = "media-name"; else return; write_string(m, key); l = strlen(prefix) + strlen(id) + strlen(str) + 6; /* "-by-" , ":" and \0 */ b = alloca(l); snprintf(b, l, "%s-by-%s:%s", prefix, id, 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(props_key_map, 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 (spa_streq(key, "media.class")) media_class = val; if (spa_streq(key, "media.role")) 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); } 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; } 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; res = read_props(m, props, false); if (res >= 0) { 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); if (res < 0) return res; 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: enc:%s n_items:%u", o, format_encoding2name(info.encoding), 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; } struct message *message_alloc(struct impl *impl, uint32_t channel, uint32_t size) { struct message *msg; if (!spa_list_is_empty(&impl->free_messages)) { msg = spa_list_first(&impl->free_messages, struct message, link); spa_list_remove(&msg->link); pw_log_trace("using recycled message %p size:%d", msg, size); spa_assert(msg->impl == impl); } else { if ((msg = calloc(1, sizeof(*msg))) == NULL) return NULL; pw_log_trace("new message %p size:%d", msg, size); msg->impl = impl; msg->impl->stat.n_allocated++; msg->impl->stat.n_accumulated++; } if (ensure_size(msg, size) < 0) { message_free(msg, false, true); return NULL; } spa_zero(msg->extra); msg->channel = channel; msg->offset = 0; msg->length = size; return msg; } void message_free(struct message *msg, bool dequeue, bool destroy) { if (dequeue) spa_list_remove(&msg->link); if (msg->impl->stat.allocated > MAX_ALLOCATED || msg->allocated > MAX_SIZE) destroy = true; if (destroy) { pw_log_trace("destroy message %p size:%d", msg, msg->allocated); msg->impl->stat.n_allocated--; msg->impl->stat.allocated -= msg->allocated; free(msg->data); free(msg); } else { pw_log_trace("recycle message %p size:%d/%d", msg, msg->length, msg->allocated); spa_list_append(&msg->impl->free_messages, &msg->link); msg->length = 0; } }