[4/4] Add A2DP LDAC codec source support

Submitted by Huang-Huang Bao on Dec. 13, 2018, 11:43 a.m.

Details

Message ID 20181213114339.9651-5-eh5@sokka.cn
State Superseded
Headers show
Series "Adds A2DP AAC, aptX(HD), LDAC codecs" ( rev: 2 ) in PulseAudio

Not browsing as part of any series.

Commit Message

Huang-Huang Bao Dec. 13, 2018, 11:43 a.m.
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)
                       auto
---
 configure.ac                           |  16 +
 src/Makefile.am                        |   8 +
 src/modules/bluetooth/a2dp/a2dp-api.h  |   6 +
 src/modules/bluetooth/a2dp/a2dp_ldac.c | 591 +++++++++++++++++++++++++
 src/modules/bluetooth/a2dp/a2dp_util.c |  28 ++
 5 files changed, 649 insertions(+)
 create mode 100644 src/modules/bluetooth/a2dp/a2dp_ldac.c

Patch hide | download patch | download mbox

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 8fa7c79d5..096788d4b 100644
--- a/src/modules/bluetooth/a2dp/a2dp-api.h
+++ b/src/modules/bluetooth/a2dp/a2dp-api.h
@@ -25,6 +25,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 */
 
@@ -53,6 +56,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..322bee6d6
--- /dev/null
+++ b/src/modules/bluetooth/a2dp/a2dp_ldac.c
@@ -0,0 +1,591 @@ 
+
+#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)
+ *                     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.vendor_id = LDAC_VENDOR_ID;
+    capabilities->info.codec_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.vendor_id = LDAC_VENDOR_ID;
+    config->info.codec_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_set_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_vendor_codec_t) {
+                .vendor_id = LDAC_VENDOR_ID,
+                .codec_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,
+        .set_configuration = pa_ldac_set_configuration
+};
diff --git a/src/modules/bluetooth/a2dp/a2dp_util.c b/src/modules/bluetooth/a2dp/a2dp_util.c
index 68db66d81..d3edc0a41 100644
--- a/src/modules/bluetooth/a2dp/a2dp_util.c
+++ b/src/modules/bluetooth/a2dp/a2dp_util.c
@@ -24,6 +24,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
@@ -221,6 +222,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;
@@ -247,6 +253,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;
@@ -273,6 +283,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;
@@ -309,6 +324,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_codec->vendor_codec->vendor_id == LDAC_VENDOR_ID &&
+                a2dp_codec->vendor_codec->codec_id == 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;
@@ -341,6 +363,12 @@  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 (vendor_codec->vendor_id == LDAC_VENDOR_ID && vendor_codec->codec_id == LDAC_CODEC_ID) {
+                *a2dp_codec = &pa_a2dp_ldac;
+                return;
+            }
 #endif
             *a2dp_codec = NULL;
             break;