[3/4] Add A2DP aptX, aptX HD codecs support

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

Details

Message ID 20181213114339.9651-4-eh5@sokka.cn
State New
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.
Optional (build with --disable-bluez5-aptx-codec)
---
 configure.ac                             |  18 +-
 src/Makefile.am                          |   8 +
 src/modules/bluetooth/a2dp/a2dp-api.h    |  12 +
 src/modules/bluetooth/a2dp/a2dp-codecs.h |   3 +
 src/modules/bluetooth/a2dp/a2dp_aptx.c   | 609 +++++++++++++++++++++++
 src/modules/bluetooth/a2dp/a2dp_util.c   |  60 +++
 6 files changed, 709 insertions(+), 1 deletion(-)
 create mode 100644 src/modules/bluetooth/a2dp/a2dp_aptx.c

Patch hide | download patch | download mbox

diff --git a/configure.ac b/configure.ac
index 0f9d7fb6c..586077e5b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1061,7 +1061,7 @@  PA_MACHINE_ID_FALLBACK="${localstatedir}/lib/dbus/machine-id"
 AX_DEFINE_DIR(PA_MACHINE_ID_FALLBACK, PA_MACHINE_ID_FALLBACK,
 	      [Fallback machine-id file])
 
-#### BlueZ support (optional, dependent on D-Bus and SBC and FDK-AAC) ####
+#### BlueZ support (optional, dependent on D-Bus, SBC, FDK-AAC and FFmpeg) ####
 
 AC_ARG_ENABLE([bluez5],
     AS_HELP_STRING([--disable-bluez5],[Disable optional BlueZ 5 support]))
@@ -1093,6 +1093,20 @@  AC_SUBST(HAVE_FDK_AAC)
 AM_CONDITIONAL([HAVE_FDK_AAC], [test "x$HAVE_FDK_AAC" = x1])
 AS_IF([test "x$HAVE_FDK_AAC" = "x1"], AC_DEFINE([HAVE_FDK_AAC], 1, [Bluez 5 A2DP AAC codec enabled]))
 
