diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:28:17 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:28:17 +0000 |
commit | 7a46c07230b8d8108c0e8e80df4522d0ac116538 (patch) | |
tree | d483300dab478b994fe199a5d19d18d74153718a /spa/plugins/bluez5/codec-loader.c | |
parent | Initial commit. (diff) | |
download | pipewire-upstream.tar.xz pipewire-upstream.zip |
Adding upstream version 0.3.65.upstream/0.3.65upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | spa/plugins/bluez5/codec-loader.c | 234 |
1 files changed, 234 insertions, 0 deletions
diff --git a/spa/plugins/bluez5/codec-loader.c b/spa/plugins/bluez5/codec-loader.c new file mode 100644 index 0000000..f8363f8 --- /dev/null +++ b/spa/plugins/bluez5/codec-loader.c @@ -0,0 +1,234 @@ +/* Spa A2DP codec API + * + * 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 "config.h" + +#include <spa/utils/string.h> + +#include "defs.h" +#include "codec-loader.h" + +#define MEDIA_CODEC_LIB_BASE "bluez5/libspa-codec-bluez5-" + +/* AVDTP allows 0x3E endpoints, can't have more codecs than that */ +#define MAX_CODECS 0x3E +#define MAX_HANDLES MAX_CODECS + +static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.bluez5.codecs"); +#undef SPA_LOG_TOPIC_DEFAULT +#define SPA_LOG_TOPIC_DEFAULT &log_topic + +struct impl { + const struct media_codec *codecs[MAX_CODECS + 1]; + struct spa_handle *handles[MAX_HANDLES]; + size_t n_codecs; + size_t n_handles; + struct spa_plugin_loader *loader; + struct spa_log *log; +}; + +static int codec_order(const struct media_codec *c) +{ + static const enum spa_bluetooth_audio_codec order[] = { + SPA_BLUETOOTH_AUDIO_CODEC_LC3, + SPA_BLUETOOTH_AUDIO_CODEC_LDAC, + SPA_BLUETOOTH_AUDIO_CODEC_APTX_HD, + SPA_BLUETOOTH_AUDIO_CODEC_APTX, + SPA_BLUETOOTH_AUDIO_CODEC_AAC, + SPA_BLUETOOTH_AUDIO_CODEC_LC3PLUS_HR, + SPA_BLUETOOTH_AUDIO_CODEC_MPEG, + SPA_BLUETOOTH_AUDIO_CODEC_SBC, + SPA_BLUETOOTH_AUDIO_CODEC_SBC_XQ, + SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL, + SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL_DUPLEX, + SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM, + SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM_DUPLEX, + SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05, + SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_51, + SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_71, + SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_DUPLEX, + SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_PRO, + }; + size_t i; + for (i = 0; i < SPA_N_ELEMENTS(order); ++i) + if (c->id == order[i]) + return i; + return SPA_N_ELEMENTS(order); +} + +static int codec_order_cmp(const void *a, const void *b) +{ + const struct media_codec * const *ca = a; + const struct media_codec * const *cb = b; + int ia = codec_order(*ca); + int ib = codec_order(*cb); + if (*ca == *cb) + return 0; + return (ia == ib) ? (*ca < *cb ? -1 : 1) : ia - ib; +} + +static int load_media_codecs_from(struct impl *impl, const char *factory_name, const char *libname) +{ + struct spa_handle *handle = NULL; + void *iface; + const struct spa_bluez5_codec_a2dp *bluez5_codec_a2dp; + int n_codecs = 0; + int res; + size_t i; + struct spa_dict_item info_items[] = { + { SPA_KEY_LIBRARY_NAME, libname }, + }; + struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items); + + handle = spa_plugin_loader_load(impl->loader, factory_name, &info); + if (handle == NULL) { + spa_log_info(impl->log, "Bluetooth codec plugin %s not available", factory_name); + return -ENOENT; + } + + spa_log_debug(impl->log, "loading codecs from %s", factory_name); + + if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Bluez5CodecMedia, &iface)) < 0) { + spa_log_warn(impl->log, "Bluetooth codec plugin %s has no codec interface", + factory_name); + goto fail; + } + + bluez5_codec_a2dp = iface; + + if (bluez5_codec_a2dp->iface.version != SPA_VERSION_BLUEZ5_CODEC_MEDIA) { + spa_log_warn(impl->log, "codec plugin %s has incompatible ABI version (%d != %d)", + factory_name, bluez5_codec_a2dp->iface.version, SPA_VERSION_BLUEZ5_CODEC_MEDIA); + res = -ENOENT; + goto fail; + } + + for (i = 0; bluez5_codec_a2dp->codecs[i]; ++i) { + const struct media_codec *c = bluez5_codec_a2dp->codecs[i]; + const char *ep = c->endpoint_name ? c->endpoint_name : c->name; + size_t j; + + if (!ep) + goto next_codec; + + if (impl->n_codecs >= MAX_CODECS) { + spa_log_error(impl->log, "too many A2DP codecs"); + break; + } + + /* Don't load duplicate endpoints */ + for (j = 0; j < impl->n_codecs; ++j) { + const struct media_codec *c2 = impl->codecs[j]; + const char *ep2 = c2->endpoint_name ? c2->endpoint_name : c2->name; + if (spa_streq(ep, ep2) && c->fill_caps && c2->fill_caps) { + spa_log_debug(impl->log, "media codec %s from %s duplicate endpoint %s", + c->name, factory_name, ep); + goto next_codec; + } + } + + spa_log_debug(impl->log, "loaded media codec %s from %s, endpoint:%s", + c->name, factory_name, ep); + + if (c->set_log) + c->set_log(impl->log); + + impl->codecs[impl->n_codecs++] = c; + ++n_codecs; + + next_codec: + continue; + } + + if (n_codecs > 0) + impl->handles[impl->n_handles++] = handle; + else + spa_plugin_loader_unload(impl->loader, handle); + + return 0; + +fail: + if (handle) + spa_plugin_loader_unload(impl->loader, handle); + return res; +} + +const struct media_codec * const *load_media_codecs(struct spa_plugin_loader *loader, struct spa_log *log) +{ + struct impl *impl; + bool has_sbc; + size_t i; + const struct { const char *factory; const char *lib; } plugins[] = { +#define MEDIA_CODEC_FACTORY_LIB(basename) \ + { MEDIA_CODEC_FACTORY_NAME(basename), MEDIA_CODEC_LIB_BASE basename } + MEDIA_CODEC_FACTORY_LIB("aac"), + MEDIA_CODEC_FACTORY_LIB("aptx"), + MEDIA_CODEC_FACTORY_LIB("faststream"), + MEDIA_CODEC_FACTORY_LIB("ldac"), + MEDIA_CODEC_FACTORY_LIB("sbc"), + MEDIA_CODEC_FACTORY_LIB("lc3plus"), + MEDIA_CODEC_FACTORY_LIB("opus"), + MEDIA_CODEC_FACTORY_LIB("lc3") +#undef MEDIA_CODEC_FACTORY_LIB + }; + + impl = calloc(sizeof(struct impl), 1); + if (impl == NULL) + return NULL; + + impl->loader = loader; + impl->log = log; + + spa_log_topic_init(impl->log, &log_topic); + + for (i = 0; i < SPA_N_ELEMENTS(plugins); ++i) + load_media_codecs_from(impl, plugins[i].factory, plugins[i].lib); + + has_sbc = false; + for (i = 0; i < impl->n_codecs; ++i) + if (impl->codecs[i]->id == SPA_BLUETOOTH_AUDIO_CODEC_SBC) + has_sbc = true; + + if (!has_sbc) { + spa_log_error(impl->log, "failed to load A2DP SBC codec from plugins"); + free_media_codecs(impl->codecs); + errno = ENOENT; + return NULL; + } + + qsort(impl->codecs, impl->n_codecs, sizeof(const struct media_codec *), codec_order_cmp); + + return impl->codecs; +} + +void free_media_codecs(const struct media_codec * const *media_codecs) +{ + struct impl *impl = SPA_CONTAINER_OF(media_codecs, struct impl, codecs); + size_t i; + + for (i = 0; i < impl->n_handles; ++i) + spa_plugin_loader_unload(impl->loader, impl->handles[i]); + + free(impl); +} |