[v3,4/4] Add A2DP LDAC codec source support

Submitted by Huang-Huang Bao on Dec. 29, 2018, 3:48 p.m.

Details

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

Not browsing as part of any series.

Commit Message

Huang-Huang Bao Dec. 29, 2018, 3:48 p.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)
                       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

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 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;

Comments

On Saturday 29 December 2018 23:48:08 Huang-Huang Bao wrote:
> 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>

This looks like an another license problem. This Sony's LDAC encoder
library is licensed under Apache License Version 2.0 which is not
compatible with above LGPL 2.1.

IIRC Apache License Version 2.0 can be used in GPLv3 projects, so
without relicensing pulseaudio to (L)GPLv3 this looks like a problem.
My bad, noticed before, but I forgot it when adding copyrights.

On 1/13/19 10:14 AM, Pali Rohár wrote:
> On Saturday 29 December 2018 23:48:08 Huang-Huang Bao wrote:
>> 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>
> This looks like an another license problem. This Sony's LDAC encoder
> library is licensed under Apache License Version 2.0 which is not
> compatible with above LGPL 2.1.
>
> IIRC Apache License Version 2.0 can be used in GPLv3 projects, so
> without relicensing pulseaudio to (L)GPLv3 this looks like a problem.
>
Could we relicense bluez modules or AAC/LDAC staffs to LGPLv3?

>
> _______________________________________________
> pulseaudio-discuss mailing list
> pulseaudio-discuss@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
On Monday 14 January 2019 12:34:02 Huang-Huang Bao wrote:
> My bad, noticed before, but I forgot it when adding copyrights.
> 
> On 1/13/19 10:14 AM, Pali Rohár wrote:
> > On Saturday 29 December 2018 23:48:08 Huang-Huang Bao wrote:
> >> 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>
> > This looks like an another license problem. This Sony's LDAC encoder
> > library is licensed under Apache License Version 2.0 which is not
> > compatible with above LGPL 2.1.
> >
> > IIRC Apache License Version 2.0 can be used in GPLv3 projects, so
> > without relicensing pulseaudio to (L)GPLv3 this looks like a problem.
> >
> Could we relicense bluez modules or AAC/LDAC staffs to LGPLv3?

I guess this is possible only in case whole pulseaudio server is
relicensed to GPLv3 as all those code is running in pulseaudio server.
And this would be problematic... :-(