[pulseaudio-discuss] [PATCH v3 4/4] Add A2DP LDAC codec source support
Huang-Huang Bao
eh5 at sokka.cn
Sat Dec 29 15:48:08 UTC 2018
Require https://github.com/EHfive/ldacBT
Optional (build with --disable-bluez5-ldac-codec)
LDAC User Configurations
KEY VALUE DESC DEFAULT
ldac_eqmid hq High Quality (encoder) auto
sq Standard Quality (encoder)
mq Mobile use Quality (encoder)
auto/abr Adaptive Bit Rate (encoder)
ldac_fmt s16 16-bit signed LE (encoder) auto
s24 24-bit signed LE (encoder)
s32 32-bit signed LE (encoder)
f32 32-bit float LE (encoder)
auto
---
configure.ac | 16 +
src/Makefile.am | 8 +
src/modules/bluetooth/a2dp/a2dp-api.h | 6 +
src/modules/bluetooth/a2dp/a2dp_ldac.c | 605 +++++++++++++++++++++++++
src/modules/bluetooth/a2dp/a2dp_util.c | 29 ++
5 files changed, 664 insertions(+)
create mode 100644 src/modules/bluetooth/a2dp/a2dp_ldac.c
diff --git a/configure.ac b/configure.ac
index 586077e5b..eccf9c3d9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1107,6 +1107,20 @@ AC_SUBST(HAVE_FF_APTX)
AM_CONDITIONAL([HAVE_FF_APTX], [test "x$HAVE_FF_APTX" = x1])
AS_IF([test "x$HAVE_FF_APTX" = "x1"], AC_DEFINE([HAVE_FF_APTX], 1, [Bluez 5 A2DP aptX, aptX HD codecs enabled]))
+## LDAC source ##
+AC_ARG_ENABLE([bluez5-ldac-codec],
+ AS_HELP_STRING([--disable-bluez5-ldac-codec],[Disable optional A2DP LDAC codec encoding support (Bluez 5)]))
+AS_IF([test "x$HAVE_BLUEZ_5" = "x1" && test "x$enable_bluez5_ldac_codec" != "xno"],
+ [PKG_CHECK_MODULES(LDACBT_ENC, [ ldacBT-enc ], HAVE_LDACBT_ENC=1, HAVE_LDACBT_ENC=0)],
+ HAVE_LDACBT_ENC=0)
+AS_IF([test "x$HAVE_BLUEZ_5" = "x1" && test "x$enable_bluez5_ldac_codec" != "xno"],
+ [PKG_CHECK_MODULES(LDACBT_ABR, [ ldacBT-abr ], HAVE_LDACBT_ABR=1, HAVE_LDACBT_ABR=0)],
+ HAVE_LDACBT_ABR=0)
+AS_IF([test "x$HAVE_LDACBT_ENC" = "x1" && test "x$HAVE_LDACBT_ABR" = "x1"], HAVE_LDACBT=1, HAVE_LDACBT=0)
+AC_SUBST(HAVE_LDACBT)
+AM_CONDITIONAL([HAVE_LDACBT], [test "x$HAVE_LDACBT" = x1])
+AS_IF([test "x$HAVE_LDACBT" = "x1"], AC_DEFINE([HAVE_LDACBT], 1, [Bluez 5 A2DP LDAC codec encoding support enabled]))
+
## Bluetooth Headset profiles backend ##
AC_ARG_ENABLE([bluez5-ofono-headset],
@@ -1613,6 +1627,7 @@ AS_IF([test "x$HAVE_SYSTEMD_JOURNAL" = "x1"], ENABLE_SYSTEMD_JOURNAL=yes, ENABLE
AS_IF([test "x$HAVE_BLUEZ_5" = "x1"], ENABLE_BLUEZ_5=yes, ENABLE_BLUEZ_5=no)
AS_IF([test "x$HAVE_FDK_AAC" = "x1"], ENABLE_BLUEZ_5_AAC_CODEC=yes, ENABLE_BLUEZ_5_AAC_CODEC=no)
AS_IF([test "x$HAVE_FF_APTX" = "x1"], ENABLE_BLUEZ_5_APTX_CODEC=yes, ENABLE_BLUEZ_5_APTX_CODEC=no)
+AS_IF([test "x$HAVE_LDACBT" = "x1"], ENABLE_BLUEZ_5_LDAC_CODEC=yes, ENABLE_BLUEZ_5_LDAC_CODEC=no)
AS_IF([test "x$HAVE_BLUEZ_5_OFONO_HEADSET" = "x1"], ENABLE_BLUEZ_5_OFONO_HEADSET=yes, ENABLE_BLUEZ_5_OFONO_HEADSET=no)
AS_IF([test "x$HAVE_BLUEZ_5_NATIVE_HEADSET" = "x1"], ENABLE_BLUEZ_5_NATIVE_HEADSET=yes, ENABLE_BLUEZ_5_NATIVE_HEADSET=no)
AS_IF([test "x$HAVE_HAL_COMPAT" = "x1"], ENABLE_HAL_COMPAT=yes, ENABLE_HAL_COMPAT=no)
@@ -1673,6 +1688,7 @@ echo "
Enable BlueZ 5: ${ENABLE_BLUEZ_5}
Enable A2DP AAC codec: ${ENABLE_BLUEZ_5_AAC_CODEC}
Enable A2DP aptX(HD): ${ENABLE_BLUEZ_5_APTX_CODEC}
+ Enable A2DP LDAC source: ${ENABLE_BLUEZ_5_LDAC_CODEC}
Enable ofono headsets: ${ENABLE_BLUEZ_5_OFONO_HEADSET}
Enable native headsets: ${ENABLE_BLUEZ_5_NATIVE_HEADSET}
Enable udev: ${ENABLE_UDEV}
diff --git a/src/Makefile.am b/src/Makefile.am
index 5bb43c3a6..51a620a81 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -2136,6 +2136,10 @@ if HAVE_FF_APTX
libbluez5_util_la_SOURCES += \
modules/bluetooth/a2dp/a2dp_aptx.c
endif
+if HAVE_LDACBT
+libbluez5_util_la_SOURCES += \
+ modules/bluetooth/a2dp/a2dp_ldac.c
+endif
if HAVE_BLUEZ_5_OFONO_HEADSET
libbluez5_util_la_SOURCES += \
modules/bluetooth/backend-ofono.c
@@ -2156,6 +2160,10 @@ if HAVE_FF_APTX
libbluez5_util_la_LIBADD += $(FF_AVCODEC_LIBS) $(FF_AVUTIL_LIBS)
libbluez5_util_la_CFLAGS += $(FF_AVCODEC_CFLAGS) $(FF_AVUTIL_CFLAGS)
endif
+if HAVE_LDACBT
+libbluez5_util_la_LIBADD += $(LDACBT_ENC_LIBS) $(LDACBT_ABR_LIBS)
+libbluez5_util_la_CFLAGS += $(LDACBT_ENC_CFLAGS) $(LDACBT_ABR_CFLAGS)
+endif
module_bluez5_discover_la_SOURCES = modules/bluetooth/module-bluez5-discover.c
module_bluez5_discover_la_LDFLAGS = $(MODULE_LDFLAGS)
diff --git a/src/modules/bluetooth/a2dp/a2dp-api.h b/src/modules/bluetooth/a2dp/a2dp-api.h
index 59ec21a8b..155127c4f 100644
--- a/src/modules/bluetooth/a2dp/a2dp-api.h
+++ b/src/modules/bluetooth/a2dp/a2dp-api.h
@@ -44,6 +44,9 @@ extern const pa_a2dp_codec_t pa_a2dp_aac;
extern const pa_a2dp_codec_t pa_a2dp_aptx;
extern const pa_a2dp_codec_t pa_a2dp_aptx_hd;
#endif
+#ifdef HAVE_LDACBT
+extern const pa_a2dp_codec_t pa_a2dp_ldac;
+#endif
/* Run from <pa_a2dp_sink_t>.encode */
@@ -72,6 +75,9 @@ typedef enum pa_a2dp_codec_index {
#ifdef HAVE_FF_APTX
PA_A2DP_SOURCE_APTX,
PA_A2DP_SOURCE_APTX_HD,
+#endif
+#ifdef HAVE_LDACBT
+ PA_A2DP_SOURCE_LDAC,
#endif
PA_A2DP_SOURCE_MAX,
PA_A2DP_CODEC_INDEX_UNAVAILABLE
diff --git a/src/modules/bluetooth/a2dp/a2dp_ldac.c b/src/modules/bluetooth/a2dp/a2dp_ldac.c
new file mode 100644
index 000000000..8095d2435
--- /dev/null
+++ b/src/modules/bluetooth/a2dp/a2dp_ldac.c
@@ -0,0 +1,605 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2018 Huang-Huang Bao
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <arpa/inet.h>
+#include <string.h>
+
+#ifdef HAVE_CONFIG_H
+
+#include <config.h>
+
+#endif
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/once.h>
+
+#include <ldacBT.h>
+#include <ldacBT_abr.h>
+
+#include "a2dp-api.h"
+
+
+#define streq(a, b) (!strcmp((a),(b)))
+
+#define AVDT_MEDIA_HDR_SIZE 12
+
+#define LDAC_ABR_THRESHOLD_CRITICAL 6
+#define LDAC_ABR_THRESHOLD_DANGEROUSTREND 4
+#define LDAC_ABR_THRESHOLD_SAFETY_FOR_HQSQ 2
+
+
+static const bool ldac_abr_loaded = true;
+
+typedef struct ldac_info {
+ HANDLE_LDAC_BT hLdacBt;
+ HANDLE_LDAC_ABR hLdacAbr;
+
+ pa_a2dp_source_read_cb_t read_pcm;
+ pa_a2dp_source_read_buf_free_cb_t read_buf_free;
+
+ int eqmid;
+ bool enable_abr;
+ int channel_mode;
+ pa_sample_format_t force_pa_fmt;
+ LDACBT_SMPL_FMT_T pcm_fmt;
+ uint32_t pcm_frequency;
+
+ uint16_t pcm_lsu;
+ size_t ldac_frame_size;
+ size_t pcm_read_size;
+ size_t q_write_block_size;
+ pa_sample_spec sample_spec;
+
+ uint16_t seq_num;
+ uint32_t layer_specific;
+ uint32_t written;
+ size_t tx_length;
+
+ size_t mtu;
+
+} ldac_info_t;
+
+static bool pa_ldac_encoder_load() {
+ return true;
+}
+
+static bool
+pa_ldac_encoder_init(pa_a2dp_source_read_cb_t read_cb, pa_a2dp_source_read_buf_free_cb_t free_cb, void **codec_data) {
+ ldac_info_t *info = pa_xmalloc0(sizeof(ldac_info_t));
+ *codec_data = info;
+ info->read_pcm = read_cb;
+ info->read_buf_free = free_cb;
+ info->eqmid = LDACBT_EQMID_HQ;
+ if (ldac_abr_loaded)
+ info->enable_abr = true;
+ info->force_pa_fmt = PA_SAMPLE_INVALID;
+ return true;
+}
+
+/* KEY VALUE DESC DEFAULT
+ * ldac_eqmid hq High Quality (encoder) auto
+ * sq Standard Quality (encoder)
+ * mq Mobile use Quality (encoder)
+ * auto/abr Adaptive Bit Rate (encoder)
+ *
+ * ldac_fmt s16 16-bit signed LE (encoder) auto
+ * s24 24-bit signed LE (encoder)
+ * s32 32-bit signed LE (encoder)
+ * f32 32-bit float LE (encoder)
+ * auto
+ */
+static int pa_ldac_update_user_config(pa_proplist *user_config, void **codec_data) {
+ ldac_info_t *i = *codec_data;
+ const char *ldac_eqmid_str, *ldac_fmt_str;
+ int ret = 0;
+ ldac_eqmid_str = pa_proplist_gets(user_config, "ldac_eqmid");
+ ldac_fmt_str = pa_proplist_gets(user_config, "ldac_fmt");
+
+ pa_log_debug("LDAC ABR library loaded: %s", ldac_abr_loaded ? "true" : "false");
+
+ if (ldac_eqmid_str) {
+ if (streq(ldac_eqmid_str, "hq")) {
+ i->eqmid = LDACBT_EQMID_HQ;
+ i->enable_abr = false;
+ ret++;
+ } else if (streq(ldac_eqmid_str, "sq")) {
+ i->eqmid = LDACBT_EQMID_SQ;
+ i->enable_abr = false;
+ ret++;
+ } else if (streq(ldac_eqmid_str, "mq")) {
+ i->eqmid = LDACBT_EQMID_MQ;
+ i->enable_abr = false;
+ ret++;
+ } else if (streq(ldac_eqmid_str, "auto") ||
+ streq(ldac_eqmid_str, "abr")) {
+ i->eqmid = LDACBT_EQMID_HQ;
+ if (ldac_abr_loaded)
+ i->enable_abr = true;
+ ret++;
+ } else {
+ pa_log("ldac_eqmid parameter must be either hq, sq, mq, or auto/abr (found %s)", ldac_eqmid_str);
+ }
+ }
+
+ if (ldac_fmt_str) {
+ if (streq(ldac_fmt_str, "s16")) {
+ i->force_pa_fmt = PA_SAMPLE_S16LE;
+ ret++;
+ } else if (streq(ldac_fmt_str, "s24")) {
+ i->force_pa_fmt = PA_SAMPLE_S24LE;
+ ret++;
+ } else if (streq(ldac_fmt_str, "s32")) {
+ i->force_pa_fmt = PA_SAMPLE_S32LE;
+ ret++;
+ } else if (streq(ldac_fmt_str, "f32")) {
+ i->force_pa_fmt = PA_SAMPLE_FLOAT32LE;
+ ret++;
+ } else if (streq(ldac_fmt_str, "auto")) {
+ i->force_pa_fmt = PA_SAMPLE_INVALID;
+ ret++;
+ } else {
+ pa_log("ldac_fmt parameter must be either s16, s24, s32, f32 or auto (found %s)", ldac_fmt_str);
+ }
+ }
+
+ return ret;
+}
+
+static size_t
+pa_ldac_encode(uint32_t timestamp, void *write_buf, size_t write_buf_size, size_t *_encoded, void *read_cb_data,
+ void **codec_data) {
+ struct rtp_header *header;
+ struct rtp_payload *payload;
+ size_t nbytes;
+ void *d;
+ const void *p;
+ size_t to_write, to_encode, ldac_enc_read;
+ unsigned frame_count;
+ ldac_info_t *ldac_info = *codec_data;
+ pa_assert(ldac_info);
+ pa_assert(ldac_info->hLdacBt);
+
+
+ if (ldac_info->hLdacAbr && ldac_info->enable_abr) {
+ ldac_ABR_Proc(ldac_info->hLdacBt, ldac_info->hLdacAbr,
+ (unsigned int) (ldac_info->tx_length / ldac_info->q_write_block_size),
+ (unsigned int) ldac_info->enable_abr);
+ }
+
+
+ ldac_enc_read = (pa_frame_size(&ldac_info->sample_spec) * LDACBT_ENC_LSU);
+
+ header = write_buf;
+ payload = (struct rtp_payload *) ((uint8_t *) write_buf + sizeof(*header));
+
+ frame_count = 0;
+
+ /* Maximum pcm size for 1 LDAC packet (LDAC MQ) */
+ to_encode = (ldac_info->mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
+ / 110 * ldac_info->pcm_read_size;
+
+ d = (uint8_t *) write_buf + sizeof(*header) + sizeof(*payload);
+ to_write = write_buf_size - sizeof(*header) - sizeof(*payload);
+
+ *_encoded = 0;
+ while (PA_LIKELY(to_encode > 0 && to_write > 0 && frame_count == 0)) {
+ int written;
+ int encoded;
+ int ldac_frame_num;
+ int ret_code;
+ ldac_info->read_pcm(&p, ldac_enc_read, read_cb_data);
+
+ ret_code = ldacBT_encode(ldac_info->hLdacBt, (void *) p, &encoded, (uint8_t *) d, &written, &ldac_frame_num);
+
+ ldac_info->read_buf_free(&p, read_cb_data);
+
+ if (PA_UNLIKELY(ret_code < 0)) {
+ int err;
+ pa_log_error("LDAC encoding error, written:%d encoded:%d ldac_frame_num:%d", written, encoded,
+ ldac_frame_num);
+ err = ldacBT_get_error_code(ldac_info->hLdacBt);
+ pa_log_error("LDACBT_API_ERR:%d LDACBT_HANDLE_ERR:%d LDACBT_BLOCK_ERR:%d", LDACBT_API_ERR(err),
+ LDACBT_HANDLE_ERR(err), LDACBT_BLOCK_ERR(err));
+ *_encoded = 0;
+ return 0;
+ }
+
+ pa_assert_fp(encoded == (int) ldac_enc_read);
+ pa_assert_fp(written <= (int) to_write);
+
+ *_encoded += encoded;
+ to_encode -= encoded;
+
+ d = (uint8_t *) d + written;
+ to_write -= written;
+
+ frame_count += ldac_frame_num;
+
+ }
+
+
+ PA_ONCE_BEGIN
+ {
+ const int v = ldacBT_get_version();
+ pa_log_notice("Using LDAC library: version: %x.%02x.%02x",
+ v >> 16,
+ (v >> 8) & 0x0ff,
+ v & 0x0ff
+ );
+ }
+ PA_ONCE_END;
+
+ /* write it to the fifo */
+ memset(write_buf, 0, sizeof(*header) + sizeof(*payload));
+ header->v = 2;
+ header->pt = 1;
+ header->sequence_number = htons(ldac_info->seq_num++);
+ header->timestamp = htonl(timestamp);
+ header->ssrc = htonl(1);
+ payload->frame_count = frame_count;
+ ldac_info->layer_specific += frame_count;
+
+ nbytes = (uint8_t *) d - (uint8_t *) write_buf;
+
+ ldac_info->written += nbytes - sizeof(*header) - sizeof(*payload);
+
+ return nbytes;
+}
+
+static void
+pa_ldac_config_transport(pa_sample_spec default_sample_spec, const void *configuration, size_t configuration_size,
+ pa_sample_spec *sample_spec, void **codec_data) {
+ ldac_info_t *ldac_info = *codec_data;
+ a2dp_ldac_t *config = (a2dp_ldac_t *) configuration;
+ pa_sample_format_t fmt;
+ pa_assert(ldac_info);
+ pa_assert_se(configuration_size == sizeof(*config));
+
+ ldac_info->hLdacBt = NULL;
+ ldac_info->hLdacAbr = NULL;
+
+ if (ldac_info->force_pa_fmt == PA_SAMPLE_INVALID)
+ fmt = default_sample_spec.format;
+ else
+ fmt = ldac_info->force_pa_fmt;
+
+ switch (fmt) {
+ case PA_SAMPLE_FLOAT32LE:
+ case PA_SAMPLE_FLOAT32BE:
+ ldac_info->pcm_fmt = LDACBT_SMPL_FMT_F32;
+ sample_spec->format = PA_SAMPLE_FLOAT32LE;
+ break;
+ case PA_SAMPLE_S32LE:
+ case PA_SAMPLE_S32BE:
+ ldac_info->pcm_fmt = LDACBT_SMPL_FMT_S32;
+ sample_spec->format = PA_SAMPLE_S32LE;
+ break;
+ case PA_SAMPLE_S24LE:
+ case PA_SAMPLE_S24BE:
+ case PA_SAMPLE_S24_32LE:
+ case PA_SAMPLE_S24_32BE:
+ ldac_info->pcm_fmt = LDACBT_SMPL_FMT_S24;
+ sample_spec->format = PA_SAMPLE_S24LE;
+ break;
+ default:
+ ldac_info->pcm_fmt = LDACBT_SMPL_FMT_S16;
+ sample_spec->format = PA_SAMPLE_S16LE;
+ }
+
+
+ switch (config->frequency) {
+ case LDACBT_SAMPLING_FREQ_044100:
+ ldac_info->pcm_frequency = 44100U;
+ sample_spec->rate = 44100U;
+ break;
+ case LDACBT_SAMPLING_FREQ_048000:
+ ldac_info->pcm_frequency = 48000U;
+ sample_spec->rate = 48000U;
+ break;
+ case LDACBT_SAMPLING_FREQ_088200:
+ ldac_info->pcm_frequency = 88200U;
+ sample_spec->rate = 88200U;
+ break;
+ case LDACBT_SAMPLING_FREQ_096000:
+ ldac_info->pcm_frequency = 96000U;
+ sample_spec->rate = 96000U;
+ break;
+ case LDACBT_SAMPLING_FREQ_176400:
+ ldac_info->pcm_frequency = 176400U;
+ sample_spec->rate = 176400U;
+ break;
+ case LDACBT_SAMPLING_FREQ_192000:
+ ldac_info->pcm_frequency = 192000U;
+ sample_spec->rate = 192000U;
+ break;
+ default:
+ pa_assert_not_reached();
+ }
+
+ switch (config->channel_mode) {
+ case LDACBT_CHANNEL_MODE_MONO:
+ ldac_info->channel_mode = LDACBT_CHANNEL_MODE_MONO;
+ sample_spec->channels = 1;
+ break;
+ case LDACBT_CHANNEL_MODE_DUAL_CHANNEL:
+ ldac_info->channel_mode = LDACBT_CHANNEL_MODE_DUAL_CHANNEL;
+ sample_spec->channels = 2;
+ break;
+ case LDACBT_CHANNEL_MODE_STEREO:
+ ldac_info->channel_mode = LDACBT_CHANNEL_MODE_STEREO;
+ sample_spec->channels = 2;
+ break;
+ default:
+ pa_assert_not_reached();
+ }
+
+ switch (ldac_info->pcm_frequency) {
+ case 44100:
+ case 48000:
+ ldac_info->pcm_lsu = 128;
+ break;
+ case 88200:
+ case 96000:
+ ldac_info->pcm_lsu = 256;
+ break;
+ case 176400:
+ case 192000:
+ ldac_info->pcm_lsu = 512;
+ break;
+ default:
+ pa_assert_not_reached();
+ }
+
+ switch (ldac_info->eqmid) {
+ case LDACBT_EQMID_HQ:
+ ldac_info->ldac_frame_size = 330;
+ break;
+ case LDACBT_EQMID_SQ:
+ ldac_info->ldac_frame_size = 220;
+ break;
+ case LDACBT_EQMID_MQ:
+ ldac_info->ldac_frame_size = 110;
+ break;
+ default:
+ pa_assert_not_reached();
+ }
+
+ ldac_info->sample_spec = *sample_spec;
+ ldac_info->pcm_read_size = (ldac_info->pcm_lsu * pa_frame_size(&ldac_info->sample_spec));
+
+};
+
+static void pa_ldac_get_block_size(size_t write_link_mtu, size_t *write_block_size, void **codec_data) {
+ ldac_info_t *ldac_info = *codec_data;
+ pa_assert(ldac_info);
+
+ ldac_info->mtu = write_link_mtu;
+
+ ldac_info->q_write_block_size = ((write_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
+ / ldac_info->ldac_frame_size * ldac_info->pcm_read_size);
+ *write_block_size = LDACBT_MAX_LSU * pa_frame_size(&ldac_info->sample_spec);
+};
+
+
+static void pa_ldac_setup_stream(void **codec_data) {
+ int ret;
+ ldac_info_t *ldac_info = *codec_data;
+ pa_assert(ldac_info);
+
+ ldac_info->layer_specific = 0;
+ ldac_info->written = 0;
+ if (ldac_info->hLdacBt)
+ ldacBT_free_handle(ldac_info->hLdacBt);
+ ldac_info->hLdacBt = ldacBT_get_handle();
+
+
+ ret = ldacBT_init_handle_encode(ldac_info->hLdacBt,
+ (int) ldac_info->mtu + AVDT_MEDIA_HDR_SIZE,
+ ldac_info->eqmid,
+ ldac_info->channel_mode,
+ ldac_info->pcm_fmt,
+ ldac_info->pcm_frequency);
+ if (ret != 0) {
+ pa_log_warn("Failed to init ldacBT handle");
+ goto fail;
+ }
+
+ if (!ldac_abr_loaded)
+ return;
+
+ if (ldac_info->hLdacAbr)
+ ldac_ABR_free_handle(ldac_info->hLdacAbr);
+ ldac_info->hLdacAbr = ldac_ABR_get_handle();
+
+ ret = ldac_ABR_Init(ldac_info->hLdacAbr,
+ (unsigned int) pa_bytes_to_usec(ldac_info->q_write_block_size, &ldac_info->sample_spec) / 1000);
+ if (ret != 0) {
+ pa_log_warn("Failed to init ldacBT_ABR handle");
+ goto fail1;
+ }
+
+ ldac_ABR_set_thresholds(ldac_info->hLdacAbr, LDAC_ABR_THRESHOLD_CRITICAL,
+ LDAC_ABR_THRESHOLD_DANGEROUSTREND, LDAC_ABR_THRESHOLD_SAFETY_FOR_HQSQ);
+ return;
+
+fail:
+ ldacBT_free_handle(ldac_info->hLdacBt);
+ ldac_info->hLdacBt = NULL;
+ if (!ldac_abr_loaded)
+ return;
+fail1:
+ ldac_ABR_free_handle(ldac_info->hLdacAbr);
+ ldac_info->hLdacAbr = NULL;
+ ldac_info->enable_abr = false;
+};
+
+static void pa_ldac_set_tx_length(size_t len, void **codec_data) {
+ ldac_info_t *ldac_info = *codec_data;
+ pa_assert(ldac_info);
+ ldac_info->tx_length = len;
+};
+
+static void pa_ldac_free(void **codec_data) {
+ ldac_info_t *ldac_info = *codec_data;
+ if (!ldac_info)
+ return;
+
+ if (ldac_info->hLdacBt)
+ ldacBT_free_handle(ldac_info->hLdacBt);
+
+ if (ldac_info->hLdacAbr && ldac_abr_loaded)
+ ldac_ABR_free_handle(ldac_info->hLdacAbr);
+
+ pa_xfree(ldac_info);
+ *codec_data = NULL;
+
+};
+
+static size_t pa_ldac_get_capabilities(void **_capabilities) {
+ a2dp_ldac_t *capabilities = pa_xmalloc0(sizeof(a2dp_ldac_t));
+
+ capabilities->info = A2DP_SET_VENDOR_ID_CODEC_ID(LDAC_VENDOR_ID, LDAC_CODEC_ID);
+ capabilities->frequency = LDACBT_SAMPLING_FREQ_044100 | LDACBT_SAMPLING_FREQ_048000 |
+ LDACBT_SAMPLING_FREQ_088200 | LDACBT_SAMPLING_FREQ_096000;
+ capabilities->channel_mode = LDACBT_CHANNEL_MODE_MONO | LDACBT_CHANNEL_MODE_DUAL_CHANNEL |
+ LDACBT_CHANNEL_MODE_STEREO;
+ *_capabilities = capabilities;
+
+ return sizeof(*capabilities);
+};
+
+static size_t
+pa_ldac_select_configuration(const pa_sample_spec default_sample_spec, const uint8_t *supported_capabilities,
+ const size_t capabilities_size, void **configuration) {
+ a2dp_ldac_t *cap = (a2dp_ldac_t *) supported_capabilities;
+ a2dp_ldac_t *config = pa_xmalloc0(sizeof(a2dp_ldac_t));
+ pa_a2dp_freq_cap_t ldac_freq_cap, ldac_freq_table[] = {
+ {44100U, LDACBT_SAMPLING_FREQ_044100},
+ {48000U, LDACBT_SAMPLING_FREQ_048000},
+ {88200U, LDACBT_SAMPLING_FREQ_088200},
+ {96000U, LDACBT_SAMPLING_FREQ_096000}
+ };
+
+ if (capabilities_size != sizeof(a2dp_ldac_t))
+ return 0;
+
+ config->info = A2DP_SET_VENDOR_ID_CODEC_ID(LDAC_VENDOR_ID, LDAC_CODEC_ID);
+
+ if (!pa_a2dp_select_cap_frequency(cap->frequency, default_sample_spec, ldac_freq_table,
+ PA_ELEMENTSOF(ldac_freq_table), &ldac_freq_cap))
+ return 0;
+
+ config->frequency = (uint8_t) ldac_freq_cap.cap;
+
+ if (default_sample_spec.channels <= 1) {
+ if (cap->channel_mode & LDACBT_CHANNEL_MODE_MONO)
+ config->channel_mode = LDACBT_CHANNEL_MODE_MONO;
+ else if (cap->channel_mode & LDACBT_CHANNEL_MODE_STEREO)
+ config->channel_mode = LDACBT_CHANNEL_MODE_STEREO;
+ else if (cap->channel_mode & LDACBT_CHANNEL_MODE_DUAL_CHANNEL)
+ config->channel_mode = LDACBT_CHANNEL_MODE_DUAL_CHANNEL;
+ else {
+ pa_log_error("No supported channel modes");
+ return 0;
+ }
+ }
+
+ if (default_sample_spec.channels >= 2) {
+ if (cap->channel_mode & LDACBT_CHANNEL_MODE_STEREO)
+ config->channel_mode = LDACBT_CHANNEL_MODE_STEREO;
+ else if (cap->channel_mode & LDACBT_CHANNEL_MODE_DUAL_CHANNEL)
+ config->channel_mode = LDACBT_CHANNEL_MODE_DUAL_CHANNEL;
+ else if (cap->channel_mode & LDACBT_CHANNEL_MODE_MONO)
+ config->channel_mode = LDACBT_CHANNEL_MODE_MONO;
+ else {
+ pa_log_error("No supported channel modes");
+ return 0;
+ }
+ }
+ *configuration = config;
+ return sizeof(*config);
+};
+
+static void pa_ldac_free_capabilities(void **capabilities) {
+ if (!capabilities || !*capabilities)
+ return;
+ pa_xfree(*capabilities);
+ *capabilities = NULL;
+}
+
+static bool pa_ldac_validate_configuration(const uint8_t *selected_configuration, const size_t configuration_size) {
+ a2dp_ldac_t *c = (a2dp_ldac_t *) selected_configuration;
+
+ if (configuration_size != sizeof(a2dp_ldac_t)) {
+ pa_log_error("LDAC configuration array of invalid size");
+ return false;
+ }
+
+ switch (c->frequency) {
+ case LDACBT_SAMPLING_FREQ_044100:
+ case LDACBT_SAMPLING_FREQ_048000:
+ case LDACBT_SAMPLING_FREQ_088200:
+ case LDACBT_SAMPLING_FREQ_096000:
+ case LDACBT_SAMPLING_FREQ_176400:
+ case LDACBT_SAMPLING_FREQ_192000:
+ break;
+ default:
+ pa_log_error("Invalid sampling frequency in LDAC configuration");
+ return false;
+ }
+
+ switch (c->channel_mode) {
+ case LDACBT_CHANNEL_MODE_STEREO:
+ case LDACBT_CHANNEL_MODE_DUAL_CHANNEL:
+ case LDACBT_CHANNEL_MODE_MONO:
+ break;
+ default:
+ pa_log_error("Invalid channel mode in LDAC Configuration");
+ return false;
+ }
+
+ return true;
+};
+
+
+static pa_a2dp_source_t pa_ldac_source = {
+ .encoder_load = pa_ldac_encoder_load,
+ .init = pa_ldac_encoder_init,
+ .update_user_config = pa_ldac_update_user_config,
+ .encode = pa_ldac_encode,
+ .config_transport = pa_ldac_config_transport,
+ .get_block_size = pa_ldac_get_block_size,
+ .setup_stream = pa_ldac_setup_stream,
+ .set_tx_length = pa_ldac_set_tx_length,
+ .decrease_quality = NULL,
+ .free = pa_ldac_free
+};
+
+const pa_a2dp_codec_t pa_a2dp_ldac = {
+ .name = "LDAC",
+ .codec = A2DP_CODEC_VENDOR,
+ .vendor_codec = &A2DP_SET_VENDOR_ID_CODEC_ID(LDAC_VENDOR_ID, LDAC_CODEC_ID),
+ .a2dp_sink = NULL,
+ .a2dp_source = &pa_ldac_source,
+ .get_capabilities = pa_ldac_get_capabilities,
+ .select_configuration = pa_ldac_select_configuration,
+ .free_capabilities = pa_ldac_free_capabilities,
+ .free_configuration = pa_ldac_free_capabilities,
+ .validate_configuration = pa_ldac_validate_configuration
+};
diff --git a/src/modules/bluetooth/a2dp/a2dp_util.c b/src/modules/bluetooth/a2dp/a2dp_util.c
index 8f4703518..a1e81980a 100644
--- a/src/modules/bluetooth/a2dp/a2dp_util.c
+++ b/src/modules/bluetooth/a2dp/a2dp_util.c
@@ -42,6 +42,7 @@
#define A2DP_APTX_HD_SRC_ENDPOINT A2DP_VENDOR_SRC_ENDPOINT "/APTXHD"
#define A2DP_APTX_HD_SNK_ENDPOINT A2DP_VENDOR_SNK_ENDPOINT "/APTXHD"
+#define A2DP_LDAC_SRC_ENDPOINT A2DP_VENDOR_SRC_ENDPOINT "/LDAC"
#define PA_A2DP_PRIORITY_DISABLE 0
#define PA_A2DP_PRIORITY_MIN 1
@@ -239,6 +240,11 @@ void pa_a2dp_codec_index_to_endpoint(pa_a2dp_codec_index_t codec_index, const ch
case PA_A2DP_SOURCE_APTX_HD:
*endpoint = A2DP_APTX_HD_SRC_ENDPOINT;
break;
+#endif
+#ifdef HAVE_LDACBT
+ case PA_A2DP_SOURCE_LDAC:
+ *endpoint = A2DP_LDAC_SRC_ENDPOINT;
+ break;
#endif
default:
*endpoint = NULL;
@@ -265,6 +271,10 @@ void pa_a2dp_endpoint_to_codec_index(const char *endpoint, pa_a2dp_codec_index_t
*codec_index = PA_A2DP_SINK_APTX_HD;
else if (streq(endpoint, A2DP_APTX_HD_SRC_ENDPOINT))
*codec_index = PA_A2DP_SOURCE_APTX_HD;
+#endif
+#ifdef HAVE_LDACBT
+ else if (streq(endpoint, A2DP_LDAC_SRC_ENDPOINT))
+ *codec_index = PA_A2DP_SOURCE_LDAC;
#endif
else
*codec_index = PA_A2DP_CODEC_INDEX_UNAVAILABLE;
@@ -291,6 +301,11 @@ void pa_a2dp_codec_index_to_a2dp_codec(pa_a2dp_codec_index_t codec_index, const
case PA_A2DP_SOURCE_APTX_HD:
*a2dp_codec = &pa_a2dp_aptx_hd;
break;
+#endif
+#ifdef HAVE_LDACBT
+ case PA_A2DP_SOURCE_LDAC:
+ *a2dp_codec = &pa_a2dp_ldac;
+ break;
#endif
default:
*a2dp_codec = NULL;
@@ -327,6 +342,13 @@ void pa_a2dp_a2dp_codec_to_codec_index(const pa_a2dp_codec_t *a2dp_codec, bool i
*codec_index = is_a2dp_sink ? PA_A2DP_SINK_APTX_HD : PA_A2DP_SOURCE_APTX_HD;
return;
}
+#endif
+#ifdef HAVE_LDACBT
+ else if (A2DP_GET_VENDOR_ID(*a2dp_codec->vendor_codec) == LDAC_VENDOR_ID &&
+ A2DP_GET_CODEC_ID(*a2dp_codec->vendor_codec) == LDAC_CODEC_ID) {
+ *codec_index = is_a2dp_sink ? PA_A2DP_CODEC_INDEX_UNAVAILABLE : PA_A2DP_SOURCE_LDAC;
+ return;
+ }
#endif
*codec_index = PA_A2DP_CODEC_INDEX_UNAVAILABLE;
break;
@@ -361,6 +383,13 @@ pa_a2dp_get_a2dp_codec(uint8_t codec, const a2dp_vendor_codec_t *vendor_codec, c
*a2dp_codec = &pa_a2dp_aptx_hd;
return;
}
+#endif
+#ifdef HAVE_LDACBT
+ else if (A2DP_GET_VENDOR_ID(*vendor_codec) == LDAC_VENDOR_ID &&
+ A2DP_GET_CODEC_ID(*vendor_codec) == LDAC_CODEC_ID) {
+ *a2dp_codec = &pa_a2dp_ldac;
+ return;
+ }
#endif
*a2dp_codec = NULL;
break;
--
2.19.2
More information about the pulseaudio-discuss
mailing list