+## aptX, aptX HD support (FFmpeg libavcodec) ##
+AC_ARG_ENABLE([bluez5-aptx-codec],
+    AS_HELP_STRING([--disable-bluez5-aptx-codec],[Disable optional A2DP aptX, aptX HD codecs support (Bluez 5)]))
+AS_IF([test "x$HAVE_BLUEZ_5" = "x1" && test "x$enable_bluez5_aptx_codec" != "xno"],
+    [PKG_CHECK_MODULES(FF_AVCODEC, [ libavcodec >= 58.18.100 ], HAVE_FF_AVCODEC=1, HAVE_FF_AVCODEC=0)],
+    HAVE_FF_AVCODEC=0)
+AS_IF([test "x$HAVE_BLUEZ_5" = "x1" && test "x$enable_bluez5_aptx_codec" != "xno"],
+    [PKG_CHECK_MODULES(FF_AVUTIL, [ libavcodec >= 56.14.100 ], HAVE_FF_AVUTIL=1, HAVE_FF_AVUTIL=0)],
+    HAVE_FF_AVUTIL=0)
+AS_IF([test "x$HAVE_FF_AVCODEC" = "x1" && test "x$HAVE_FF_AVUTIL" = "x1"], HAVE_FF_APTX=1, HAVE_FF_APTX=0)
+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]))
+
 ## Bluetooth Headset profiles backend ##
 
 AC_ARG_ENABLE([bluez5-ofono-headset],
@@ -1598,6 +1612,7 @@  AS_IF([test "x$HAVE_SYSTEMD_LOGIN" = "x1"], ENABLE_SYSTEMD_LOGIN=yes, ENABLE_SYS
 AS_IF([test "x$HAVE_SYSTEMD_JOURNAL" = "x1"], ENABLE_SYSTEMD_JOURNAL=yes, ENABLE_SYSTEMD_JOURNAL=no)
 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_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)
@@ -1657,6 +1672,7 @@  echo "
     Enable D-Bus:                  ${ENABLE_DBUS}
       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 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 c44a65f05..5bb43c3a6 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -2132,6 +2132,10 @@  if HAVE_FDK_AAC
 libbluez5_util_la_SOURCES += \
 		modules/bluetooth/a2dp/a2dp_aac.c
 endif
+if HAVE_FF_APTX
+libbluez5_util_la_SOURCES += \
+		modules/bluetooth/a2dp/a2dp_aptx.c
+endif
 if HAVE_BLUEZ_5_OFONO_HEADSET
 libbluez5_util_la_SOURCES += \
 		modules/bluetooth/backend-ofono.c
@@ -2148,6 +2152,10 @@  if HAVE_FDK_AAC
 libbluez5_util_la_LIBADD += $(FDK_AAC_LIBS)
 libbluez5_util_la_CFLAGS += $(FDK_AAC_CFLAGS)
 endif
+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
 
 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 73fa18377..8fa7c79d5 100644
--- a/src/modules/bluetooth/a2dp/a2dp-api.h
+++ b/src/modules/bluetooth/a2dp/a2dp-api.h
@@ -21,6 +21,10 @@  extern const pa_a2dp_codec_t pa_a2dp_sbc;
 #ifdef HAVE_FDK_AAC
 extern const pa_a2dp_codec_t pa_a2dp_aac;
 #endif
+#ifdef HAVE_FF_APTX
+extern const pa_a2dp_codec_t pa_a2dp_aptx;
+extern const pa_a2dp_codec_t pa_a2dp_aptx_hd;
+#endif
 
 /* Run from <pa_a2dp_sink_t>.encode */
 
@@ -35,12 +39,20 @@  typedef enum pa_a2dp_codec_index {
     PA_A2DP_SINK_SBC,
 #ifdef HAVE_FDK_AAC
     PA_A2DP_SINK_AAC,
+#endif
+#ifdef HAVE_FF_APTX
+    PA_A2DP_SINK_APTX,
+    PA_A2DP_SINK_APTX_HD,
 #endif
     PA_A2DP_SINK_MAX,
     PA_A2DP_SOURCE_MIN = PA_A2DP_SINK_MAX,
     PA_A2DP_SOURCE_SBC,
 #ifdef HAVE_FDK_AAC
     PA_A2DP_SOURCE_AAC,
+#endif
+#ifdef HAVE_FF_APTX
+    PA_A2DP_SOURCE_APTX,
+    PA_A2DP_SOURCE_APTX_HD,
 #endif
     PA_A2DP_SOURCE_MAX,
     PA_A2DP_CODEC_INDEX_UNAVAILABLE
diff --git a/src/modules/bluetooth/a2dp/a2dp-codecs.h b/src/modules/bluetooth/a2dp/a2dp-codecs.h
index aefafb635..a6f054aec 100644
--- a/src/modules/bluetooth/a2dp/a2dp-codecs.h
+++ b/src/modules/bluetooth/a2dp/a2dp-codecs.h
@@ -141,6 +141,9 @@ 
 #define APTX_SAMPLING_FREQ_44100	0x02
 #define APTX_SAMPLING_FREQ_48000	0x01
 
+#define APTX_HD_VENDOR_ID			0x000000D7
+#define APTX_HD_CODEC_ID			0x0024
+
 #define LDAC_VENDOR_ID			0x0000012d
 #define LDAC_CODEC_ID			0x00aa
 
diff --git a/src/modules/bluetooth/a2dp/a2dp_aptx.c b/src/modules/bluetooth/a2dp/a2dp_aptx.c
new file mode 100644
index 000000000..a6e65d04a
--- /dev/null
+++ b/src/modules/bluetooth/a2dp/a2dp_aptx.c
@@ -0,0 +1,609 @@ 
+
+#include <arpa/inet.h>
+#include <string.h>
+
+#include <libavcodec/avcodec.h>
+#include <libavutil/samplefmt.h>
+
+#include <pulse/xmalloc.h>
+
+#include "a2dp-api.h"
+
+
+#define streq(a, b) (!strcmp((a),(b)))
+
+
+typedef struct aptx_info {
+    pa_a2dp_source_read_cb_t read_pcm;
+    pa_a2dp_source_read_buf_free_cb_t read_buf_free;
+
+    bool is_a2dp_sink;
+    bool is_hd;
+
+    size_t aptx_frame_size;
+    int nb_samples;
+    const AVCodec *av_codec;
+    AVCodecContext *av_codec_ctx;
+    int channel_mode;
+    uint16_t seq_num;
+
+
+    size_t block_size;
+
+
+} aptx_info_t;
+
+static const AVCodec *av_codec_aptx_decoder = NULL;
+static const AVCodec *av_codec_aptx_encoder = NULL;
+
+static const AVCodec *av_codec_aptx_hd_decoder = NULL;
+static const AVCodec *av_codec_aptx_hd_encoder = NULL;
+
+
+static bool pa_aptx_decoder_load() {
+    if (av_codec_aptx_decoder)
+        return true;
+    av_codec_aptx_decoder = avcodec_find_decoder(AV_CODEC_ID_APTX);
+    if (!av_codec_aptx_decoder) {
+        pa_log_debug("Cannot find aptX decoder in FFmpeg avcodec library");
+        return false;
+    }
+
+    return true;
+}
+
+static bool pa_aptx_encoder_load() {
+    if (av_codec_aptx_encoder)
+        return true;
+    av_codec_aptx_encoder = avcodec_find_encoder(AV_CODEC_ID_APTX);
+    if (!av_codec_aptx_encoder) {
+        pa_log_debug("Cannot find aptX encoder in FFmpeg avcodec library");
+        return false;
+    }
+
+    return true;
+}
+
+static bool pa_aptx_hd_decoder_load() {
+    if (av_codec_aptx_hd_decoder)
+        return true;
+    av_codec_aptx_hd_decoder = avcodec_find_decoder(AV_CODEC_ID_APTX_HD);
+    if (!av_codec_aptx_hd_decoder) {
+        pa_log_debug("Cannot find aptX HD decoder in FFmpeg avcodec library");
+        return false;
+    }
+
+    return true;
+}
+
+static bool pa_aptx_hd_encoder_load() {
+    if (av_codec_aptx_hd_encoder)
+        return true;
+    av_codec_aptx_hd_encoder = avcodec_find_encoder(AV_CODEC_ID_APTX_HD);
+    if (!av_codec_aptx_hd_encoder) {
+        pa_log_debug("Cannot find aptX HD encoder in FFmpeg avcodec library");
+        return false;
+    }
+
+    return true;
+}
+
+static bool _internal_pa_dual_decoder_init(bool is_hd, void **codec_data) {
+    aptx_info_t *info = pa_xmalloc0(sizeof(aptx_info_t));
+    *codec_data = info;
+    info->is_hd = is_hd;
+    info->is_a2dp_sink = true;
+    info->aptx_frame_size = is_hd ? 6 : 4;
+    info->av_codec = is_hd ? av_codec_aptx_hd_decoder : av_codec_aptx_decoder;
+    return true;
+}
+
+static bool
+_internal_pa_dual_encoder_init(bool is_hd, pa_a2dp_source_read_cb_t read_cb, pa_a2dp_source_read_buf_free_cb_t free_cb,
+                               void **codec_data) {
+    aptx_info_t *info = pa_xmalloc0(sizeof(aptx_info_t));
+    *codec_data = info;
+    info->is_hd = is_hd;
+    info->is_a2dp_sink = false;
+    info->read_pcm = read_cb;
+    info->read_buf_free = free_cb;
+    info->aptx_frame_size = is_hd ? 6 : 4;
+    info->av_codec = is_hd ? av_codec_aptx_hd_encoder : av_codec_aptx_encoder;
+    return true;
+}
+
+static int pa_dual_update_user_config(pa_proplist *user_config, void **codec_data) {
+    return 0;
+}
+
+static size_t
+pa_dual_decode(const void *read_buf, size_t read_buf_size, void *write_buf, size_t write_buf_size, size_t *_decoded,
+               uint32_t *timestamp, void **codec_data) {
+    const struct rtp_header *header;
+    void *p;
+    int ret;
+    size_t i;
+    AVPacket *pkt;
+    size_t to_decode;
+    size_t total_written = 0;
+    AVFrame *av_frame = NULL;
+    aptx_info_t *aptx_info = *codec_data;
+
+
+    pa_assert(aptx_info);
+    pa_assert(aptx_info->av_codec);
+    pa_assert(aptx_info->av_codec_ctx);
+
+    if (aptx_info->is_hd) {
+        header = read_buf;
+
+        *timestamp = ntohl(header->timestamp);
+
+        p = (uint8_t *) read_buf + sizeof(*header);
+        to_decode = read_buf_size - sizeof(*header);
+    } else {
+        *timestamp = (uint32_t) - 1;
+
+        p = (uint8_t *) read_buf;
+        to_decode = read_buf_size;
+    }
+
+    av_frame = av_frame_alloc();
+    pkt = av_packet_alloc();
+    pkt->data = p;
+    pkt->size = (int) to_decode;
+
+
+    *_decoded = 0;
+
+    ret = avcodec_send_packet(aptx_info->av_codec_ctx, pkt);
+    if (PA_UNLIKELY(ret < 0)) {
+        pa_log_debug("Error submitting the packet to the aptX decoder");
+        goto done;
+    }
+    ret = avcodec_receive_frame(aptx_info->av_codec_ctx, av_frame);
+    if (PA_UNLIKELY(ret < 0)) {
+        pa_log_debug("Error during aptX decoding");
+        goto done;
+    }
+
+    *_decoded = aptx_info->aptx_frame_size * av_frame->nb_samples / 4;
+
+    total_written = (size_t) av_frame->nb_samples * (4 * 2);
+
+    pa_assert_fp(_decoded <= read_buf_size);
+    pa_assert_fp(total_written <= write_buf_size);
+
+    for (i = 0; i < av_frame->nb_samples * sizeof(uint32_t); i += sizeof(uint32_t)) {
+        memcpy((uint8_t *) write_buf + i * 2, av_frame->data[0] + i, sizeof(uint32_t));
+        memcpy((uint8_t *) write_buf + i * 2 + sizeof(uint32_t), av_frame->data[1] + i, sizeof(uint32_t));
+    }
+
+done:
+    av_frame_free(&av_frame);
+    av_packet_free(&pkt);
+    return total_written;
+}
+
+static size_t
+pa_dual_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;
+    size_t nbytes;
+    void *d;
+    const void *p;
+    AVFrame *av_frame;
+    AVPacket *pkt;
+    aptx_info_t *aptx_info = *codec_data;
+    int ret;
+    size_t i;
+
+    pa_assert(aptx_info);
+    pa_assert(aptx_info->av_codec);
+    pa_assert(aptx_info->av_codec_ctx);
+
+    pa_assert_fp(aptx_info->av_codec_ctx->frame_size <= write_buf_size);
+
+    aptx_info->read_pcm(&p, (size_t) aptx_info->block_size, read_cb_data);
+
+    if (aptx_info->is_hd) {
+        header = write_buf;
+        memset(write_buf, 0, sizeof(*header));
+        header->v = 2;
+        header->pt = 1;
+        header->sequence_number = htons(aptx_info->seq_num++);
+        header->timestamp = htonl(timestamp);
+        header->ssrc = htonl(1);
+        d = (uint8_t *) write_buf + sizeof(*header);
+    } else {
+        d = (uint8_t *) write_buf;
+    }
+
+    av_frame = av_frame_alloc();
+    av_frame->nb_samples = aptx_info->nb_samples;
+    av_frame->format = aptx_info->av_codec_ctx->sample_fmt;
+    av_frame->channel_layout = aptx_info->av_codec_ctx->channel_layout;
+
+    pkt = av_packet_alloc();
+
+    pa_assert_se(av_frame_get_buffer(av_frame, 0) >= 0);
+    pa_assert_se(av_frame_make_writable(av_frame) >= 0);
+
+
+    for (i = 0; i < av_frame->nb_samples * sizeof(uint32_t); i += sizeof(uint32_t)) {
+        memcpy(av_frame->data[0] + i, (uint8_t *) p + i * 2, sizeof(uint32_t));
+        memcpy(av_frame->data[1] + i, (uint8_t *) p + i * 2 + sizeof(uint32_t), sizeof(uint32_t));
+    }
+    *_encoded = 0;
+
+    ret = avcodec_send_frame(aptx_info->av_codec_ctx, av_frame);
+
+    if (PA_UNLIKELY(ret < 0)) {
+        fprintf(stderr, "Error sending the frame to the aptX encoder\n");
+        nbytes = 0;
+        goto done;
+    }
+
+    ret = avcodec_receive_packet(aptx_info->av_codec_ctx, pkt);
+
+    if (PA_UNLIKELY(ret != 0)) {
+        fprintf(stderr, "Error receiving the packet from the aptX encoder\n");
+        nbytes = 0;
+        goto done;
+    }
+
+    memcpy(d, pkt->data, (size_t) pkt->size);
+    d = (uint8_t *) d + pkt->size;
+
+    nbytes = (uint8_t *) d - (uint8_t *) write_buf;
+    *_encoded += aptx_info->block_size;
+
+done:
+    av_frame_free(&av_frame);
+    av_packet_free(&pkt);
+    aptx_info->read_buf_free(&p, read_cb_data);
+    return nbytes;
+}
+
+static void
+pa_dual_config_transport(pa_sample_spec default_sample_spec, const void *configuration, size_t configuration_size,
+                         pa_sample_spec *sample_spec, void **codec_data) {
+    aptx_info_t *aptx_info = *codec_data;
+    a2dp_aptx_t *config = (a2dp_aptx_t *) configuration;
+    AVCodecContext *aptx_ctx;
+    pa_assert(aptx_info);
+    pa_assert(aptx_info->av_codec);
+    pa_assert_se(configuration_size == (aptx_info->is_hd ? sizeof(a2dp_aptxhd_t) : sizeof(a2dp_aptx_t)));
+
+    if (aptx_info->av_codec_ctx)
+        avcodec_free_context(&aptx_info->av_codec_ctx);
+
+    aptx_info->av_codec_ctx = avcodec_alloc_context3(aptx_info->av_codec);
+    aptx_ctx = aptx_info->av_codec_ctx;
+
+    aptx_ctx->sample_fmt = AV_SAMPLE_FMT_S32P;
+    sample_spec->format = PA_SAMPLE_S32LE;
+
+    switch (config->frequency) {
+        case APTX_SAMPLING_FREQ_16000:
+            aptx_ctx->sample_rate = 16000;
+            aptx_ctx->bit_rate = 16000;
+            sample_spec->rate = 16000U;
+            break;
+        case APTX_SAMPLING_FREQ_32000:
+            aptx_ctx->sample_rate = 32000;
+            aptx_ctx->bit_rate = 32000;
+            sample_spec->rate = 32000U;
+            break;
+        case APTX_SAMPLING_FREQ_44100:
+            aptx_ctx->sample_rate = 44100;
+            aptx_ctx->bit_rate = 44100;
+            sample_spec->rate = 44100U;
+            break;
+        case APTX_SAMPLING_FREQ_48000:
+            aptx_ctx->sample_rate = 48000;
+            aptx_ctx->bit_rate = 48000;
+            sample_spec->rate = 48000U;
+            break;
+        default:
+            pa_assert_not_reached();
+    }
+
+    switch (config->channel_mode) {
+        case APTX_CHANNEL_MODE_STEREO:
+            aptx_ctx->channel_layout = AV_CH_LAYOUT_STEREO;
+            aptx_ctx->channels = 2;
+            sample_spec->channels = 2;
+            break;
+        default:
+            pa_assert_not_reached();
+    }
+
+    pa_assert_se(avcodec_open2(aptx_info->av_codec_ctx, aptx_info->av_codec, NULL) == 0);
+
+};
+
+static void pa_dual_get_read_block_size(size_t read_link_mtu, size_t *read_block_size, void **codec_data) {
+    aptx_info_t *aptx_info = *codec_data;
+    size_t aptx_frame_size = aptx_info->aptx_frame_size;
+    size_t rtp_use_size = aptx_info->is_hd ? sizeof(struct rtp_header) : 0;
+    pa_assert(aptx_info);
+
+    /*
+     * PCM 32-bit, 2 channel (4 bytes * 2)
+     * PCM frames/APTX frames == 4
+     * */
+    *read_block_size = (read_link_mtu - rtp_use_size) / aptx_frame_size * (4 * 2) * 4;
+    aptx_info->block_size = *read_block_size;
+};
+
+static void pa_dual_get_write_block_size(size_t write_link_mtu, size_t *write_block_size, void **codec_data) {
+    aptx_info_t *aptx_info = *codec_data;
+    size_t aptx_frame_size = aptx_info->aptx_frame_size;
+    size_t rtp_use_size = aptx_info->is_hd ? sizeof(struct rtp_header) : 0;
+    pa_assert(aptx_info);
+
+    /*
+     * PCM 32-bit, 2 channel (4 bytes * 2)
+     * PCM frames/APTX frames == 4
+     * */
+    *write_block_size = (write_link_mtu - rtp_use_size) / aptx_frame_size * (4 * 2) * 4;
+    aptx_info->block_size = *write_block_size;
+};
+
+
+static void pa_dual_setup_stream(void **codec_data) {
+    aptx_info_t *aptx_info = *codec_data;
+    pa_assert(aptx_info);
+    aptx_info->nb_samples = (int) (aptx_info->block_size / (4 * 2));
+    aptx_info->av_codec_ctx->frame_size = (int) (aptx_info->aptx_frame_size * aptx_info->nb_samples / 4);
+};
+
+static void pa_dual_free(void **codec_data) {
+    aptx_info_t *aptx_info = *codec_data;
+    if (!aptx_info)
+        return;
+    if (aptx_info->av_codec_ctx)
+        avcodec_free_context(&aptx_info->av_codec_ctx);
+    pa_xfree(aptx_info);
+    *codec_data = NULL;
+
+};
+
+static size_t _internal_pa_dual_get_capabilities(bool is_hd, void **_capabilities) {
+
+    const size_t cap_size = is_hd ? sizeof(a2dp_aptxhd_t) : sizeof(a2dp_aptx_t);
+    a2dp_aptx_t *capabilities = (a2dp_aptx_t *) pa_xmalloc0(cap_size);
+
+    if (is_hd) {
+        capabilities->info.vendor_id = APTX_HD_VENDOR_ID;
+        capabilities->info.codec_id = APTX_HD_CODEC_ID;
+    } else {
+        capabilities->info.vendor_id = APTX_VENDOR_ID;
+        capabilities->info.codec_id = APTX_CODEC_ID;
+    }
+
+    capabilities->channel_mode = APTX_CHANNEL_MODE_STEREO;
+    capabilities->frequency = APTX_SAMPLING_FREQ_16000 | APTX_SAMPLING_FREQ_32000 | APTX_SAMPLING_FREQ_44100 |
+                              APTX_SAMPLING_FREQ_48000;
+    *_capabilities = capabilities;
+
+    return cap_size;
+};
+
+
+static size_t
+_internal_pa_dual_select_configuration(bool is_hd, const pa_sample_spec default_sample_spec,
+                                       const uint8_t *supported_capabilities,
+                                       const size_t capabilities_size, void **configuration) {
+    a2dp_aptx_t *cap;
+    a2dp_aptx_t *config;
+    const size_t cap_size = is_hd ? sizeof(a2dp_aptxhd_t) : sizeof(a2dp_aptx_t);
+    pa_a2dp_freq_cap_t aptx_freq_cap, aptx_freq_table[] = {
+            {16000U, APTX_SAMPLING_FREQ_16000},
+            {32000U, APTX_SAMPLING_FREQ_32000},
+            {44100U, APTX_SAMPLING_FREQ_44100},
+            {48000U, APTX_SAMPLING_FREQ_48000}
+    };
+
+    cap = (a2dp_aptx_t *) supported_capabilities;
+
+    if (capabilities_size != cap_size)
+        return 0;
+
+    config = (a2dp_aptx_t *) pa_xmalloc0(cap_size);
+
+    if (is_hd) {
+        config->info.vendor_id = APTX_HD_VENDOR_ID;
+        config->info.codec_id = APTX_HD_CODEC_ID;
+    } else {
+        config->info.vendor_id = APTX_VENDOR_ID;
+        config->info.codec_id = APTX_CODEC_ID;
+    }
+
+    if (!pa_a2dp_select_cap_frequency(cap->frequency, default_sample_spec, aptx_freq_table,
+                                      PA_ELEMENTSOF(aptx_freq_table), &aptx_freq_cap))
+        return 0;
+
+    config->frequency = (uint8_t) aptx_freq_cap.cap;
+
+    if (cap->channel_mode & APTX_CHANNEL_MODE_STEREO)
+        config->channel_mode = APTX_CHANNEL_MODE_STEREO;
+    else {
+        pa_log_error("No supported channel modes");
+        return 0;
+    }
+
+    *configuration = config;
+    return cap_size;
+};
+
+static void pa_dual_free_capabilities(void **capabilities) {
+    if (!capabilities || !*capabilities)
+        return;
+    pa_xfree(*capabilities);
+    *capabilities = NULL;
+}
+
+static bool _internal_pa_dual_set_configuration(bool is_hd, const uint8_t *selected_configuration,
+                                                const size_t configuration_size) {
+    a2dp_aptx_t *c = (a2dp_aptx_t *) selected_configuration;
+
+    if (configuration_size != (is_hd ? sizeof(a2dp_aptxhd_t) : sizeof(a2dp_aptx_t))) {
+        pa_log_error("APTX configuration array of invalid size");
+        return false;
+    }
+
+    switch (c->frequency) {
+        case APTX_SAMPLING_FREQ_16000:
+        case APTX_SAMPLING_FREQ_32000:
+        case APTX_SAMPLING_FREQ_44100:
+        case APTX_SAMPLING_FREQ_48000:
+            break;
+        default:
+            pa_log_error("Invalid sampling frequency in aptX configuration");
+            return false;
+    }
+
+    switch (c->channel_mode) {
+        case APTX_CHANNEL_MODE_STEREO:
+            break;
+        default:
+            pa_log_error("Invalid channel mode in aptX Configuration");
+            return false;
+    }
+    return true;
+};
+
+
+static bool pa_aptx_decoder_init(void **codec_data) {
+    return _internal_pa_dual_decoder_init(false, codec_data);
+}
+
+static bool pa_aptx_hd_decoder_init(void **codec_data) {
+    return _internal_pa_dual_decoder_init(true, codec_data);
+}
+
+static bool
+pa_aptx_encoder_init(pa_a2dp_source_read_cb_t read_cb, pa_a2dp_source_read_buf_free_cb_t free_cb, void **codec_data) {
+    return _internal_pa_dual_encoder_init(false, read_cb, free_cb, codec_data);
+}
+
+static bool
+pa_aptx_hd_encoder_init(pa_a2dp_source_read_cb_t read_cb, pa_a2dp_source_read_buf_free_cb_t free_cb,
+                        void **codec_data) {
+    return _internal_pa_dual_encoder_init(true, read_cb, free_cb, codec_data);
+}
+
+
+static size_t pa_aptx_get_capabilities(void **_capabilities) {
+    return _internal_pa_dual_get_capabilities(false, _capabilities);
+}
+
+static size_t pa_aptx_select_configuration(const pa_sample_spec default_sample_spec,
+                                           const uint8_t *supported_capabilities,
+                                           const size_t capabilities_size, void **configuration) {
+    return _internal_pa_dual_select_configuration(false, default_sample_spec, supported_capabilities, capabilities_size,
+                                                  configuration);
+}
+
+static bool pa_aptx_set_configuration(const uint8_t *selected_configuration,
+                                      const size_t configuration_size) {
+    return _internal_pa_dual_set_configuration(false, selected_configuration, configuration_size);
+}
+
+static size_t pa_aptx_hd_get_capabilities(void **_capabilities) {
+    return _internal_pa_dual_get_capabilities(true, _capabilities);
+}
+
+static size_t pa_aptx_hd_select_configuration(const pa_sample_spec default_sample_spec,
+                                              const uint8_t *supported_capabilities,
+                                              const size_t capabilities_size, void **configuration) {
+    return _internal_pa_dual_select_configuration(true, default_sample_spec, supported_capabilities, capabilities_size,
+                                                  configuration);
+}
+
+static bool pa_aptx_hd_set_configuration(const uint8_t *selected_configuration,
+                                         const size_t configuration_size) {
+    return _internal_pa_dual_set_configuration(true, selected_configuration, configuration_size);
+}
+
+static pa_a2dp_source_t pa_aptx_source = {
+        .encoder_load = pa_aptx_encoder_load,
+        .init = pa_aptx_encoder_init,
+        .update_user_config = pa_dual_update_user_config,
+        .encode = pa_dual_encode,
+        .config_transport = pa_dual_config_transport,
+        .get_block_size = pa_dual_get_write_block_size,
+        .setup_stream = pa_dual_setup_stream,
+        .set_tx_length = NULL,
+        .decrease_quality = NULL,
+        .free = pa_dual_free
+};
+
+static pa_a2dp_sink_t pa_aptx_sink = {
+        .decoder_load = pa_aptx_decoder_load,
+        .init = pa_aptx_decoder_init,
+        .update_user_config = pa_dual_update_user_config,
+        .config_transport = pa_dual_config_transport,
+        .get_block_size = pa_dual_get_read_block_size,
+        .setup_stream = pa_dual_setup_stream,
+        .decode = pa_dual_decode,
+        .free = pa_dual_free
+};
+
+const pa_a2dp_codec_t pa_a2dp_aptx = {
+        .name = "aptX",
+        .codec = A2DP_CODEC_VENDOR,
+        .vendor_codec = &((a2dp_vendor_codec_t) {
+                .vendor_id = APTX_VENDOR_ID,
+                .codec_id = APTX_CODEC_ID
+        }),
+        .a2dp_sink = &pa_aptx_sink,
+        .a2dp_source = &pa_aptx_source,
+        .get_capabilities = pa_aptx_get_capabilities,
+        .select_configuration = pa_aptx_select_configuration,
+        .free_capabilities = pa_dual_free_capabilities,
+        .free_configuration = pa_dual_free_capabilities,
+        .set_configuration = pa_aptx_set_configuration
+};
+
+static pa_a2dp_source_t pa_aptx_hd_source = {
+        .encoder_load = pa_aptx_hd_encoder_load,
+        .init = pa_aptx_hd_encoder_init,
+        .update_user_config = pa_dual_update_user_config,
+        .encode = pa_dual_encode,
+        .config_transport = pa_dual_config_transport,
+        .get_block_size = pa_dual_get_write_block_size,
+        .setup_stream = pa_dual_setup_stream,
+        .set_tx_length = NULL,
+        .decrease_quality = NULL,
+        .free = pa_dual_free
+};
+
+static pa_a2dp_sink_t pa_aptx_hd_sink = {
+        .decoder_load = pa_aptx_hd_decoder_load,
+        .init = pa_aptx_hd_decoder_init,
+        .update_user_config = pa_dual_update_user_config,
+        .config_transport = pa_dual_config_transport,
+        .get_block_size = pa_dual_get_read_block_size,
+        .setup_stream = pa_dual_setup_stream,
+        .decode = pa_dual_decode,
+        .free = pa_dual_free
+};
+
+const pa_a2dp_codec_t pa_a2dp_aptx_hd = {
+        .name = "aptX_HD",
+        .codec = A2DP_CODEC_VENDOR,
+        .vendor_codec = &((a2dp_vendor_codec_t) {
+                .vendor_id = APTX_HD_VENDOR_ID,
+                .codec_id = APTX_HD_CODEC_ID
+        }),
+        .a2dp_sink = &pa_aptx_hd_sink,
+        .a2dp_source = &pa_aptx_hd_source,
+        .get_capabilities = pa_aptx_hd_get_capabilities,
+        .select_configuration = pa_aptx_hd_select_configuration,
+        .free_capabilities = pa_dual_free_capabilities,
+        .free_configuration = pa_dual_free_capabilities,
+        .set_configuration = pa_aptx_hd_set_configuration
+};
diff --git a/src/modules/bluetooth/a2dp/a2dp_util.c b/src/modules/bluetooth/a2dp/a2dp_util.c
index 297dcd940..68db66d81 100644
--- a/src/modules/bluetooth/a2dp/a2dp_util.c
+++ b/src/modules/bluetooth/a2dp/a2dp_util.c
@@ -18,6 +18,12 @@ 
 #define A2DP_VENDOR_SRC_ENDPOINT A2DP_SOURCE_ENDPOINT "/VENDOR"
 #define A2DP_VENDOR_SNK_ENDPOINT A2DP_SINK_ENDPOINT "/VENDOR"
 
+#define A2DP_APTX_SRC_ENDPOINT A2DP_VENDOR_SRC_ENDPOINT "/APTX"
+#define A2DP_APTX_SNK_ENDPOINT A2DP_VENDOR_SNK_ENDPOINT "/APTX"
+
+#define A2DP_APTX_HD_SRC_ENDPOINT A2DP_VENDOR_SRC_ENDPOINT "/APTXHD"
+#define A2DP_APTX_HD_SNK_ENDPOINT A2DP_VENDOR_SNK_ENDPOINT "/APTXHD"
+
 
 #define PA_A2DP_PRIORITY_DISABLE 0
 #define PA_A2DP_PRIORITY_MIN 1
@@ -201,6 +207,20 @@  void pa_a2dp_codec_index_to_endpoint(pa_a2dp_codec_index_t codec_index, const ch
         case PA_A2DP_SOURCE_AAC:
             *endpoint = A2DP_AAC_SRC_ENDPOINT;
             break;
+#endif
+#ifdef HAVE_FF_APTX
+        case PA_A2DP_SINK_APTX:
+            *endpoint = A2DP_APTX_SNK_ENDPOINT;
+            break;
+        case PA_A2DP_SOURCE_APTX:
+            *endpoint = A2DP_APTX_SRC_ENDPOINT;
+            break;
+        case PA_A2DP_SINK_APTX_HD:
+            *endpoint = A2DP_APTX_HD_SNK_ENDPOINT;
+            break;
+        case PA_A2DP_SOURCE_APTX_HD:
+            *endpoint = A2DP_APTX_HD_SRC_ENDPOINT;
+            break;
 #endif
         default:
             *endpoint = NULL;
@@ -217,6 +237,16 @@  void pa_a2dp_endpoint_to_codec_index(const char *endpoint, pa_a2dp_codec_index_t
         *codec_index = PA_A2DP_SINK_AAC;
     else if (streq(endpoint, A2DP_AAC_SRC_ENDPOINT))
         *codec_index = PA_A2DP_SOURCE_AAC;
+#endif
+#ifdef HAVE_FF_APTX
+    else if (streq(endpoint, A2DP_APTX_SNK_ENDPOINT))
+        *codec_index = PA_A2DP_SINK_APTX;
+    else if (streq(endpoint, A2DP_APTX_SRC_ENDPOINT))
+        *codec_index = PA_A2DP_SOURCE_APTX;
+    else if (streq(endpoint, A2DP_APTX_HD_SNK_ENDPOINT))
+        *codec_index = PA_A2DP_SINK_APTX_HD;
+    else if (streq(endpoint, A2DP_APTX_HD_SRC_ENDPOINT))
+        *codec_index = PA_A2DP_SOURCE_APTX_HD;
 #endif
     else
         *codec_index = PA_A2DP_CODEC_INDEX_UNAVAILABLE;
@@ -233,6 +263,16 @@  void pa_a2dp_codec_index_to_a2dp_codec(pa_a2dp_codec_index_t codec_index, const
         case PA_A2DP_SOURCE_AAC:
             *a2dp_codec = &pa_a2dp_aac;
             break;
+#endif
+#ifdef HAVE_FF_APTX
+        case PA_A2DP_SINK_APTX:
+        case PA_A2DP_SOURCE_APTX:
+            *a2dp_codec = &pa_a2dp_aptx;
+            break;
+        case PA_A2DP_SINK_APTX_HD:
+        case PA_A2DP_SOURCE_APTX_HD:
+            *a2dp_codec = &pa_a2dp_aptx_hd;
+            break;
 #endif
         default:
             *a2dp_codec = NULL;
@@ -259,6 +299,17 @@  void pa_a2dp_a2dp_codec_to_codec_index(const pa_a2dp_codec_t *a2dp_codec, bool i
                 *codec_index = PA_A2DP_CODEC_INDEX_UNAVAILABLE;
                 return;
             }
+#ifdef HAVE_FF_APTX
+            else if (a2dp_codec->vendor_codec->vendor_id == APTX_VENDOR_ID &&
+                      a2dp_codec->vendor_codec->codec_id == APTX_CODEC_ID) {
+                *codec_index = is_a2dp_sink ? PA_A2DP_SINK_APTX : PA_A2DP_SOURCE_APTX;
+                return;
+            } else if (a2dp_codec->vendor_codec->vendor_id == APTX_HD_VENDOR_ID &&
+                       a2dp_codec->vendor_codec->codec_id == APTX_HD_CODEC_ID) {
+                *codec_index = is_a2dp_sink ? PA_A2DP_SINK_APTX_HD : PA_A2DP_SOURCE_APTX_HD;
+                return;
+            }
+#endif
             *codec_index = PA_A2DP_CODEC_INDEX_UNAVAILABLE;
             break;
         default:
@@ -282,6 +333,15 @@  pa_a2dp_get_a2dp_codec(uint8_t codec, const a2dp_vendor_codec_t *vendor_codec, c
                 *a2dp_codec = NULL;
                 pa_assert_not_reached();
             }
+#ifdef HAVE_FF_APTX
+            else if (vendor_codec->vendor_id == APTX_VENDOR_ID && vendor_codec->codec_id == APTX_CODEC_ID) {
+                *a2dp_codec = &pa_a2dp_aptx;
+                return;
+            } else if (vendor_codec->vendor_id == APTX_HD_VENDOR_ID && vendor_codec->codec_id == APTX_HD_CODEC_ID) {
+                *a2dp_codec = &pa_a2dp_aptx_hd;
+                return;
+            }
+#endif
             *a2dp_codec = NULL;
             break;
         default:

Comments

What is the purpose of adding aptX codec (non HD variant)? It is not
better then default and mandatory SBC codec.
On Thursday 13 December 2018 19:43:38 EHfive wrote:
> diff --git a/src/modules/bluetooth/a2dp/a2dp-codecs.h b/src/modules/bluetooth/a2dp/a2dp-codecs.h
> index aefafb635..a6f054aec 100644
> --- a/src/modules/bluetooth/a2dp/a2dp-codecs.h
> +++ b/src/modules/bluetooth/a2dp/a2dp-codecs.h
> @@ -141,6 +141,9 @@
>  #define APTX_SAMPLING_FREQ_44100	0x02
>  #define APTX_SAMPLING_FREQ_48000	0x01
>  
> +#define APTX_HD_VENDOR_ID			0x000000D7

This ID is assigned to Qualcomm Technologies, Inc. Not for aptX HD
codec, or aptX HD vendor.

> +#define APTX_HD_CODEC_ID			0x0024
> +
>  #define LDAC_VENDOR_ID			0x0000012d
>  #define LDAC_CODEC_ID			0x00aa
>  

I wrote alternative implementation of aptX codec support for pulseaudio.
It does not use big ffmpeg, but rather small libopenaptx library from
https://github.com/pali/libopenaptx Patches are already in mailing list...

Anyway, aptX decoder (both hd and non-hd variants) in ffmpeg has delay
of about 90 samples. I have already some idea how to fix... and will try
to implement it in libopenaptx. (Recompiling ffmpeg just for testing and
debugging one codec is big pain; small library is better for this).
People want using aptx(HD) early.

So I decide to use ffmpeg.

90 samples are only 90/44100 s = 2ms , which is ignorable.


On 12/13/18 11:43 PM, Pali Rohár wrote:
> On Thursday 13 December 2018 19:43:38 EHfive wrote:
>> diff --git a/src/modules/bluetooth/a2dp/a2dp-codecs.h b/src/modules/bluetooth/a2dp/a2dp-codecs.h
>> index aefafb635..a6f054aec 100644
>> --- a/src/modules/bluetooth/a2dp/a2dp-codecs.h
>> +++ b/src/modules/bluetooth/a2dp/a2dp-codecs.h
>> @@ -141,6 +141,9 @@
>>  #define APTX_SAMPLING_FREQ_44100	0x02
>>  #define APTX_SAMPLING_FREQ_48000	0x01
>>  
>> +#define APTX_HD_VENDOR_ID			0x000000D7
> This ID is assigned to Qualcomm Technologies, Inc. Not for aptX HD
> codec, or aptX HD vendor.
>
>> +#define APTX_HD_CODEC_ID			0x0024
>> +
>>  #define LDAC_VENDOR_ID			0x0000012d
>>  #define LDAC_CODEC_ID			0x00aa
>>  
> I wrote alternative implementation of aptX codec support for pulseaudio.
> It does not use big ffmpeg, but rather small libopenaptx library from
> https://github.com/pali/libopenaptx Patches are already in mailing list...
>
> Anyway, aptX decoder (both hd and non-hd variants) in ffmpeg has delay
> of about 90 samples. I have already some idea how to fix... and will try
> to implement it in libopenaptx. (Recompiling ffmpeg just for testing and
> debugging one codec is big pain; small library is better for this).
>
It's just a ability.

If we want to disable aptx or adjust it's priority, just modify enum
pa_a2dp_codec_index or using pa_a2dp_set_max_priority(),
pa_a2dp_set_disable().

On 12/13/18 11:36 PM, Pali Rohár wrote:
> What is the purpose of adding aptX codec (non HD variant)? It is not
> better then default and mandatory SBC codec.
>
On Thu, Dec 13, 2018 at 04:36:59PM +0100, Pali Rohár wrote:
> What is the purpose of adding aptX codec (non HD variant)? It is not
> better then default and mandatory SBC codec.
>

When using AptX codec one can be certain it's always 352 kbps bitrate.
with SBC you never know what you end up using..

So it's good to have normal (non-HD) AptX supported aswell.
 
> -- 
> Pali Rohár
> pali.rohar@gmail.com


-- Pasi
On Thursday 13 December 2018 18:42:11 Pasi Kärkkäinen wrote:
> On Thu, Dec 13, 2018 at 04:36:59PM +0100, Pali Rohár wrote:
> > What is the purpose of adding aptX codec (non HD variant)? It is not
> > better then default and mandatory SBC codec.
> >
> 
> When using AptX codec one can be certain it's always 352 kbps bitrate.
> with SBC you never know what you end up using..

This is just because of current implementation of SBC in pulseaudio. Why
not rather invest time to properly support SBC at high quality or allow
user to set quality / show user current quality? SBC is mandatory and
supported by all devices, aptX only by some (plus codec is proprietary).

Adding aptX codec just because SBC implementation in pulseaudio is not
so "nice" is not a good argument.

> So it's good to have normal (non-HD) AptX supported aswell.
>  
> > -- 
> > Pali Rohár
> > pali.rohar@gmail.com
> 
> 
> -- Pasi
> 
> _______________________________________________
> pulseaudio-discuss mailing list
> pulseaudio-discuss@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
On Friday 14 December 2018 00:16:24 Huang-Huang Bao wrote:
> 90 samples are only 90/44100 s = 2ms , which is ignorable.

Yes, this not a problem. Problem is that first 90 samples is just
"garbage" or rather initialization data for predictor.

And to prevent hearing such "garbage" when decoding stream, you need to
drop them. Put aptX stream into aptX decoder, but drop first 90 samples
of decoded output.

Hearing noise or any other disrupting sound also for just 2ms is not
something which you want to hear.

This also applies when resetting decoder, e.g. after aptX parity check
does not match.

In most cases, first 90 decoded samples should be just silence (at least
this is doing ffmpeg and android's proprietary encoders). But when you
start decoding not from beginning but from existing stream, then it is
a problem.

Decoding from the middle of stream happens when some A2DP packets are
lost and decoder needs to be reset and start decoding again.
On Thu, Dec 13, 2018 at 08:53:05PM +0100, Pali Rohár wrote:
> On Thursday 13 December 2018 18:42:11 Pasi Kärkkäinen wrote:
> > On Thu, Dec 13, 2018 at 04:36:59PM +0100, Pali Rohár wrote:
> > > What is the purpose of adding aptX codec (non HD variant)? It is not
> > > better then default and mandatory SBC codec.
> > >
> > 
> > When using AptX codec one can be certain it's always 352 kbps bitrate.
> > with SBC you never know what you end up using..
> 
> This is just because of current implementation of SBC in pulseaudio. Why
> not rather invest time to properly support SBC at high quality or allow
> user to set quality / show user current quality? SBC is mandatory and
> supported by all devices, aptX only by some (plus codec is proprietary).
>

I agree that pulseaudio should be improved so that it's easy to verify
the currently in use active codec settings/profile/bitrate.

 
> Adding aptX codec just because SBC implementation in pulseaudio is not
> so "nice" is not a good argument.
>

There are lots of physical AptX receiver/transmitter devices out there,
and while they of course also support SBC, they're often marketed as 
"AptX" high quality devices, so users will want to use AptX codec with them.

When you have both the SBC and AptX codecs available you can actually
easily compare them against each other, and use whatever you prefer :)


-- Pasi