1 /* Spa A2DP FastStream codec
2  *
3  * Copyright © 2020 Wim Taymans
4  * Copyright © 2021 Pauli Virtanen
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a
7  * copy of this software and associated documentation files (the "Software"),
8  * to deal in the Software without restriction, including without limitation
9  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10  * and/or sell copies of the Software, and to permit persons to whom the
11  * Software is furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice (including the next
14  * paragraph) shall be included in all copies or substantial portions of the
15  * Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
20  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23  * DEALINGS IN THE SOFTWARE.
24  */
25 
26 #include <unistd.h>
27 #include <stddef.h>
28 #include <errno.h>
29 #include <arpa/inet.h>
30 #if __BYTE_ORDER != __LITTLE_ENDIAN
31 #include <byteswap.h>
32 #endif
33 
34 #include <spa/param/audio/format.h>
35 #include <spa/param/audio/format-utils.h>
36 
37 #include <sbc/sbc.h>
38 
39 #include "a2dp-codecs.h"
40 
41 struct impl {
42 	sbc_t sbc;
43 
44 	size_t mtu;
45 	int codesize;
46 	int frame_count;
47 	int max_frames;
48 };
49 
50 struct duplex_impl {
51 	sbc_t sbc;
52 };
53 
codec_fill_caps(const struct a2dp_codec * codec,uint32_t flags,uint8_t caps[A2DP_MAX_CAPS_SIZE])54 static int codec_fill_caps(const struct a2dp_codec *codec, uint32_t flags,
55 		uint8_t caps[A2DP_MAX_CAPS_SIZE])
56 {
57 	const a2dp_faststream_t a2dp_faststream = {
58 		.info = codec->vendor,
59 		.direction = FASTSTREAM_DIRECTION_SINK |
60 			(codec->duplex_codec ? FASTSTREAM_DIRECTION_SOURCE : 0),
61 		.sink_frequency =
62 			FASTSTREAM_SINK_SAMPLING_FREQ_44100 |
63 			FASTSTREAM_SINK_SAMPLING_FREQ_48000,
64 		.source_frequency =
65 			FASTSTREAM_SOURCE_SAMPLING_FREQ_16000,
66 	};
67 
68 	memcpy(caps, &a2dp_faststream, sizeof(a2dp_faststream));
69 	return sizeof(a2dp_faststream);
70 }
71 
72 static const struct a2dp_codec_config
73 frequencies[] = {
74 	{ FASTSTREAM_SINK_SAMPLING_FREQ_48000, 48000, 1 },
75 	{ FASTSTREAM_SINK_SAMPLING_FREQ_44100, 44100, 0 },
76 };
77 
78 static const struct a2dp_codec_config
79 duplex_frequencies[] = {
80 	{ FASTSTREAM_SOURCE_SAMPLING_FREQ_16000, 16000, 0 },
81 };
82 
codec_select_config(const struct a2dp_codec * codec,uint32_t flags,const void * caps,size_t caps_size,const struct a2dp_codec_audio_info * info,const struct spa_dict * settings,uint8_t config[A2DP_MAX_CAPS_SIZE])83 static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags,
84 		const void *caps, size_t caps_size,
85 		const struct a2dp_codec_audio_info *info,
86 		const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE])
87 {
88 	a2dp_faststream_t conf;
89 	int i;
90 
91 	if (caps_size < sizeof(conf))
92 		return -EINVAL;
93 
94 	memcpy(&conf, caps, sizeof(conf));
95 
96 	if (codec->vendor.vendor_id != conf.info.vendor_id ||
97 	    codec->vendor.codec_id != conf.info.codec_id)
98 		return -ENOTSUP;
99 
100 	if (codec->duplex_codec && !(conf.direction & FASTSTREAM_DIRECTION_SOURCE))
101 		return -ENOTSUP;
102 
103 	if (!(conf.direction & FASTSTREAM_DIRECTION_SINK))
104 		return -ENOTSUP;
105 
106 	conf.direction = FASTSTREAM_DIRECTION_SINK;
107 
108 	if (codec->duplex_codec)
109 		conf.direction |= FASTSTREAM_DIRECTION_SOURCE;
110 
111 	if ((i = a2dp_codec_select_config(frequencies,
112 			SPA_N_ELEMENTS(frequencies),
113 			conf.sink_frequency,
114 			info ? info->rate : A2DP_CODEC_DEFAULT_RATE
115 			)) < 0)
116 		return -ENOTSUP;
117 	conf.sink_frequency = frequencies[i].config;
118 
119 	if ((i = a2dp_codec_select_config(duplex_frequencies,
120 			SPA_N_ELEMENTS(duplex_frequencies),
121 			conf.source_frequency,
122 			16000
123 			)) < 0)
124 		return -ENOTSUP;
125 	conf.source_frequency = duplex_frequencies[i].config;
126 
127 	memcpy(config, &conf, sizeof(conf));
128 
129 	return sizeof(conf);
130 }
131 
codec_enum_config(const struct a2dp_codec * codec,const void * caps,size_t caps_size,uint32_t id,uint32_t idx,struct spa_pod_builder * b,struct spa_pod ** param)132 static int codec_enum_config(const struct a2dp_codec *codec,
133 		const void *caps, size_t caps_size, uint32_t id, uint32_t idx,
134 		struct spa_pod_builder *b, struct spa_pod **param)
135 {
136 	a2dp_faststream_t conf;
137         struct spa_pod_frame f[2];
138 	struct spa_pod_choice *choice;
139 	uint32_t position[SPA_AUDIO_MAX_CHANNELS];
140 	uint32_t i = 0;
141 
142 	if (caps_size < sizeof(conf))
143 		return -EINVAL;
144 
145 	memcpy(&conf, caps, sizeof(conf));
146 
147 	if (idx > 0)
148 		return 0;
149 
150 	spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id);
151 	spa_pod_builder_add(b,
152 			SPA_FORMAT_mediaType,      SPA_POD_Id(SPA_MEDIA_TYPE_audio),
153 			SPA_FORMAT_mediaSubtype,   SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
154 			SPA_FORMAT_AUDIO_format,   SPA_POD_Id(SPA_AUDIO_FORMAT_S16),
155 			0);
156 	spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_rate, 0);
157 
158 	spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_None, 0);
159 	choice = (struct spa_pod_choice*)spa_pod_builder_frame(b, &f[1]);
160 	i = 0;
161 	if (conf.sink_frequency & FASTSTREAM_SINK_SAMPLING_FREQ_48000) {
162 		if (i++ == 0)
163 			spa_pod_builder_int(b, 48000);
164 		spa_pod_builder_int(b, 48000);
165 	}
166 	if (conf.sink_frequency & FASTSTREAM_SINK_SAMPLING_FREQ_44100) {
167 		if (i++ == 0)
168 			spa_pod_builder_int(b, 44100);
169 		spa_pod_builder_int(b, 44100);
170 	}
171 	if (i == 0)
172 		return -EINVAL;
173 	if (i > 1)
174 		choice->body.type = SPA_CHOICE_Enum;
175 	spa_pod_builder_pop(b, &f[1]);
176 
177 	position[0] = SPA_AUDIO_CHANNEL_FL;
178 	position[1] = SPA_AUDIO_CHANNEL_FR;
179 	spa_pod_builder_add(b,
180 			SPA_FORMAT_AUDIO_channels, SPA_POD_Int(2),
181 			SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t),
182 					SPA_TYPE_Id, 2, position),
183 			0);
184 
185 	*param = spa_pod_builder_pop(b, &f[0]);
186 	return *param == NULL ? -EIO : 1;
187 }
188 
codec_reduce_bitpool(void * data)189 static int codec_reduce_bitpool(void *data)
190 {
191 	return -ENOTSUP;
192 }
193 
codec_increase_bitpool(void * data)194 static int codec_increase_bitpool(void *data)
195 {
196 	return -ENOTSUP;
197 }
198 
codec_get_block_size(void * data)199 static int codec_get_block_size(void *data)
200 {
201 	struct impl *this = data;
202 	return this->codesize;
203 }
204 
ceil2(size_t v)205 static size_t ceil2(size_t v)
206 {
207 	if (v % 2 != 0 && v < SIZE_MAX)
208 		v += 1;
209 	return v;
210 }
211 
codec_init(const struct a2dp_codec * codec,uint32_t flags,void * config,size_t config_len,const struct spa_audio_info * info,void * props,size_t mtu)212 static void *codec_init(const struct a2dp_codec *codec, uint32_t flags,
213 		void *config, size_t config_len, const struct spa_audio_info *info,
214 		void *props, size_t mtu)
215 {
216 	a2dp_faststream_t *conf = config;
217 	struct impl *this;
218 	bool sbc_initialized = false;
219 	int res;
220 
221 	if ((this = calloc(1, sizeof(struct impl))) == NULL)
222 		goto error_errno;
223 
224 	if ((res = sbc_init(&this->sbc, 0)) < 0)
225 		goto error;
226 
227 	sbc_initialized = true;
228 	this->sbc.endian = SBC_LE;
229 	this->mtu = mtu;
230 
231 	if (info->media_type != SPA_MEDIA_TYPE_audio ||
232 	    info->media_subtype != SPA_MEDIA_SUBTYPE_raw ||
233 	    info->info.raw.format != SPA_AUDIO_FORMAT_S16) {
234 		res = -EINVAL;
235 		goto error;
236 	}
237 
238 	switch (conf->sink_frequency) {
239 	case FASTSTREAM_SINK_SAMPLING_FREQ_44100:
240 		this->sbc.frequency = SBC_FREQ_44100;
241 		break;
242 	case FASTSTREAM_SINK_SAMPLING_FREQ_48000:
243 		this->sbc.frequency = SBC_FREQ_48000;
244 		break;
245 	default:
246 		res = -EINVAL;
247                 goto error;
248         }
249 
250 	this->sbc.mode = SBC_MODE_JOINT_STEREO;
251 	this->sbc.subbands = SBC_SB_8;
252 	this->sbc.allocation = SBC_AM_LOUDNESS;
253 	this->sbc.blocks = SBC_BLK_16;
254 	this->sbc.bitpool = 29;
255 
256 	this->codesize = sbc_get_codesize(&this->sbc);
257 
258 	this->max_frames = 3;
259 	if (this->mtu < this->max_frames * ceil2(sbc_get_frame_length(&this->sbc))) {
260 		res = -EINVAL;
261 		goto error;
262 	}
263 
264 	return this;
265 
266 error_errno:
267 	res = -errno;
268 	goto error;
269 
270 error:
271 	if (sbc_initialized)
272 		sbc_finish(&this->sbc);
273 	free(this);
274 	errno = -res;
275 	return NULL;
276 }
277 
codec_deinit(void * data)278 static void codec_deinit(void *data)
279 {
280 	struct impl *this = data;
281 	sbc_finish(&this->sbc);
282 	free(this);
283 }
284 
codec_abr_process(void * data,size_t unsent)285 static int codec_abr_process (void *data, size_t unsent)
286 {
287 	return -ENOTSUP;
288 }
289 
codec_start_encode(void * data,void * dst,size_t dst_size,uint16_t seqnum,uint32_t timestamp)290 static int codec_start_encode (void *data,
291 		void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp)
292 {
293 	struct impl *this = data;
294 	this->frame_count = 0;
295 	return 0;
296 }
297 
codec_encode(void * data,const void * src,size_t src_size,void * dst,size_t dst_size,size_t * dst_out,int * need_flush)298 static int codec_encode(void *data,
299 		const void *src, size_t src_size,
300 		void *dst, size_t dst_size,
301 		size_t *dst_out, int *need_flush)
302 {
303 	struct impl *this = data;
304 	int res;
305 
306 	res = sbc_encode(&this->sbc, src, src_size,
307 			dst, dst_size, (ssize_t*)dst_out);
308 	if (SPA_UNLIKELY(res < 0))
309 		return -EINVAL;
310 	spa_assert(res == this->codesize);
311 
312 	if (*dst_out % 2 != 0 && *dst_out < dst_size) {
313 		/* Pad similarly as in input stream */
314 		*((uint8_t *)dst + *dst_out) = 0;
315 		++*dst_out;
316 	}
317 
318 	this->frame_count += res / this->codesize;
319 	*need_flush = this->frame_count >= this->max_frames;
320 	return res;
321 }
322 
codec_start_decode(void * data,const void * src,size_t src_size,uint16_t * seqnum,uint32_t * timestamp)323 static SPA_UNUSED int codec_start_decode (void *data,
324 		const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp)
325 {
326 	return 0;
327 }
328 
do_decode(sbc_t * sbc,const void * src,size_t src_size,void * dst,size_t dst_size,size_t * dst_out)329 static int do_decode(sbc_t *sbc,
330 		const void *src, size_t src_size,
331 		void *dst, size_t dst_size,
332 		size_t *dst_out)
333 {
334 	size_t processed = 0;
335 	int res;
336 
337 	*dst_out = 0;
338 
339 	/* Scan for SBC syncword.
340 	 * We could probably assume 1-byte paddings instead,
341 	 * which devices seem to be sending.
342 	 */
343 	while (src_size >= 1) {
344 		if (*(uint8_t*)src == 0x9C)
345 			break;
346 		src = (uint8_t*)src + 1;
347 		--src_size;
348 		++processed;
349 	}
350 
351 	res = sbc_decode(sbc, src, src_size,
352 			dst, dst_size, dst_out);
353 	if (res <= 0)
354 		res = SPA_MIN((size_t)1, src_size);    /* skip bad payload */
355 
356 	processed += res;
357 	return processed;
358 }
359 
codec_decode(void * data,const void * src,size_t src_size,void * dst,size_t dst_size,size_t * dst_out)360 static SPA_UNUSED int codec_decode(void *data,
361 		const void *src, size_t src_size,
362 		void *dst, size_t dst_size,
363 		size_t *dst_out)
364 {
365 	struct impl *this = data;
366 	return do_decode(&this->sbc, src, src_size, dst, dst_size, dst_out);
367 }
368 
369 /*
370  * Duplex codec
371  *
372  * When connected as SRC to SNK, FastStream sink may send back SBC data.
373  */
374 
duplex_enum_config(const struct a2dp_codec * codec,const void * caps,size_t caps_size,uint32_t id,uint32_t idx,struct spa_pod_builder * b,struct spa_pod ** param)375 static int duplex_enum_config(const struct a2dp_codec *codec,
376 		const void *caps, size_t caps_size, uint32_t id, uint32_t idx,
377 		struct spa_pod_builder *b, struct spa_pod **param)
378 {
379 	a2dp_faststream_t conf;
380 	struct spa_audio_info_raw info = { 0, };
381 
382 	if (caps_size < sizeof(conf))
383 		return -EINVAL;
384 
385 	memcpy(&conf, caps, sizeof(conf));
386 
387 	if (idx > 0)
388 		return 0;
389 
390 	switch (conf.source_frequency) {
391 	case FASTSTREAM_SOURCE_SAMPLING_FREQ_16000:
392 		info.rate = 16000;
393 		break;
394 	default:
395 		return -EINVAL;
396         }
397 
398 	/*
399 	 * Some headsets send mono stream, others stereo.  This information
400 	 * is contained in the SBC headers, and becomes known only when
401 	 * stream arrives. To be able to work in both cases, we will
402 	 * produce 2-channel output, and will double the channels
403 	 * in the decoding step if mono stream was received.
404 	 */
405 	info.format = SPA_AUDIO_FORMAT_S16_LE;
406 	info.channels = 2;
407 	info.position[0] = SPA_AUDIO_CHANNEL_FL;
408 	info.position[1] = SPA_AUDIO_CHANNEL_FR;
409 
410 	*param = spa_format_audio_raw_build(b, id, &info);
411 	return *param == NULL ? -EIO : 1;
412 }
413 
duplex_validate_config(const struct a2dp_codec * codec,uint32_t flags,const void * caps,size_t caps_size,struct spa_audio_info * info)414 static int duplex_validate_config(const struct a2dp_codec *codec, uint32_t flags,
415 			const void *caps, size_t caps_size,
416 			struct spa_audio_info *info)
417 {
418 	spa_zero(*info);
419 	info->media_type = SPA_MEDIA_TYPE_audio;
420 	info->media_subtype = SPA_MEDIA_SUBTYPE_raw;
421 	info->info.raw.format = SPA_AUDIO_FORMAT_S16_LE;
422 	info->info.raw.channels = 2;
423 	info->info.raw.position[0] = SPA_AUDIO_CHANNEL_FL;
424 	info->info.raw.position[1] = SPA_AUDIO_CHANNEL_FR;
425 	info->info.raw.rate = 16000;
426 	return 0;
427 }
428 
duplex_reduce_bitpool(void * data)429 static int duplex_reduce_bitpool(void *data)
430 {
431 	return -ENOTSUP;
432 }
433 
duplex_increase_bitpool(void * data)434 static int duplex_increase_bitpool(void *data)
435 {
436 	return -ENOTSUP;
437 }
438 
duplex_get_block_size(void * data)439 static int duplex_get_block_size(void *data)
440 {
441 	return 0;
442 }
443 
duplex_init(const struct a2dp_codec * codec,uint32_t flags,void * config,size_t config_len,const struct spa_audio_info * info,void * props,size_t mtu)444 static void *duplex_init(const struct a2dp_codec *codec, uint32_t flags,
445 		void *config, size_t config_len, const struct spa_audio_info *info,
446 		void *props, size_t mtu)
447 {
448 	a2dp_faststream_t *conf = config;
449 	struct duplex_impl *this = NULL;
450 	int res;
451 
452 	if (info->media_type != SPA_MEDIA_TYPE_audio ||
453 	    info->media_subtype != SPA_MEDIA_SUBTYPE_raw ||
454 	    info->info.raw.format != SPA_AUDIO_FORMAT_S16_LE) {
455 		res = -EINVAL;
456 		goto error;
457 	}
458 
459 	if ((this = calloc(1, sizeof(struct duplex_impl))) == NULL)
460 		goto error_errno;
461 
462 	if ((res = sbc_init(&this->sbc, 0)) < 0)
463 		goto error;
464 
465 	switch (conf->source_frequency) {
466 	case FASTSTREAM_SOURCE_SAMPLING_FREQ_16000:
467 		this->sbc.frequency = SBC_FREQ_16000;
468 		break;
469 	default:
470 		res = -EINVAL;
471                 goto error;
472         }
473 
474 	this->sbc.endian = SBC_LE;
475 	this->sbc.mode = SBC_MODE_MONO;
476 	this->sbc.subbands = SBC_SB_8;
477 	this->sbc.allocation = SBC_AM_LOUDNESS;
478 	this->sbc.blocks = SBC_BLK_16;
479 	this->sbc.bitpool = 32;
480 
481 	return this;
482 
483 error_errno:
484 	res = -errno;
485 	goto error;
486 error:
487 	free(this);
488 	errno = -res;
489 	return NULL;
490 }
491 
duplex_deinit(void * data)492 static void duplex_deinit(void *data)
493 {
494 	struct duplex_impl *this = data;
495 	sbc_finish(&this->sbc);
496 	free(this);
497 }
498 
duplex_abr_process(void * data,size_t unsent)499 static int duplex_abr_process (void *data, size_t unsent)
500 {
501 	return -ENOTSUP;
502 }
503 
duplex_start_encode(void * data,void * dst,size_t dst_size,uint16_t seqnum,uint32_t timestamp)504 static int duplex_start_encode (void *data,
505 		void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp)
506 {
507 	return -ENOTSUP;
508 }
509 
duplex_encode(void * data,const void * src,size_t src_size,void * dst,size_t dst_size,size_t * dst_out,int * need_flush)510 static int duplex_encode(void *data,
511 		const void *src, size_t src_size,
512 		void *dst, size_t dst_size,
513 		size_t *dst_out, int *need_flush)
514 {
515 	return -ENOTSUP;
516 }
517 
duplex_start_decode(void * data,const void * src,size_t src_size,uint16_t * seqnum,uint32_t * timestamp)518 static int duplex_start_decode (void *data,
519 		const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp)
520 {
521 	return 0;
522 }
523 
524 /** Convert S16LE stereo -> S16LE mono, in-place (only for testing purposes) */
convert_s16le_c2_to_c1(int16_t * data,size_t size,size_t max_size)525 static SPA_UNUSED size_t convert_s16le_c2_to_c1(int16_t *data, size_t size, size_t max_size)
526 {
527 	size_t i;
528 	for (i = 0; i < size / 2; ++i)
529 #if __BYTE_ORDER == __LITTLE_ENDIAN
530 		data[i] = data[2*i]/2 + data[2*i+1]/2;
531 #else
532 		data[i] = bswap_16(bswap_16(data[2*i])/2 + bswap_16(data[2*i+1])/2);
533 #endif
534 	return size / 2;
535 }
536 
537 /** Convert S16LE mono -> S16LE stereo, in-place */
convert_s16le_c1_to_c2(uint8_t * data,size_t size,size_t max_size)538 static size_t convert_s16le_c1_to_c2(uint8_t *data, size_t size, size_t max_size)
539 {
540 	size_t pos;
541 
542 	pos = 2 * SPA_MIN(size / 2, max_size / 4);
543 	size = 2 * pos;
544 
545 	/* We'll trust the compiler to optimize this */
546 	while (pos >= 2) {
547 		pos -= 2;
548 		data[2*pos+3] = data[pos+1];
549 		data[2*pos+2] = data[pos];
550 		data[2*pos+1] = data[pos+1];
551 		data[2*pos] = data[pos];
552 	}
553 
554 	return size;
555 }
556 
duplex_decode(void * data,const void * src,size_t src_size,void * dst,size_t dst_size,size_t * dst_out)557 static int duplex_decode(void *data,
558 		const void *src, size_t src_size,
559 		void *dst, size_t dst_size,
560 		size_t *dst_out)
561 {
562 	struct duplex_impl *this = data;
563 	int res;
564 
565 	*dst_out = 0;
566 	res = do_decode(&this->sbc, src, src_size, dst, dst_size, dst_out);
567 
568 	/*
569 	 * Depending on headers of first frame, libsbc may output either
570 	 * 1 or 2 channels. This function should always produce 2 channels,
571 	 * so we'll just double the channels here.
572 	 */
573 	if (this->sbc.mode == SBC_MODE_MONO)
574 		*dst_out = convert_s16le_c1_to_c2(dst, *dst_out, dst_size);
575 
576 	return res;
577 }
578 
579 /* Voice channel SBC, not a real A2DP codec */
580 static const struct a2dp_codec duplex_codec = {
581 	.codec_id = A2DP_CODEC_VENDOR,
582 	.name = "faststream_sbc",
583 	.description = "FastStream duplex SBC",
584 	.fill_caps = codec_fill_caps,
585 	.select_config = codec_select_config,
586 	.enum_config = duplex_enum_config,
587 	.validate_config = duplex_validate_config,
588 	.init = duplex_init,
589 	.deinit = duplex_deinit,
590 	.get_block_size = duplex_get_block_size,
591 	.abr_process = duplex_abr_process,
592 	.start_encode = duplex_start_encode,
593 	.encode = duplex_encode,
594 	.start_decode = duplex_start_decode,
595 	.decode = duplex_decode,
596 	.reduce_bitpool = duplex_reduce_bitpool,
597 	.increase_bitpool = duplex_increase_bitpool,
598 };
599 
600 #define FASTSTREAM_COMMON_DEFS				\
601 	.codec_id = A2DP_CODEC_VENDOR,			\
602 	.vendor = { .vendor_id = FASTSTREAM_VENDOR_ID,	\
603 		.codec_id = FASTSTREAM_CODEC_ID },	\
604 	.description = "FastStream",			\
605 	.fill_caps = codec_fill_caps,			\
606 	.select_config = codec_select_config,		\
607 	.enum_config = codec_enum_config,		\
608 	.init = codec_init,				\
609 	.deinit = codec_deinit,				\
610 	.get_block_size = codec_get_block_size,		\
611 	.abr_process = codec_abr_process,		\
612 	.start_encode = codec_start_encode,		\
613 	.encode = codec_encode,				\
614 	.reduce_bitpool = codec_reduce_bitpool,		\
615 	.increase_bitpool = codec_increase_bitpool
616 
617 const struct a2dp_codec a2dp_codec_faststream = {
618 	FASTSTREAM_COMMON_DEFS,
619 	.id = SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM,
620 	.name = "faststream",
621 };
622 
623 const struct a2dp_codec a2dp_codec_faststream_duplex = {
624 	FASTSTREAM_COMMON_DEFS,
625 	.id = SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM_DUPLEX,
626 	.name = "faststream_duplex",
627 	.duplex_codec = &duplex_codec,
628 };
629 
630 A2DP_CODEC_EXPORT_DEF(
631 	"faststream",
632 	&a2dp_codec_faststream,
633 	&a2dp_codec_faststream_duplex
634 );
635