summaryrefslogtreecommitdiffstats
path: root/spa/plugins/bluez5/a2dp-codec-aptx.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:28:17 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:28:17 +0000
commit7a46c07230b8d8108c0e8e80df4522d0ac116538 (patch)
treed483300dab478b994fe199a5d19d18d74153718a /spa/plugins/bluez5/a2dp-codec-aptx.c
parentInitial commit. (diff)
downloadpipewire-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/a2dp-codec-aptx.c748
1 files changed, 748 insertions, 0 deletions
diff --git a/spa/plugins/bluez5/a2dp-codec-aptx.c b/spa/plugins/bluez5/a2dp-codec-aptx.c
new file mode 100644
index 0000000..6938e47
--- /dev/null
+++ b/spa/plugins/bluez5/a2dp-codec-aptx.c
@@ -0,0 +1,748 @@
+/* Spa A2DP aptX codec
+ *
+ * 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 <unistd.h>
+#include <stddef.h>
+#include <errno.h>
+#include <arpa/inet.h>
+
+#include <spa/param/audio/format.h>
+#include <spa/param/audio/format-utils.h>
+
+#include <sbc/sbc.h>
+
+#include <freeaptx.h>
+
+#include "rtp.h"
+#include "media-codecs.h"
+
+#define APTX_LL_LEVEL1(level) (((level) >> 8) & 0xFF)
+#define APTX_LL_LEVEL2(level) (((level) >> 0) & 0xFF)
+#define APTX_LL_LEVEL(level1, level2) ((((level1) & 0xFF) << 8) | (((level2) & 0xFF) << 0))
+
+#define MSBC_DECODED_SIZE 240
+#define MSBC_ENCODED_SIZE 60
+#define MSBC_PAYLOAD_SIZE 57
+
+/*
+ * XXX: Bump requested device buffer levels up by 50% from defaults,
+ * XXX: increasing latency similarly. This seems to be necessary for
+ * XXX: stable output when moving headphones. It might be possible to
+ * XXX: reduce this by changing the scheduling of the socket writes.
+ */
+#define LL_LEVEL_ADJUSTMENT 3/2
+
+struct impl {
+ struct aptx_context *aptx;
+
+ struct rtp_header *header;
+
+ size_t mtu;
+ int codesize;
+ int frame_length;
+ int frame_count;
+ int max_frames;
+
+ bool hd;
+};
+
+struct msbc_impl {
+ sbc_t msbc;
+};
+
+static inline bool codec_is_hd(const struct media_codec *codec)
+{
+ return codec->vendor.codec_id == APTX_HD_CODEC_ID
+ && codec->vendor.vendor_id == APTX_HD_VENDOR_ID;
+}
+
+static inline bool codec_is_ll(const struct media_codec *codec)
+{
+ return (codec->id == SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL) ||
+ (codec->id == SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL_DUPLEX);
+}
+
+static inline size_t codec_get_caps_size(const struct media_codec *codec)
+{
+ if (codec_is_hd(codec))
+ return sizeof(a2dp_aptx_hd_t);
+ else if (codec_is_ll(codec))
+ return sizeof(a2dp_aptx_ll_t);
+ else
+ return sizeof(a2dp_aptx_t);
+}
+
+static int codec_fill_caps(const struct media_codec *codec, uint32_t flags,
+ uint8_t caps[A2DP_MAX_CAPS_SIZE])
+{
+ size_t actual_conf_size = codec_get_caps_size(codec);
+ const a2dp_aptx_t a2dp_aptx = {
+ .info = codec->vendor,
+ .frequency =
+ APTX_SAMPLING_FREQ_16000 |
+ APTX_SAMPLING_FREQ_32000 |
+ APTX_SAMPLING_FREQ_44100 |
+ APTX_SAMPLING_FREQ_48000,
+ .channel_mode =
+ APTX_CHANNEL_MODE_STEREO,
+ };
+ const a2dp_aptx_ll_t a2dp_aptx_ll = {
+ .aptx = a2dp_aptx,
+ .bidirect_link = codec->duplex_codec ? true : false,
+ .has_new_caps = false,
+ };
+ if (codec_is_ll(codec))
+ memcpy(caps, &a2dp_aptx_ll, sizeof(a2dp_aptx_ll));
+ else
+ memcpy(caps, &a2dp_aptx, sizeof(a2dp_aptx));
+ return actual_conf_size;
+}
+
+static const struct media_codec_config
+aptx_frequencies[] = {
+ { APTX_SAMPLING_FREQ_48000, 48000, 3 },
+ { APTX_SAMPLING_FREQ_44100, 44100, 2 },
+ { APTX_SAMPLING_FREQ_32000, 32000, 1 },
+ { APTX_SAMPLING_FREQ_16000, 16000, 0 },
+};
+
+static int codec_select_config(const struct media_codec *codec, uint32_t flags,
+ const void *caps, size_t caps_size,
+ const struct media_codec_audio_info *info,
+ const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE])
+{
+ a2dp_aptx_t conf;
+ int i;
+ size_t actual_conf_size = codec_get_caps_size(codec);
+
+ if (caps_size < sizeof(conf) || actual_conf_size < sizeof(conf))
+ return -EINVAL;
+
+ memcpy(&conf, caps, sizeof(conf));
+
+ if (codec->vendor.vendor_id != conf.info.vendor_id ||
+ codec->vendor.codec_id != conf.info.codec_id)
+ return -ENOTSUP;
+
+ if ((i = media_codec_select_config(aptx_frequencies,
+ SPA_N_ELEMENTS(aptx_frequencies),
+ conf.frequency,
+ info ? info->rate : A2DP_CODEC_DEFAULT_RATE
+ )) < 0)
+ return -ENOTSUP;
+ conf.frequency = aptx_frequencies[i].config;
+
+ if (conf.channel_mode & APTX_CHANNEL_MODE_STEREO)
+ conf.channel_mode = APTX_CHANNEL_MODE_STEREO;
+ else
+ return -ENOTSUP;
+
+ memcpy(config, &conf, sizeof(conf));
+
+ return actual_conf_size;
+}
+
+static int codec_select_config_ll(const struct media_codec *codec, uint32_t flags,
+ const void *caps, size_t caps_size,
+ const struct media_codec_audio_info *info,
+ const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE])
+{
+ a2dp_aptx_ll_ext_t conf = { 0 };
+ size_t actual_conf_size;
+ int res;
+
+ /* caps may contain only conf.base, or also the extended attributes */
+
+ if (caps_size < sizeof(conf.base))
+ return -EINVAL;
+
+ memcpy(&conf, caps, SPA_MIN(caps_size, sizeof(conf)));
+
+ actual_conf_size = conf.base.has_new_caps ? sizeof(conf) : sizeof(conf.base);
+ if (caps_size < actual_conf_size)
+ return -EINVAL;
+
+ if (codec->duplex_codec && !conf.base.bidirect_link)
+ return -ENOTSUP;
+
+ if ((res = codec_select_config(codec, flags, caps, caps_size, info, settings, config)) < 0)
+ return res;
+
+ memcpy(&conf.base.aptx, config, sizeof(conf.base.aptx));
+
+ if (conf.base.has_new_caps) {
+ int target_level = APTX_LL_LEVEL(conf.target_level1, conf.target_level2);
+ int initial_level = APTX_LL_LEVEL(conf.initial_level1, conf.initial_level2);
+ int good_working_level = APTX_LL_LEVEL(conf.good_working_level1, conf.good_working_level2);
+
+ target_level = SPA_MAX(target_level, APTX_LL_TARGET_CODEC_LEVEL * LL_LEVEL_ADJUSTMENT);
+ initial_level = SPA_MAX(initial_level, APTX_LL_INITIAL_CODEC_LEVEL * LL_LEVEL_ADJUSTMENT);
+ good_working_level = SPA_MAX(good_working_level, APTX_LL_GOOD_WORKING_LEVEL * LL_LEVEL_ADJUSTMENT);
+
+ conf.target_level1 = APTX_LL_LEVEL1(target_level);
+ conf.target_level2 = APTX_LL_LEVEL2(target_level);
+ conf.initial_level1 = APTX_LL_LEVEL1(initial_level);
+ conf.initial_level2 = APTX_LL_LEVEL2(initial_level);
+ conf.good_working_level1 = APTX_LL_LEVEL1(good_working_level);
+ conf.good_working_level2 = APTX_LL_LEVEL2(good_working_level);
+
+ if (conf.sra_max_rate == 0)
+ conf.sra_max_rate = APTX_LL_SRA_MAX_RATE;
+ if (conf.sra_avg_time == 0)
+ conf.sra_avg_time = APTX_LL_SRA_AVG_TIME;
+ }
+
+ memcpy(config, &conf, actual_conf_size);
+
+ return actual_conf_size;
+}
+
+static int codec_enum_config(const struct media_codec *codec, uint32_t flags,
+ const void *caps, size_t caps_size, uint32_t id, uint32_t idx,
+ struct spa_pod_builder *b, struct spa_pod **param)
+{
+ a2dp_aptx_t conf;
+ struct spa_pod_frame f[2];
+ struct spa_pod_choice *choice;
+ uint32_t position[SPA_AUDIO_MAX_CHANNELS];
+ uint32_t i = 0;
+
+ if (caps_size < sizeof(conf))
+ return -EINVAL;
+
+ memcpy(&conf, caps, sizeof(conf));
+
+ if (idx > 0)
+ return 0;
+
+ spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id);
+ spa_pod_builder_add(b,
+ SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio),
+ SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
+ SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_S24),
+ 0);
+ spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_rate, 0);
+
+ spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_None, 0);
+ choice = (struct spa_pod_choice*)spa_pod_builder_frame(b, &f[1]);
+ i = 0;
+ if (conf.frequency & APTX_SAMPLING_FREQ_48000) {
+ if (i++ == 0)
+ spa_pod_builder_int(b, 48000);
+ spa_pod_builder_int(b, 48000);
+ }
+ if (conf.frequency & APTX_SAMPLING_FREQ_44100) {
+ if (i++ == 0)
+ spa_pod_builder_int(b, 44100);
+ spa_pod_builder_int(b, 44100);
+ }
+ if (conf.frequency & APTX_SAMPLING_FREQ_32000) {
+ if (i++ == 0)
+ spa_pod_builder_int(b, 32000);
+ spa_pod_builder_int(b, 32000);
+ }
+ if (conf.frequency & APTX_SAMPLING_FREQ_16000) {
+ if (i++ == 0)
+ spa_pod_builder_int(b, 16000);
+ spa_pod_builder_int(b, 16000);
+ }
+ if (i == 0)
+ return -EINVAL;
+ if (i > 1)
+ choice->body.type = SPA_CHOICE_Enum;
+ spa_pod_builder_pop(b, &f[1]);
+
+ if (SPA_FLAG_IS_SET(conf.channel_mode, APTX_CHANNEL_MODE_MONO | APTX_CHANNEL_MODE_STEREO)) {
+ spa_pod_builder_add(b,
+ SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int(2, 1, 2),
+ 0);
+ } else if (conf.channel_mode & APTX_CHANNEL_MODE_MONO) {
+ position[0] = SPA_AUDIO_CHANNEL_MONO;
+ spa_pod_builder_add(b,
+ SPA_FORMAT_AUDIO_channels, SPA_POD_Int(1),
+ SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t),
+ SPA_TYPE_Id, 1, position),
+ 0);
+ } else if (conf.channel_mode & APTX_CHANNEL_MODE_STEREO) {
+ position[0] = SPA_AUDIO_CHANNEL_FL;
+ position[1] = SPA_AUDIO_CHANNEL_FR;
+ spa_pod_builder_add(b,
+ SPA_FORMAT_AUDIO_channels, SPA_POD_Int(2),
+ SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t),
+ SPA_TYPE_Id, 2, position),
+ 0);
+ } else
+ return -EINVAL;
+
+ *param = spa_pod_builder_pop(b, &f[0]);
+ return *param == NULL ? -EIO : 1;
+}
+
+static int codec_reduce_bitpool(void *data)
+{
+ return -ENOTSUP;
+}
+
+static int codec_increase_bitpool(void *data)
+{
+ return -ENOTSUP;
+}
+
+static int codec_get_block_size(void *data)
+{
+ struct impl *this = data;
+ return this->codesize;
+}
+
+static void *codec_init(const struct media_codec *codec, uint32_t flags,
+ void *config, size_t config_len, const struct spa_audio_info *info,
+ void *props, size_t mtu)
+{
+ struct impl *this;
+ int res;
+
+ if ((this = calloc(1, sizeof(struct impl))) == NULL)
+ goto error_errno;
+
+ this->hd = codec_is_hd(codec);
+
+ if ((this->aptx = aptx_init(this->hd)) == NULL)
+ goto error_errno;
+
+ this->mtu = mtu;
+
+ if (info->media_type != SPA_MEDIA_TYPE_audio ||
+ info->media_subtype != SPA_MEDIA_SUBTYPE_raw ||
+ info->info.raw.format != SPA_AUDIO_FORMAT_S24) {
+ res = -EINVAL;
+ goto error;
+ }
+ this->frame_length = this->hd ? 6 : 4;
+ this->codesize = 4 * 3 * 2;
+
+ if (this->hd)
+ this->max_frames = (this->mtu - sizeof(struct rtp_header)) / this->frame_length;
+ else if (codec_is_ll(codec))
+ this->max_frames = SPA_MIN(256u, this->mtu) / this->frame_length;
+ else
+ this->max_frames = this->mtu / this->frame_length;
+
+ return this;
+
+error_errno:
+ res = -errno;
+ goto error;
+error:
+ if (this->aptx)
+ aptx_finish(this->aptx);
+ free(this);
+ errno = -res;
+ return NULL;
+}
+
+static void codec_deinit(void *data)
+{
+ struct impl *this = data;
+ aptx_finish(this->aptx);
+ free(this);
+}
+
+static int codec_abr_process (void *data, size_t unsent)
+{
+ return -ENOTSUP;
+}
+
+static int codec_start_encode (void *data,
+ void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp)
+{
+ struct impl *this = data;
+
+ this->frame_count = 0;
+
+ if (!this->hd)
+ return 0;
+
+ this->header = (struct rtp_header *)dst;
+ memset(this->header, 0, sizeof(struct rtp_header));
+
+ this->header->v = 2;
+ this->header->pt = 96;
+ this->header->sequence_number = htons(seqnum);
+ this->header->timestamp = htonl(timestamp);
+ return sizeof(struct rtp_header);
+}
+
+static int codec_encode(void *data,
+ const void *src, size_t src_size,
+ void *dst, size_t dst_size,
+ size_t *dst_out, int *need_flush)
+{
+ struct impl *this = data;
+ size_t avail_dst_size;
+ int res;
+
+ avail_dst_size = (this->max_frames - this->frame_count) * this->frame_length;
+ if (SPA_UNLIKELY(dst_size < avail_dst_size)) {
+ *need_flush = NEED_FLUSH_ALL;
+ return 0;
+ }
+
+ res = aptx_encode(this->aptx, src, src_size,
+ dst, avail_dst_size, dst_out);
+ if(SPA_UNLIKELY(res < 0))
+ return -EINVAL;
+
+ this->frame_count += *dst_out / this->frame_length;
+ *need_flush = (this->frame_count >= this->max_frames) ? NEED_FLUSH_ALL : NEED_FLUSH_NO;
+ return res;
+}
+
+static int codec_start_decode (void *data,
+ const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp)
+{
+ struct impl *this = data;
+
+ if (!this->hd)
+ return 0;
+
+ const struct rtp_header *header = src;
+ size_t header_size = sizeof(struct rtp_header);
+
+ spa_return_val_if_fail(src_size > header_size, -EINVAL);
+
+ if (seqnum)
+ *seqnum = ntohs(header->sequence_number);
+ if (timestamp)
+ *timestamp = ntohl(header->timestamp);
+ return header_size;
+}
+
+static int codec_decode(void *data,
+ const void *src, size_t src_size,
+ void *dst, size_t dst_size,
+ size_t *dst_out)
+{
+ struct impl *this = data;
+ int res;
+
+ res = aptx_decode(this->aptx, src, src_size,
+ dst, dst_size, dst_out);
+
+ return res;
+}
+
+/*
+ * mSBC duplex codec
+ *
+ * When connected as SRC to SNK, aptX-LL sink may send back mSBC data.
+ */
+
+static int msbc_enum_config(const struct media_codec *codec, uint32_t flags,
+ const void *caps, size_t caps_size, uint32_t id, uint32_t idx,
+ struct spa_pod_builder *b, struct spa_pod **param)
+{
+ struct spa_audio_info_raw info = { 0, };
+
+ if (caps_size < sizeof(a2dp_aptx_ll_t))
+ return -EINVAL;
+
+ if (idx > 0)
+ return 0;
+
+ info.format = SPA_AUDIO_FORMAT_S16_LE;
+ info.channels = 1;
+ info.position[0] = SPA_AUDIO_CHANNEL_MONO;
+ info.rate = 16000;
+
+ *param = spa_format_audio_raw_build(b, id, &info);
+ return *param == NULL ? -EIO : 1;
+}
+
+static int msbc_validate_config(const struct media_codec *codec, uint32_t flags,
+ const void *caps, size_t caps_size,
+ struct spa_audio_info *info)
+{
+ spa_zero(*info);
+ info->media_type = SPA_MEDIA_TYPE_audio;
+ info->media_subtype = SPA_MEDIA_SUBTYPE_raw;
+ info->info.raw.format = SPA_AUDIO_FORMAT_S16_LE;
+ info->info.raw.channels = 1;
+ info->info.raw.position[0] = SPA_AUDIO_CHANNEL_MONO;
+ info->info.raw.rate = 16000;
+ return 0;
+}
+
+static int msbc_reduce_bitpool(void *data)
+{
+ return -ENOTSUP;
+}
+
+static int msbc_increase_bitpool(void *data)
+{
+ return -ENOTSUP;
+}
+
+static int msbc_get_block_size(void *data)
+{
+ return MSBC_DECODED_SIZE;
+}
+
+static void *msbc_init(const struct media_codec *codec, uint32_t flags,
+ void *config, size_t config_len, const struct spa_audio_info *info,
+ void *props, size_t mtu)
+{
+ struct msbc_impl *this = NULL;
+ int res;
+
+ if (info->media_type != SPA_MEDIA_TYPE_audio ||
+ info->media_subtype != SPA_MEDIA_SUBTYPE_raw ||
+ info->info.raw.format != SPA_AUDIO_FORMAT_S16_LE) {
+ res = -EINVAL;
+ goto error;
+ }
+
+ if ((this = calloc(1, sizeof(struct msbc_impl))) == NULL)
+ goto error_errno;
+
+ if ((res = sbc_init_msbc(&this->msbc, 0)) < 0)
+ goto error;
+
+ this->msbc.endian = SBC_LE;
+
+ return this;
+
+error_errno:
+ res = -errno;
+ goto error;
+error:
+ free(this);
+ errno = -res;
+ return NULL;
+}
+
+static void msbc_deinit(void *data)
+{
+ struct msbc_impl *this = data;
+ sbc_finish(&this->msbc);
+ free(this);
+}
+
+static int msbc_abr_process (void *data, size_t unsent)
+{
+ return -ENOTSUP;
+}
+
+static int msbc_start_encode (void *data,
+ void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp)
+{
+ return -ENOTSUP;
+}
+
+static int msbc_encode(void *data,
+ const void *src, size_t src_size,
+ void *dst, size_t dst_size,
+ size_t *dst_out, int *need_flush)
+{
+ return -ENOTSUP;
+}
+
+static int msbc_start_decode (void *data,
+ const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp)
+{
+ return 0;
+}
+
+static int msbc_decode(void *data,
+ const void *src, size_t src_size,
+ void *dst, size_t dst_size,
+ size_t *dst_out)
+{
+ struct msbc_impl *this = data;
+ const uint8_t sync[3] = { 0xAD, 0x00, 0x00 };
+ size_t processed = 0;
+ int res;
+
+ spa_assert(sizeof(sync) <= MSBC_PAYLOAD_SIZE);
+
+ *dst_out = 0;
+
+ /* Scan for msbc sync sequence.
+ * We could probably assume fixed (<57-byte payload><1-byte pad>)+ format
+ * which devices seem to be sending. Don't know if there are variations,
+ * so we make weaker assumption here.
+ */
+ while (src_size >= MSBC_PAYLOAD_SIZE) {
+ if (memcmp(src, sync, sizeof(sync)) == 0)
+ break;
+ src = (uint8_t*)src + 1;
+ --src_size;
+ ++processed;
+ }
+
+ res = sbc_decode(&this->msbc, src, src_size,
+ dst, dst_size, dst_out);
+ if (res <= 0)
+ res = SPA_MIN((size_t)MSBC_PAYLOAD_SIZE, src_size); /* skip bad payload */
+
+ processed += res;
+ return processed;
+}
+
+
+const struct media_codec a2dp_codec_aptx = {
+ .id = SPA_BLUETOOTH_AUDIO_CODEC_APTX,
+ .codec_id = A2DP_CODEC_VENDOR,
+ .vendor = { .vendor_id = APTX_VENDOR_ID,
+ .codec_id = APTX_CODEC_ID },
+ .name = "aptx",
+ .description = "aptX",
+ .fill_caps = codec_fill_caps,
+ .select_config = codec_select_config,
+ .enum_config = codec_enum_config,
+ .init = codec_init,
+ .deinit = codec_deinit,
+ .get_block_size = codec_get_block_size,
+ .abr_process = codec_abr_process,
+ .start_encode = codec_start_encode,
+ .encode = codec_encode,
+ .start_decode = codec_start_decode,
+ .decode = codec_decode,
+ .reduce_bitpool = codec_reduce_bitpool,
+ .increase_bitpool = codec_increase_bitpool,
+};
+
+
+const struct media_codec a2dp_codec_aptx_hd = {
+ .id = SPA_BLUETOOTH_AUDIO_CODEC_APTX_HD,
+ .codec_id = A2DP_CODEC_VENDOR,
+ .vendor = { .vendor_id = APTX_HD_VENDOR_ID,
+ .codec_id = APTX_HD_CODEC_ID },
+ .name = "aptx_hd",
+ .description = "aptX HD",
+ .fill_caps = codec_fill_caps,
+ .select_config = codec_select_config,
+ .enum_config = codec_enum_config,
+ .init = codec_init,
+ .deinit = codec_deinit,
+ .get_block_size = codec_get_block_size,
+ .abr_process = codec_abr_process,
+ .start_encode = codec_start_encode,
+ .encode = codec_encode,
+ .start_decode = codec_start_decode,
+ .decode = codec_decode,
+ .reduce_bitpool = codec_reduce_bitpool,
+ .increase_bitpool = codec_increase_bitpool,
+};
+
+#define APTX_LL_COMMON_DEFS \
+ .codec_id = A2DP_CODEC_VENDOR, \
+ .description = "aptX-LL", \
+ .fill_caps = codec_fill_caps, \
+ .select_config = codec_select_config_ll, \
+ .enum_config = codec_enum_config, \
+ .init = codec_init, \
+ .deinit = codec_deinit, \
+ .get_block_size = codec_get_block_size, \
+ .abr_process = codec_abr_process, \
+ .start_encode = codec_start_encode, \
+ .encode = codec_encode, \
+ .reduce_bitpool = codec_reduce_bitpool, \
+ .increase_bitpool = codec_increase_bitpool
+
+
+const struct media_codec a2dp_codec_aptx_ll_0 = {
+ APTX_LL_COMMON_DEFS,
+ .id = SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL,
+ .vendor = { .vendor_id = APTX_LL_VENDOR_ID,
+ .codec_id = APTX_LL_CODEC_ID },
+ .name = "aptx_ll",
+ .endpoint_name = "aptx_ll_0",
+};
+
+const struct media_codec a2dp_codec_aptx_ll_1 = {
+ APTX_LL_COMMON_DEFS,
+ .id = SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL,
+ .vendor = { .vendor_id = APTX_LL_VENDOR_ID2,
+ .codec_id = APTX_LL_CODEC_ID },
+ .name = "aptx_ll",
+ .endpoint_name = "aptx_ll_1",
+};
+
+/* Voice channel mSBC, not a real A2DP codec */
+static const struct media_codec aptx_ll_msbc = {
+ .codec_id = A2DP_CODEC_VENDOR,
+ .name = "aptx_ll_msbc",
+ .description = "aptX-LL mSBC",
+ .fill_caps = codec_fill_caps,
+ .select_config = codec_select_config_ll,
+ .enum_config = msbc_enum_config,
+ .validate_config = msbc_validate_config,
+ .init = msbc_init,
+ .deinit = msbc_deinit,
+ .get_block_size = msbc_get_block_size,
+ .abr_process = msbc_abr_process,
+ .start_encode = msbc_start_encode,
+ .encode = msbc_encode,
+ .start_decode = msbc_start_decode,
+ .decode = msbc_decode,
+ .reduce_bitpool = msbc_reduce_bitpool,
+ .increase_bitpool = msbc_increase_bitpool,
+};
+
+static const struct spa_dict_item duplex_info_items[] = {
+ { "duplex.boost", "true" },
+};
+static const struct spa_dict duplex_info = SPA_DICT_INIT_ARRAY(duplex_info_items);
+
+const struct media_codec a2dp_codec_aptx_ll_duplex_0 = {
+ APTX_LL_COMMON_DEFS,
+ .id = SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL_DUPLEX,
+ .vendor = { .vendor_id = APTX_LL_VENDOR_ID,
+ .codec_id = APTX_LL_CODEC_ID },
+ .name = "aptx_ll_duplex",
+ .endpoint_name = "aptx_ll_duplex_0",
+ .duplex_codec = &aptx_ll_msbc,
+ .info = &duplex_info,
+};
+
+const struct media_codec a2dp_codec_aptx_ll_duplex_1 = {
+ APTX_LL_COMMON_DEFS,
+ .id = SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL_DUPLEX,
+ .vendor = { .vendor_id = APTX_LL_VENDOR_ID2,
+ .codec_id = APTX_LL_CODEC_ID },
+ .name = "aptx_ll_duplex",
+ .endpoint_name = "aptx_ll_duplex_1",
+ .duplex_codec = &aptx_ll_msbc,
+ .info = &duplex_info,
+};
+
+MEDIA_CODEC_EXPORT_DEF(
+ "aptx",
+ &a2dp_codec_aptx_hd,
+ &a2dp_codec_aptx,
+ &a2dp_codec_aptx_ll_0,
+ &a2dp_codec_aptx_ll_1,
+ &a2dp_codec_aptx_ll_duplex_0,
+ &a2dp_codec_aptx_ll_duplex_1
+);