1 /* Spa A2DP codec API
2  *
3  * Copyright © 2020 Wim Taymans
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a
6  * copy of this software and associated documentation files (the "Software"),
7  * to deal in the Software without restriction, including without limitation
8  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9  * and/or sell copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice (including the next
13  * paragraph) shall be included in all copies or substantial portions of the
14  * Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
19  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22  * DEALINGS IN THE SOFTWARE.
23  */
24 
25 #include "config.h"
26 
27 #include <spa/utils/string.h>
28 
29 #include "defs.h"
30 #include "codec-loader.h"
31 
32 #define A2DP_CODEC_LIB_BASE	"bluez5/libspa-codec-bluez5-"
33 
34 /* AVDTP allows 0x3E endpoints, can't have more codecs than that */
35 #define MAX_CODECS	0x3E
36 #define MAX_HANDLES	MAX_CODECS
37 
38 static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.bluez5.codecs");
39 #undef SPA_LOG_TOPIC_DEFAULT
40 #define SPA_LOG_TOPIC_DEFAULT &log_topic
41 
42 struct impl {
43 	const struct a2dp_codec *codecs[MAX_CODECS + 1];
44 	struct spa_handle *handles[MAX_HANDLES];
45 	size_t n_codecs;
46 	size_t n_handles;
47 	struct spa_plugin_loader *loader;
48 	struct spa_log *log;
49 };
50 
codec_order(const struct a2dp_codec * c)51 static int codec_order(const struct a2dp_codec *c)
52 {
53 	static const enum spa_bluetooth_audio_codec order[] = {
54 		SPA_BLUETOOTH_AUDIO_CODEC_LDAC,
55 		SPA_BLUETOOTH_AUDIO_CODEC_APTX_HD,
56 		SPA_BLUETOOTH_AUDIO_CODEC_APTX,
57 		SPA_BLUETOOTH_AUDIO_CODEC_AAC,
58 		SPA_BLUETOOTH_AUDIO_CODEC_MPEG,
59 		SPA_BLUETOOTH_AUDIO_CODEC_SBC,
60 		SPA_BLUETOOTH_AUDIO_CODEC_SBC_XQ,
61 		SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL,
62 		SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL_DUPLEX,
63 		SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM,
64 		SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM_DUPLEX,
65 	};
66 	size_t i;
67 	for (i = 0; i < SPA_N_ELEMENTS(order); ++i)
68 		if (c->id == order[i])
69 			return i;
70 	return SPA_N_ELEMENTS(order);
71 }
72 
codec_order_cmp(const void * a,const void * b)73 static int codec_order_cmp(const void *a, const void *b)
74 {
75 	const struct a2dp_codec * const *ca = a;
76 	const struct a2dp_codec * const *cb = b;
77 	int ia = codec_order(*ca);
78 	int ib = codec_order(*cb);
79 	if (*ca == *cb)
80 		return 0;
81 	return (ia == ib) ? (*ca < *cb ? -1 : 1) : ia - ib;
82 }
83 
load_a2dp_codecs_from(struct impl * impl,const char * factory_name,const char * libname)84 static int load_a2dp_codecs_from(struct impl *impl, const char *factory_name, const char *libname)
85 {
86 	struct spa_handle *handle = NULL;
87 	void *iface;
88 	const struct spa_bluez5_codec_a2dp *bluez5_codec_a2dp;
89 	int n_codecs = 0;
90 	int res;
91 	size_t i;
92 	struct spa_dict_item info_items[] = {
93 		{ SPA_KEY_LIBRARY_NAME, libname },
94 	};
95 	struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items);
96 
97 	handle = spa_plugin_loader_load(impl->loader, factory_name, &info);
98 	if (handle == NULL) {
99 		spa_log_info(impl->log, "Bluetooth codec plugin %s not available", factory_name);
100 		return -ENOENT;
101 	}
102 
103 	spa_log_debug(impl->log, "loading codecs from %s", factory_name);
104 
105 	if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Bluez5CodecA2DP, &iface)) < 0) {
106 		spa_log_info(impl->log, "Bluetooth codec plugin %s has no codec interface",
107 				factory_name);
108 		goto fail;
109 	}
110 
111 	bluez5_codec_a2dp = iface;
112 
113 	if (bluez5_codec_a2dp->iface.version != SPA_VERSION_BLUEZ5_CODEC_A2DP) {
114 		spa_log_info(impl->log, "codec plugin %s has incompatible ABI version (%d != %d)",
115 				factory_name, bluez5_codec_a2dp->iface.version, SPA_VERSION_BLUEZ5_CODEC_A2DP);
116 		res = -ENOENT;
117 		goto fail;
118 	}
119 
120 	for (i = 0; bluez5_codec_a2dp->codecs[i]; ++i) {
121 		const struct a2dp_codec *c = bluez5_codec_a2dp->codecs[i];
122 		size_t j;
123 
124 		if (impl->n_codecs >= MAX_CODECS) {
125 			spa_log_error(impl->log, "too many A2DP codecs");
126 			break;
127 		}
128 
129 		/* Don't load duplicate endpoints */
130 		for (j = 0; j < impl->n_codecs; ++j) {
131 			const struct a2dp_codec *c2 = impl->codecs[j];
132 			const char *ep1 = c->endpoint_name ? c->endpoint_name : c->name;
133 			const char *ep2 = c2->endpoint_name ? c2->endpoint_name : c2->name;
134 			if (spa_streq(ep1, ep2))
135 				goto next_codec;
136 		}
137 
138 		spa_log_debug(impl->log, "loaded A2DP codec %s from %s", c->name, factory_name);
139 
140 		impl->codecs[impl->n_codecs++] = c;
141 		++n_codecs;
142 
143 	next_codec:
144 		continue;
145 	}
146 
147 	if (n_codecs > 0)
148 		impl->handles[impl->n_handles++] = handle;
149 	else
150 		spa_plugin_loader_unload(impl->loader, handle);
151 
152 	return 0;
153 
154 fail:
155 	if (handle)
156 		spa_plugin_loader_unload(impl->loader, handle);
157 	return res;
158 }
159 
load_a2dp_codecs(struct spa_plugin_loader * loader,struct spa_log * log)160 const struct a2dp_codec * const *load_a2dp_codecs(struct spa_plugin_loader *loader, struct spa_log *log)
161 {
162 	struct impl *impl;
163 	bool has_sbc;
164 	size_t i;
165 	const struct { const char *factory; const char *lib; } plugins[] = {
166 #define A2DP_CODEC_FACTORY_LIB(basename) \
167 		{ A2DP_CODEC_FACTORY_NAME(basename), A2DP_CODEC_LIB_BASE basename }
168 		A2DP_CODEC_FACTORY_LIB("aac"),
169 		A2DP_CODEC_FACTORY_LIB("aptx"),
170 		A2DP_CODEC_FACTORY_LIB("faststream"),
171 		A2DP_CODEC_FACTORY_LIB("ldac"),
172 		A2DP_CODEC_FACTORY_LIB("sbc")
173 #undef A2DP_CODEC_FACTORY_LIB
174 	};
175 
176 	impl = calloc(sizeof(struct impl), 1);
177 	if (impl == NULL)
178 		return NULL;
179 
180 	impl->loader = loader;
181 	impl->log = log;
182 
183 	spa_log_topic_init(impl->log, &log_topic);
184 
185 	for (i = 0; i < SPA_N_ELEMENTS(plugins); ++i)
186 		load_a2dp_codecs_from(impl, plugins[i].factory, plugins[i].lib);
187 
188 	has_sbc = false;
189 	for (i = 0; i < impl->n_codecs; ++i)
190 		if (impl->codecs[i]->id == SPA_BLUETOOTH_AUDIO_CODEC_SBC)
191 			has_sbc = true;
192 
193 	if (!has_sbc) {
194 		spa_log_error(impl->log, "failed to load A2DP SBC codec from plugins");
195 		free_a2dp_codecs(impl->codecs);
196 		errno = ENOENT;
197 		return NULL;
198 	}
199 
200 	qsort(impl->codecs, impl->n_codecs, sizeof(const struct a2dp_codec *), codec_order_cmp);
201 
202 	return impl->codecs;
203 }
204 
free_a2dp_codecs(const struct a2dp_codec * const * a2dp_codecs)205 void free_a2dp_codecs(const struct a2dp_codec * const *a2dp_codecs)
206 {
207 	struct impl *impl = SPA_CONTAINER_OF(a2dp_codecs, struct impl, codecs);
208 	size_t i;
209 
210 	for (i = 0; i < impl->n_handles; ++i)
211 		spa_plugin_loader_unload(impl->loader, impl->handles[i]);
212 
213 	free(impl);
214 }
215