[v3,2/4] Add A2DP AAC codec support

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

Details

Message ID 20181229154808.17235-3-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.
Optional (build with --disable-bluez5-aac-codec)

AAC User Configurations:
   KEY                 VALUE    DESC                                      DEFAULT
   aac_bitrate_mode    [1, 5]   Variable Bitrate (VBR) (encoder)          5
                       0        Constant Bitrate (CBR) (encoder)

   aac_fmt             s16      16-bit signed LE (encoder)                auto
                       s32      32-bit signed LE (encoder)
                       auto

   aac_afterburner     <on/off> FDK-AAC afterburner feature (encoder)     off
---
 configure.ac                           |  14 +-
 src/Makefile.am                        |   8 +
 src/modules/bluetooth/a2dp/a2dp-api.h  |  10 +-
 src/modules/bluetooth/a2dp/a2dp_aac.c  | 762 +++++++++++++++++++++++++
 src/modules/bluetooth/a2dp/a2dp_util.c |  33 ++
 5 files changed, 825 insertions(+), 2 deletions(-)
 create mode 100644 src/modules/bluetooth/a2dp/a2dp_aac.c

Patch hide | download patch | download mbox

diff --git a/configure.ac b/configure.ac
index 2512d3c95..0f9d7fb6c 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) ####
+#### BlueZ support (optional, dependent on D-Bus and SBC and FDK-AAC) ####
 
 AC_ARG_ENABLE([bluez5],
     AS_HELP_STRING([--disable-bluez5],[Disable optional BlueZ 5 support]))
@@ -1083,6 +1083,16 @@  AS_IF([test "x$HAVE_BLUEZ_5" = "x1"], HAVE_BLUEZ=1, HAVE_BLUEZ=0)
 AC_SUBST(HAVE_BLUEZ)
 AM_CONDITIONAL([HAVE_BLUEZ], [test "x$HAVE_BLUEZ" = x1])
 
+## FDK-AAC ##
+AC_ARG_ENABLE([bluez5-aac-codec],
+    AS_HELP_STRING([--disable-bluez5-aac-codec],[Disable optional A2DP AAC codec support (Bluez 5)]))
+AS_IF([test "x$HAVE_BLUEZ_5" = "x1" && test "x$enable_bluez5_aac_codec" != "xno"],
+    [PKG_CHECK_MODULES(FDK_AAC, [ fdk-aac >= 0.1.5 ], HAVE_FDK_AAC=1, HAVE_FDK_AAC=0)],
+    HAVE_FDK_AAC=0)
+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]))
+
 ## Bluetooth Headset profiles backend ##
 
 AC_ARG_ENABLE([bluez5-ofono-headset],
@@ -1587,6 +1597,7 @@  AS_IF([test "x$HAVE_SYSTEMD_DAEMON" = "x1"], ENABLE_SYSTEMD_DAEMON=yes, ENABLE_S
 AS_IF([test "x$HAVE_SYSTEMD_LOGIN" = "x1"], ENABLE_SYSTEMD_LOGIN=yes, ENABLE_SYSTEMD_LOGIN=no)
 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_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)
@@ -1645,6 +1656,7 @@  echo "
     Enable LIRC:                   ${ENABLE_LIRC}
     Enable D-Bus:                  ${ENABLE_DBUS}
       Enable BlueZ 5:              ${ENABLE_BLUEZ_5}
+        Enable A2DP AAC codec:     ${ENABLE_BLUEZ_5_AAC_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 521b9b684..c44a65f05 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -2128,6 +2128,10 @@  libbluez5_util_la_SOURCES = \
 		modules/bluetooth/a2dp/a2dp-api.h \
 		modules/bluetooth/a2dp/a2dp-codecs.h \
 		modules/bluetooth/a2dp/rtp.h
+if HAVE_FDK_AAC
+libbluez5_util_la_SOURCES += \
+		modules/bluetooth/a2dp/a2dp_aac.c
+endif
 if HAVE_BLUEZ_5_OFONO_HEADSET
 libbluez5_util_la_SOURCES += \
 		modules/bluetooth/backend-ofono.c
@@ -2140,6 +2144,10 @@  endif
 libbluez5_util_la_LDFLAGS = -avoid-version
 libbluez5_util_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS) $(SBC_LIBS)
 libbluez5_util_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) $(SBC_CFLAGS)
+if HAVE_FDK_AAC
+libbluez5_util_la_LIBADD += $(FDK_AAC_LIBS)
+libbluez5_util_la_CFLAGS += $(FDK_AAC_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 1492d29fd..1e601a9c5 100644
--- a/src/modules/bluetooth/a2dp/a2dp-api.h
+++ b/src/modules/bluetooth/a2dp/a2dp-api.h
@@ -37,7 +37,9 @@  typedef struct pa_a2dp_codec pa_a2dp_codec_t;
 typedef struct pa_a2dp_config pa_a2dp_config_t;
 
 extern const pa_a2dp_codec_t pa_a2dp_sbc;
-
+#ifdef HAVE_FDK_AAC
+extern const pa_a2dp_codec_t pa_a2dp_aac;
+#endif
 
 /* Run from <pa_a2dp_sink_t>.encode */
 
@@ -50,9 +52,15 @@  typedef void (*pa_a2dp_source_read_buf_free_cb_t)(const void **read_buf, void *d
 typedef enum pa_a2dp_codec_index {
     PA_A2DP_SINK_MIN,
     PA_A2DP_SINK_SBC,
+#ifdef HAVE_FDK_AAC
+    PA_A2DP_SINK_AAC,
+#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
     PA_A2DP_SOURCE_MAX,
     PA_A2DP_CODEC_INDEX_UNAVAILABLE
 } pa_a2dp_codec_index_t;
diff --git a/src/modules/bluetooth/a2dp/a2dp_aac.c b/src/modules/bluetooth/a2dp/a2dp_aac.c
new file mode 100644
index 000000000..a1d7bf1d2
--- /dev/null
+++ b/src/modules/bluetooth/a2dp/a2dp_aac.c
@@ -0,0 +1,762 @@ 
+/***
+  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>
+
+#include <fdk-aac/aacenc_lib.h>
+#include <fdk-aac/aacdecoder_lib.h>
+
+#include <pulse/xmalloc.h>
+
+#include "a2dp-api.h"
+
+#define streq(a, b) (!strcmp((a),(b)))
+
+#define AAC_DEFAULT_BITRATE 320000u
+
+typedef struct aac_info {
+    pa_a2dp_source_read_cb_t read_pcm;
+    pa_a2dp_source_read_buf_free_cb_t read_buf_free;
+
+    bool is_a2dp_sink;
+
+    uint16_t seq_num;
+
+    HANDLE_AACDECODER aacdecoder_handle;
+    bool aacdecoder_handle_opened;
+
+    HANDLE_AACENCODER aacenc_handle;
+    bool aacenc_handle_opened;
+    AACENC_InfoStruct aacenc_info;
+
+    uint32_t bitrate;
+    size_t mtu;
+
+    /* Constant Bitrate: 0
+     * Variable Bitrate: 1-5 (Only effects when both bluetooth devices have vbr support ) */
+    int aac_enc_bitrate_mode;
+    uint32_t aac_afterburner;
+    pa_sample_format_t force_pa_fmt;
+
+    pa_sample_spec sample_spec;
+
+    size_t read_block_size;
+    size_t write_block_size;
+
+} aac_info_t;
+
+static bool pa_aac_decoder_load() {
+    /* AAC libs dynamically linked */
+    return true;
+}
+
+static bool pa_aac_encoder_load() {
+    /* AAC libs dynamically linked */
+    return true;
+}
+
+static bool
+pa_aac_decoder_init(void **codec_data) {
+    aac_info_t *info = pa_xmalloc0(sizeof(aac_info_t));
+    *codec_data = info;
+    info->is_a2dp_sink = true;
+    return true;
+}
+
+static bool
+pa_aac_encoder_init(pa_a2dp_source_read_cb_t read_cb, pa_a2dp_source_read_buf_free_cb_t free_cb, void **codec_data) {
+    aac_info_t *info = pa_xmalloc0(sizeof(aac_info_t));
+    *codec_data = info;
+    info->is_a2dp_sink = false;
+    info->read_pcm = read_cb;
+    info->read_buf_free = free_cb;
+    info->aacenc_handle_opened = false;
+    info->aac_enc_bitrate_mode = 5;
+    info->aac_afterburner = false;
+    info->force_pa_fmt = PA_SAMPLE_INVALID;
+    return true;
+}
+
+/* KEY                 VALUE    DESC                                      DEFAULT
+ * aac_bitrate_mode    [1, 5]   Variable Bitrate (VBR) (encoder)          5
+ *                     0        Constant Bitrate (CBR) (encoder)
+ *
+ * aac_fmt             s16      16-bit signed LE (encoder)                auto
+ *                     s32      32-bit signed LE (encoder)
+ *                     auto
+ *
+ * aac_afterburner     <on/off> FDK-AAC afterburner feature (encoder)     off
+ */
+static int pa_aac_update_user_config(pa_proplist *user_config, void **codec_data) {
+    aac_info_t *i = *codec_data;
+    const char *aac_bitrate_mode_str, *aac_fmt_str, *aac_afterburner_str;
+    int aac_bitrate_mode = 0, ret = 0;
+    pa_assert(i);
+
+    aac_bitrate_mode_str = pa_proplist_gets(user_config, "aac_bitrate_mode");
+    aac_fmt_str = pa_proplist_gets(user_config, "aac_fmt");
+    aac_afterburner_str = pa_proplist_gets(user_config, "aac_afterburner");
+
+    if (aac_bitrate_mode_str) {
+        aac_bitrate_mode = atoi(aac_bitrate_mode_str);
+
+        if (aac_bitrate_mode >= 0 && aac_bitrate_mode <= 5) {
+            i->aac_enc_bitrate_mode = aac_bitrate_mode;
+            ret++;
+        } else
+            pa_log ("aac_bitrate_mode parameter must in range [0, 5] (found %s)", aac_bitrate_mode_str);
+    }
+
+    if (aac_fmt_str) {
+        if (streq(aac_fmt_str, "s16")) {
+            i->force_pa_fmt = PA_SAMPLE_S16LE;
+            ret++;
+        } else if (streq(aac_fmt_str, "s32")) {
+            i->force_pa_fmt = PA_SAMPLE_S32LE;
+            ret++;
+        } else if (streq(aac_fmt_str, "auto")) {
+            i->force_pa_fmt = PA_SAMPLE_INVALID;
+            ret++;
+        } else
+            pa_log ("aac_fmt parameter must be either s16, s32 or auto (found %s)", aac_fmt_str);
+    }
+
+    if (aac_afterburner_str) {
+        if (streq("on", aac_afterburner_str)) {
+            i->aac_afterburner = 1;
+            ret++;
+        } else if (streq("off", aac_afterburner_str)) {
+            i->aac_afterburner = 0;
+            ret++;
+        } else
+            pa_log ("aac_afterburner parameter must be either on or off (found %s)", aac_afterburner_str);
+    }
+
+    return ret;
+}
+
+static size_t
+pa_aac_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;
+    const UCHAR *p;
+    INT_PCM *d;
+    UINT to_decode, pkt_size;
+    UINT total_written = 0;
+    aac_info_t *aac_info = *codec_data;
+    pa_assert(aac_info);
+
+    header = read_buf;
+    *timestamp = ntohl(header->timestamp);
+
+    p = (UCHAR *) read_buf + sizeof(*header);
+    pkt_size = to_decode = (UINT) (read_buf_size - sizeof(*header));
+
+    d = write_buf;
+
+    *_decoded = 0;
+    while (PA_LIKELY(to_decode > 0)) {
+        CStreamInfo* info;
+
+        AAC_DECODER_ERROR aac_err = aacDecoder_Fill(aac_info->aacdecoder_handle,
+                                                    (UCHAR **) &p, &pkt_size, &to_decode);
+
+        if (PA_UNLIKELY(aac_err != AAC_DEC_OK)) {
+            pa_log_error("aacDecoder_Fill() error 0x%x", aac_err);
+            *_decoded = 0;
+            return 0;
+        }
+
+        while (true) {
+            INT written;
+            aac_err = aacDecoder_DecodeFrame(aac_info->aacdecoder_handle, d, (INT) write_buf_size, 0);
+            if (PA_UNLIKELY(aac_err == AAC_DEC_NOT_ENOUGH_BITS))
+                break;
+            if (PA_UNLIKELY(aac_err != AAC_DEC_OK)){
+                pa_log_error("aacDecoder_DecodeFrame() error 0x%x", aac_err);
+                break;
+            }
+
+            info = aacDecoder_GetStreamInfo(aac_info->aacdecoder_handle);
+            if(PA_UNLIKELY(!info || info->sampleRate <= 0)) {
+                pa_log_error("Invalid stream info");
+                break;
+            }
+
+            written = info->frameSize * info->numChannels * 2;
+            d += written;
+            total_written += (UINT) written;
+        }
+    }
+
+    *_decoded = pkt_size;
+
+    return total_written;
+}
+
+static size_t
+pa_aac_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;
+    uint8_t *d;
+    const uint8_t *p;
+    int to_write;
+    unsigned frame_count;
+    aac_info_t *aac_info = *codec_data;
+    const size_t sample_size = pa_sample_size(&aac_info->sample_spec),
+            frame_size = pa_frame_size(&aac_info->sample_spec);
+    void *in_bufs[1] = {NULL};
+    void *out_bufs[1] = {NULL};
+    int in_bufferIdentifiers[1] = {IN_AUDIO_DATA};
+    int out_bufferIdentifiers[1] = {OUT_BITSTREAM_DATA};
+    int in_bufSizes[1] = {(int) (aac_info->aacenc_info.frameLength * frame_size)};
+    int out_bufSizes[1];
+    int bufElSizes[1] = {(int) sample_size};
+    AACENC_BufDesc in_bufDesc = {
+            .numBufs = 1,
+            .bufs = in_bufs,
+            .bufferIdentifiers = in_bufferIdentifiers,
+            .bufSizes = in_bufSizes,
+            .bufElSizes = bufElSizes
+    };
+    AACENC_BufDesc out_bufDesc = {
+            .numBufs = 1,
+            .bufs = out_bufs,
+            .bufferIdentifiers = out_bufferIdentifiers,
+            .bufSizes = out_bufSizes,
+            .bufElSizes = bufElSizes
+    };
+    AACENC_InArgs in_args = {
+            .numAncBytes = 0,
+            .numInSamples = aac_info->aacenc_info.frameLength * aac_info->aacenc_info.inputChannels
+    };
+    AACENC_OutArgs out_args;
+
+    pa_assert(aac_info);
+
+    header = write_buf;
+
+    frame_count = 0;
+
+    aac_info->read_pcm((const void **) &p, (size_t) in_bufSizes[0], read_cb_data);
+
+    in_bufDesc.bufs[0] = (void *) p;
+
+    d = (uint8_t *) write_buf + sizeof(*header);
+    to_write = (int) (write_buf_size - sizeof(*header));
+    out_bufDesc.bufs[0] = d;
+    out_bufSizes[0] = to_write;
+
+
+    *_encoded = 0;
+
+    while (PA_UNLIKELY(in_args.numInSamples && to_write > 0)) {
+        size_t encoded;
+
+        AACENC_ERROR aac_err = aacEncEncode(aac_info->aacenc_handle, &in_bufDesc, &out_bufDesc, &in_args, &out_args);
+
+        if (PA_UNLIKELY(aac_err != AACENC_OK)) {
+            pa_log_error("AAC encoding error, 0x%x", aac_err);
+            aac_info->read_buf_free((const void **) &p, read_cb_data);
+            *_encoded = 0;
+            return 0;
+        }
+
+        encoded = out_args.numInSamples * sample_size;
+
+        in_args.numInSamples -= out_args.numInSamples;
+        p += encoded;
+        *_encoded += encoded;
+
+        to_write -= out_args.numOutBytes;
+        d += out_args.numOutBytes;
+
+        frame_count++;
+    }
+
+    aac_info->read_buf_free((const void **) &p, read_cb_data);
+
+    memset(write_buf, 0, sizeof(*header));
+    header->v = 2;
+    header->pt = 1;
+    header->sequence_number = htons(aac_info->seq_num++);
+    header->timestamp = htonl(timestamp);
+    header->ssrc = htonl(1);
+
+    nbytes = d - (uint8_t *) write_buf;
+
+    return nbytes;
+}
+
+static void
+pa_aac_config_transport(pa_sample_spec default_sample_spec, const void *configuration, size_t configuration_size,
+                        pa_sample_spec *sample_spec, void **codec_data) {
+    AACENC_ERROR aac_err;
+    aac_info_t *aac_info = *codec_data;
+    a2dp_aac_t *config = (a2dp_aac_t *) configuration;
+    UINT aot, sample_rate, channels;
+    pa_sample_format_t fmt;
+
+    pa_assert(aac_info);
+    pa_assert_se(configuration_size == sizeof(*config));
+
+    aac_info->bitrate = PA_MIN(AAC_DEFAULT_BITRATE, ((uint32_t) AAC_GET_BITRATE(*config)));
+
+
+    if(aac_info->is_a2dp_sink)
+        sample_spec->format = PA_SAMPLE_S16LE;
+    else{
+        if (aac_info->force_pa_fmt == PA_SAMPLE_INVALID)
+            fmt = default_sample_spec.format;
+        else
+            fmt = aac_info->force_pa_fmt;
+
+        switch (fmt) {
+            case PA_SAMPLE_S24LE:
+            case PA_SAMPLE_S24BE:
+            case PA_SAMPLE_S24_32LE:
+            case PA_SAMPLE_S24_32BE:
+            case PA_SAMPLE_S32LE:
+            case PA_SAMPLE_S32BE:
+            case PA_SAMPLE_FLOAT32LE:
+            case PA_SAMPLE_FLOAT32BE:
+                sample_spec->format = PA_SAMPLE_S32LE;
+                break;
+            default:
+                sample_spec->format = PA_SAMPLE_S16LE;
+        }
+    }
+
+    switch (config->object_type) {
+        case AAC_OBJECT_TYPE_MPEG2_AAC_LC:
+            aot = AOT_AAC_LC;
+            break;
+        case AAC_OBJECT_TYPE_MPEG4_AAC_LC:
+            aot = AOT_AAC_LC;
+            break;
+        case AAC_OBJECT_TYPE_MPEG4_AAC_LTP:
+            aot = AOT_AAC_LTP;
+            break;
+        case AAC_OBJECT_TYPE_MPEG4_AAC_SCA:
+            aot = AOT_AAC_SCAL;
+            break;
+        default:
+            pa_log_error("Invalid AAC object type");
+            pa_assert_not_reached();
+    }
+
+    switch (AAC_GET_FREQUENCY(*config)) {
+        case AAC_SAMPLING_FREQ_8000:
+            sample_rate = 8000;
+            sample_spec->rate = 8000;
+            break;
+        case AAC_SAMPLING_FREQ_11025:
+            sample_rate = 11025;
+            sample_spec->rate = 11025;
+            break;
+        case AAC_SAMPLING_FREQ_12000:
+            sample_rate = 12000;
+            sample_spec->rate = 12000;
+            break;
+        case AAC_SAMPLING_FREQ_16000:
+            sample_rate = 16000;
+            sample_spec->rate = 16000;
+            break;
+        case AAC_SAMPLING_FREQ_22050:
+            sample_rate = 22050;
+            sample_spec->rate = 22050;
+            break;
+        case AAC_SAMPLING_FREQ_24000:
+            sample_rate = 24000;
+            sample_spec->rate = 24000;
+            break;
+        case AAC_SAMPLING_FREQ_32000:
+            sample_rate = 32000;
+            sample_spec->rate = 32000;
+            break;
+        case AAC_SAMPLING_FREQ_44100:
+            sample_rate = 44100;
+            sample_spec->rate = 44100;
+            break;
+        case AAC_SAMPLING_FREQ_48000:
+            sample_rate = 48000;
+            sample_spec->rate = 48000;
+            break;
+        case AAC_SAMPLING_FREQ_64000:
+            sample_rate = 64000;
+            sample_spec->rate = 64000;
+            break;
+        case AAC_SAMPLING_FREQ_88200:
+            sample_rate = 88200;
+            sample_spec->rate = 88200;
+            break;
+        case AAC_SAMPLING_FREQ_96000:
+            sample_rate = 96000;
+            sample_spec->rate = 96000;
+            break;
+        default:
+            pa_log_error("Invalid AAC frequency");
+            pa_assert_not_reached();
+    }
+
+    switch (config->channels) {
+        case AAC_CHANNELS_1:
+            channels = MODE_1;
+            sample_spec->channels = 1;
+            break;
+        case AAC_CHANNELS_2:
+            channels = MODE_2;
+            sample_spec->channels = 2;
+            break;
+        default:
+            pa_log_error("Invalid AAC channel mode");
+            pa_assert_not_reached();
+    }
+
+    aac_info->sample_spec = *sample_spec;
+
+    /* AAC SINK */
+    if (aac_info->is_a2dp_sink) {
+        if (!aac_info->aacdecoder_handle_opened) {
+            aac_info->aacdecoder_handle = aacDecoder_Open(TT_MP4_LATM_MCP1, 1);
+            aac_info->aacdecoder_handle_opened = true;
+        }
+
+        pa_assert_se(AAC_DEC_OK == aacDecoder_SetParam(aac_info->aacdecoder_handle, AAC_PCM_MIN_OUTPUT_CHANNELS,
+                                                       sample_spec->channels));
+        pa_assert_se(AAC_DEC_OK == aacDecoder_SetParam(aac_info->aacdecoder_handle, AAC_PCM_MAX_OUTPUT_CHANNELS,
+                                                       sample_spec->channels));
+
+        return;
+    }
+
+
+    /* AAC SOURCE */
+
+    if (!aac_info->aacenc_handle_opened) {
+        aac_err = aacEncOpen(&aac_info->aacenc_handle, 0, 2);
+
+        if (aac_err != AACENC_OK) {
+            pa_log_error("Cannot open AAC encoder handle: AAC error 0x%x", aac_err);
+            pa_assert_not_reached();
+        }
+        aac_info->aacenc_handle_opened = true;
+    }
+
+    aac_err = aacEncoder_SetParam(aac_info->aacenc_handle, AACENC_AOT, aot);
+    if (aac_err != AACENC_OK)
+        pa_assert_not_reached();
+
+    aac_err = aacEncoder_SetParam(aac_info->aacenc_handle, AACENC_SAMPLERATE, sample_rate);
+    if (aac_err != AACENC_OK)
+        pa_assert_not_reached();
+
+    aac_err = aacEncoder_SetParam(aac_info->aacenc_handle, AACENC_CHANNELMODE, channels);
+    if (aac_err != AACENC_OK)
+        pa_assert_not_reached();
+
+    if (config->vbr) {
+        aac_err = aacEncoder_SetParam(aac_info->aacenc_handle, AACENC_BITRATEMODE,
+                                      (UINT) aac_info->aac_enc_bitrate_mode);
+        if (aac_err != AACENC_OK)
+            pa_assert_not_reached();
+    }
+
+    aac_err = aacEncoder_SetParam(aac_info->aacenc_handle, AACENC_AUDIOMUXVER, 2);
+    if (aac_err != AACENC_OK)
+        pa_assert_not_reached();
+
+    aac_err = aacEncoder_SetParam(aac_info->aacenc_handle, AACENC_SIGNALING_MODE, 1);
+    if (aac_err != AACENC_OK)
+        pa_assert_not_reached();
+
+    aac_err = aacEncoder_SetParam(aac_info->aacenc_handle, AACENC_BITRATE, aac_info->bitrate);
+    if (aac_err != AACENC_OK)
+        pa_assert_not_reached();
+
+    aac_err = aacEncoder_SetParam(aac_info->aacenc_handle, AACENC_TRANSMUX, TT_MP4_LATM_MCP1);
+    if (aac_err != AACENC_OK)
+        pa_assert_not_reached();
+
+    aac_err = aacEncoder_SetParam(aac_info->aacenc_handle, AACENC_HEADER_PERIOD, 1);
+    if (aac_err != AACENC_OK)
+        pa_assert_not_reached();
+
+    aac_err = aacEncoder_SetParam(aac_info->aacenc_handle, AACENC_AFTERBURNER, aac_info->aac_afterburner);
+    if (aac_err != AACENC_OK)
+        pa_assert_not_reached();
+
+    aac_err = aacEncEncode(aac_info->aacenc_handle, NULL, NULL, NULL, NULL);
+    if (aac_err != AACENC_OK)
+        pa_assert_not_reached();
+
+    pa_assert_se(AACENC_OK == aacEncInfo(aac_info->aacenc_handle, &aac_info->aacenc_info));
+
+    pa_assert(aac_info->aacenc_info.inputChannels == aac_info->sample_spec.channels);
+
+};
+
+static void pa_aac_get_read_block_size(size_t read_link_mtu, size_t *read_block_size, void **codec_data) {
+    aac_info_t *aac_info = *codec_data;
+    pa_assert(aac_info);
+
+    aac_info->mtu = read_link_mtu;
+
+    /* aacEncoder.pdf Section 3.2.1
+     * AAC-LC audio frame contains 1024 PCM samples per channel */
+    *read_block_size = 1024 * pa_frame_size(&aac_info->sample_spec);
+    aac_info->read_block_size = *read_block_size;
+};
+
+static void pa_aac_get_write_block_size(size_t write_link_mtu, size_t *write_block_size, void **codec_data) {
+    aac_info_t *aac_info = *codec_data;
+    pa_assert(aac_info);
+
+    aac_info->mtu = write_link_mtu;
+
+    /* aacEncoder.pdf section 3.2.1
+     * AAC-LC audio frame contains 1024 PCM samples per channel */
+    *write_block_size = 1024 * pa_frame_size(&aac_info->sample_spec);
+    aac_info->write_block_size = *write_block_size;
+};
+
+static void pa_aac_setup_stream(void **codec_data) {
+    AACENC_ERROR aac_err;
+    aac_info_t *aac_info = *codec_data;
+    uint32_t max_bitrate;
+    pa_assert(aac_info);
+
+    max_bitrate = (uint32_t) ((8 * (aac_info->mtu - sizeof(struct rtp_header)) * aac_info->sample_spec.rate) / 1024);
+
+    aac_info->bitrate = PA_MIN(max_bitrate, aac_info->bitrate);
+
+    pa_log_debug("Maximum AAC transmission bitrate: %d bps; Bitrate in use: %d bps", max_bitrate, aac_info->bitrate);
+
+    /* AAC SINK */
+    if (aac_info->is_a2dp_sink) {
+        return;
+    }
+
+
+    /* AAC SOURCE */
+
+    aac_err = aacEncoder_SetParam(aac_info->aacenc_handle, AACENC_BITRATE, aac_info->bitrate);
+    if (aac_err != AACENC_OK)
+        pa_assert_not_reached();
+
+    aac_err = aacEncoder_SetParam(aac_info->aacenc_handle, AACENC_PEAK_BITRATE, (UINT) max_bitrate);
+    if (aac_err != AACENC_OK)
+        pa_assert_not_reached();
+
+};
+
+static void pa_aac_free(void **codec_data) {
+    aac_info_t *aac_info = *codec_data;
+    if (!aac_info)
+        return;
+
+    if (aac_info->aacenc_handle_opened)
+        aacEncClose(&aac_info->aacenc_handle);
+
+    if (aac_info->aacdecoder_handle_opened)
+        aacDecoder_Close(aac_info->aacdecoder_handle);
+
+    pa_xfree(aac_info);
+    *codec_data = NULL;
+
+};
+
+static size_t pa_aac_get_capabilities(void **_capabilities) {
+    a2dp_aac_t *capabilities = pa_xmalloc0(sizeof(a2dp_aac_t));
+
+    capabilities->object_type = AAC_OBJECT_TYPE_MPEG2_AAC_LC | AAC_OBJECT_TYPE_MPEG4_AAC_LC;
+    capabilities->channels = AAC_CHANNELS_1 | AAC_CHANNELS_2;
+    AAC_SET_BITRATE(*capabilities, AAC_DEFAULT_BITRATE);
+    AAC_SET_FREQUENCY(*capabilities, (AAC_SAMPLING_FREQ_8000 | AAC_SAMPLING_FREQ_11025 | AAC_SAMPLING_FREQ_12000 |
+                                      AAC_SAMPLING_FREQ_16000 | AAC_SAMPLING_FREQ_22050 | AAC_SAMPLING_FREQ_24000 |
+                                      AAC_SAMPLING_FREQ_32000 | AAC_SAMPLING_FREQ_44100 | AAC_SAMPLING_FREQ_48000 |
+                                      AAC_SAMPLING_FREQ_64000 | AAC_SAMPLING_FREQ_88200 | AAC_SAMPLING_FREQ_96000));
+    capabilities->vbr = 1;
+    *_capabilities = capabilities;
+
+    return sizeof(*capabilities);
+};
+
+static size_t
+pa_aac_select_configuration(const pa_sample_spec default_sample_spec, const uint8_t *supported_capabilities,
+                            const size_t capabilities_size, void **configuration) {
+    a2dp_aac_t *cap = (a2dp_aac_t *) supported_capabilities;
+    a2dp_aac_t *config = pa_xmalloc0(sizeof(a2dp_aac_t));
+    pa_a2dp_freq_cap_t aac_freq_cap, aac_freq_table[] = {
+            {8000U,  AAC_SAMPLING_FREQ_8000},
+            {11025U, AAC_SAMPLING_FREQ_11025},
+            {12000U, AAC_SAMPLING_FREQ_12000},
+            {16000U, AAC_SAMPLING_FREQ_16000},
+            {22050U, AAC_SAMPLING_FREQ_22050},
+            {24000U, AAC_SAMPLING_FREQ_24000},
+            {32000U, AAC_SAMPLING_FREQ_32000},
+            {44100U, AAC_SAMPLING_FREQ_44100},
+            {48000U, AAC_SAMPLING_FREQ_48000},
+            {64000U, AAC_SAMPLING_FREQ_64000},
+            {88200U, AAC_SAMPLING_FREQ_88200},
+            {96000U, AAC_SAMPLING_FREQ_96000}
+    };
+
+    if (capabilities_size != sizeof(a2dp_aac_t))
+        return 0;
+
+    if (!pa_a2dp_select_cap_frequency(AAC_GET_FREQUENCY(*cap), default_sample_spec, aac_freq_table,
+                                      PA_ELEMENTSOF(aac_freq_table), &aac_freq_cap))
+        return 0;
+
+    AAC_SET_FREQUENCY(*config, aac_freq_cap.cap);
+
+    AAC_SET_BITRATE(*config, AAC_GET_BITRATE(*cap));
+
+    if (default_sample_spec.channels <= 1) {
+        if (cap->channels & AAC_CHANNELS_1)
+            config->channels = AAC_CHANNELS_1;
+        else if (cap->channels & AAC_CHANNELS_2)
+            config->channels = AAC_CHANNELS_2;
+        else {
+            pa_log_error("No supported channel modes");
+            return 0;
+        }
+    }
+
+    if (default_sample_spec.channels >= 2) {
+        if (cap->channels & AAC_CHANNELS_2)
+            config->channels = AAC_CHANNELS_2;
+        else if (cap->channels & AAC_CHANNELS_1)
+            config->channels = AAC_CHANNELS_1;
+        else {
+            pa_log_error("No supported channel modes");
+            return 0;
+        }
+    }
+
+    if (cap->object_type & AAC_OBJECT_TYPE_MPEG4_AAC_LC)
+        config->object_type = AAC_OBJECT_TYPE_MPEG4_AAC_LC;
+    else if (cap->object_type & AAC_OBJECT_TYPE_MPEG2_AAC_LC)
+        config->object_type = AAC_OBJECT_TYPE_MPEG2_AAC_LC;
+    else {
+        pa_log_error("No supported aac object type");
+        return 0;
+    }
+
+    config->vbr = cap->vbr;
+
+    *configuration = config;
+    return sizeof(*config);
+};
+
+static void pa_aac_free_capabilities(void **capabilities) {
+    if (!capabilities || !*capabilities)
+        return;
+    pa_xfree(*capabilities);
+    *capabilities = NULL;
+}
+
+static bool pa_aac_validate_configuration(const uint8_t *selected_configuration, const size_t configuration_size) {
+    a2dp_aac_t *c = (a2dp_aac_t *) selected_configuration;
+
+    if (configuration_size != sizeof(a2dp_aac_t)) {
+        pa_log_error("AAC configuration array of invalid size");
+        return false;
+    }
+
+    switch (c->object_type) {
+        case AAC_OBJECT_TYPE_MPEG2_AAC_LC:
+        case AAC_OBJECT_TYPE_MPEG4_AAC_LC:
+            break;
+        default:
+            pa_log_error("Invalid object type in AAC configuration");
+            return false;
+    }
+
+    switch (AAC_GET_FREQUENCY(*c)) {
+        case AAC_SAMPLING_FREQ_8000:
+        case AAC_SAMPLING_FREQ_11025:
+        case AAC_SAMPLING_FREQ_12000:
+        case AAC_SAMPLING_FREQ_16000:
+        case AAC_SAMPLING_FREQ_22050:
+        case AAC_SAMPLING_FREQ_24000:
+        case AAC_SAMPLING_FREQ_32000:
+        case AAC_SAMPLING_FREQ_44100:
+        case AAC_SAMPLING_FREQ_48000:
+        case AAC_SAMPLING_FREQ_64000:
+        case AAC_SAMPLING_FREQ_88200:
+        case AAC_SAMPLING_FREQ_96000:
+            break;
+        default:
+            pa_log_error("Invalid sampling frequency in AAC configuration");
+            return false;
+    }
+
+    switch (c->channels) {
+        case AAC_CHANNELS_1:
+        case AAC_CHANNELS_2:
+            break;
+        default:
+            pa_log_error("Invalid channel mode in AAC Configuration");
+            return false;
+    }
+
+    return true;
+};
+
+
+static pa_a2dp_source_t pa_aac_source = {
+        .encoder_load = pa_aac_encoder_load,
+        .init = pa_aac_encoder_init,
+        .update_user_config = pa_aac_update_user_config,
+        .encode = pa_aac_encode,
+        .config_transport=pa_aac_config_transport,
+        .get_block_size=pa_aac_get_write_block_size,
+        .setup_stream = pa_aac_setup_stream,
+        .set_tx_length = NULL,
+        .decrease_quality = NULL,
+        .free = pa_aac_free
+};
+
+static pa_a2dp_sink_t pa_aac_sink = {
+        .decoder_load = pa_aac_decoder_load,
+        .init = pa_aac_decoder_init,
+        .update_user_config = NULL,
+        .config_transport=pa_aac_config_transport,
+        .get_block_size=pa_aac_get_read_block_size,
+        .setup_stream = pa_aac_setup_stream,
+        .decode = pa_aac_decode,
+        .free = pa_aac_free
+};
+
+const pa_a2dp_codec_t pa_a2dp_aac = {
+        .name = "AAC",
+        .codec = A2DP_CODEC_MPEG24,
+        .vendor_codec = NULL,
+        .a2dp_sink = &pa_aac_sink,
+        .a2dp_source = &pa_aac_source,
+        .get_capabilities = pa_aac_get_capabilities,
+        .select_configuration = pa_aac_select_configuration,
+        .free_capabilities = pa_aac_free_capabilities,
+        .free_configuration = pa_aac_free_capabilities,
+        .validate_configuration = pa_aac_validate_configuration
+};
diff --git a/src/modules/bluetooth/a2dp/a2dp_util.c b/src/modules/bluetooth/a2dp/a2dp_util.c
index cc9382f2e..edd28b5cc 100644
--- a/src/modules/bluetooth/a2dp/a2dp_util.c
+++ b/src/modules/bluetooth/a2dp/a2dp_util.c
@@ -30,6 +30,9 @@ 
 #define A2DP_SBC_SRC_ENDPOINT A2DP_SOURCE_ENDPOINT "/SBC"
 #define A2DP_SBC_SNK_ENDPOINT A2DP_SINK_ENDPOINT "/SBC"
 
+#define A2DP_AAC_SRC_ENDPOINT A2DP_SOURCE_ENDPOINT "/AAC"
+#define A2DP_AAC_SNK_ENDPOINT A2DP_SINK_ENDPOINT "/AAC"
+
 #define A2DP_VENDOR_SRC_ENDPOINT A2DP_SOURCE_ENDPOINT "/VENDOR"
 #define A2DP_VENDOR_SNK_ENDPOINT A2DP_SINK_ENDPOINT "/VENDOR"
 
@@ -209,6 +212,14 @@  void pa_a2dp_codec_index_to_endpoint(pa_a2dp_codec_index_t codec_index, const ch
         case PA_A2DP_SOURCE_SBC:
             *endpoint = A2DP_SBC_SRC_ENDPOINT;
             break;
+#ifdef HAVE_FDK_AAC
+        case PA_A2DP_SINK_AAC:
+            *endpoint = A2DP_AAC_SNK_ENDPOINT;
+            break;
+        case PA_A2DP_SOURCE_AAC:
+            *endpoint = A2DP_AAC_SRC_ENDPOINT;
+            break;
+#endif
         default:
             *endpoint = NULL;
     }
@@ -219,6 +230,12 @@  void pa_a2dp_endpoint_to_codec_index(const char *endpoint, pa_a2dp_codec_index_t
         *codec_index = PA_A2DP_SINK_SBC;
     else if (streq(endpoint, A2DP_SBC_SRC_ENDPOINT))
         *codec_index = PA_A2DP_SOURCE_SBC;
+#ifdef HAVE_FDK_AAC
+    else if (streq(endpoint, A2DP_AAC_SNK_ENDPOINT))
+        *codec_index = PA_A2DP_SINK_AAC;
+    else if (streq(endpoint, A2DP_AAC_SRC_ENDPOINT))
+        *codec_index = PA_A2DP_SOURCE_AAC;
+#endif
     else
         *codec_index = PA_A2DP_CODEC_INDEX_UNAVAILABLE;
 };
@@ -229,6 +246,12 @@  void pa_a2dp_codec_index_to_a2dp_codec(pa_a2dp_codec_index_t codec_index, const
         case PA_A2DP_SOURCE_SBC:
             *a2dp_codec = &pa_a2dp_sbc;
             break;
+#ifdef HAVE_FDK_AAC
+        case PA_A2DP_SINK_AAC:
+        case PA_A2DP_SOURCE_AAC:
+            *a2dp_codec = &pa_a2dp_aac;
+            break;
+#endif
         default:
             *a2dp_codec = NULL;
     }
@@ -244,6 +267,11 @@  void pa_a2dp_a2dp_codec_to_codec_index(const pa_a2dp_codec_t *a2dp_codec, bool i
         case A2DP_CODEC_SBC:
             *codec_index = is_a2dp_sink ? PA_A2DP_SINK_SBC : PA_A2DP_SOURCE_SBC;
             return;
+#ifdef HAVE_FDK_AAC
+        case A2DP_CODEC_MPEG24:
+            *codec_index = is_a2dp_sink ? PA_A2DP_SINK_AAC : PA_A2DP_SOURCE_AAC;
+            return;
+#endif
         case A2DP_CODEC_VENDOR:
             if (!a2dp_codec->vendor_codec) {
                 *codec_index = PA_A2DP_CODEC_INDEX_UNAVAILABLE;
@@ -262,6 +290,11 @@  pa_a2dp_get_a2dp_codec(uint8_t codec, const a2dp_vendor_codec_t *vendor_codec, c
         case A2DP_CODEC_SBC:
             *a2dp_codec = &pa_a2dp_sbc;
             return;
+#ifdef HAVE_FDK_AAC
+        case A2DP_CODEC_MPEG24:
+            *a2dp_codec = &pa_a2dp_aac;
+            return;
+#endif
         case A2DP_CODEC_VENDOR:
             if (!vendor_codec) {
                 *a2dp_codec = NULL;

Comments

Hi,

On Sat, Dec 29, 2018 at 12:49 PM Huang-Huang Bao <eh5@sokka.cn> wrote:
>
> Optional (build with --disable-bluez5-aac-codec)
>
> AAC User Configurations:
>    KEY                 VALUE    DESC                                      DEFAULT
>    aac_bitrate_mode    [1, 5]   Variable Bitrate (VBR) (encoder)          5
>                        0        Constant Bitrate (CBR) (encoder)
>
>    aac_fmt             s16      16-bit signed LE (encoder)                auto
>                        s32      32-bit signed LE (encoder)
>                        auto
>
>    aac_afterburner     <on/off> FDK-AAC afterburner feature (encoder)     off
> ---
>  configure.ac                           |  14 +-
>  src/Makefile.am                        |   8 +
>  src/modules/bluetooth/a2dp/a2dp-api.h  |  10 +-
>  src/modules/bluetooth/a2dp/a2dp_aac.c  | 762 +++++++++++++++++++++++++
>  src/modules/bluetooth/a2dp/a2dp_util.c |  33 ++
>  5 files changed, 825 insertions(+), 2 deletions(-)
>  create mode 100644 src/modules/bluetooth/a2dp/a2dp_aac.c
>
> diff --git a/configure.ac b/configure.ac
> index 2512d3c95..0f9d7fb6c 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) ####
> +#### BlueZ support (optional, dependent on D-Bus and SBC and FDK-AAC) ####
>
>  AC_ARG_ENABLE([bluez5],
>      AS_HELP_STRING([--disable-bluez5],[Disable optional BlueZ 5 support]))
> @@ -1083,6 +1083,16 @@ AS_IF([test "x$HAVE_BLUEZ_5" = "x1"], HAVE_BLUEZ=1, HAVE_BLUEZ=0)
>  AC_SUBST(HAVE_BLUEZ)
>  AM_CONDITIONAL([HAVE_BLUEZ], [test "x$HAVE_BLUEZ" = x1])
>
> +## FDK-AAC ##
> +AC_ARG_ENABLE([bluez5-aac-codec],
> +    AS_HELP_STRING([--disable-bluez5-aac-codec],[Disable optional A2DP AAC codec support (Bluez 5)]))
> +AS_IF([test "x$HAVE_BLUEZ_5" = "x1" && test "x$enable_bluez5_aac_codec" != "xno"],
> +    [PKG_CHECK_MODULES(FDK_AAC, [ fdk-aac >= 0.1.5 ], HAVE_FDK_AAC=1, HAVE_FDK_AAC=0)],
> +    HAVE_FDK_AAC=0)
> +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]))
> +
>  ## Bluetooth Headset profiles backend ##
>
>  AC_ARG_ENABLE([bluez5-ofono-headset],
> @@ -1587,6 +1597,7 @@ AS_IF([test "x$HAVE_SYSTEMD_DAEMON" = "x1"], ENABLE_SYSTEMD_DAEMON=yes, ENABLE_S
>  AS_IF([test "x$HAVE_SYSTEMD_LOGIN" = "x1"], ENABLE_SYSTEMD_LOGIN=yes, ENABLE_SYSTEMD_LOGIN=no)
>  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_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)
> @@ -1645,6 +1656,7 @@ echo "
>      Enable LIRC:                   ${ENABLE_LIRC}
>      Enable D-Bus:                  ${ENABLE_DBUS}
>        Enable BlueZ 5:              ${ENABLE_BLUEZ_5}
> +        Enable A2DP AAC codec:     ${ENABLE_BLUEZ_5_AAC_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 521b9b684..c44a65f05 100644
> --- a/src/Makefile.am
> +++ b/src/Makefile.am
> @@ -2128,6 +2128,10 @@ libbluez5_util_la_SOURCES = \
>                 modules/bluetooth/a2dp/a2dp-api.h \
>                 modules/bluetooth/a2dp/a2dp-codecs.h \
>                 modules/bluetooth/a2dp/rtp.h
> +if HAVE_FDK_AAC
> +libbluez5_util_la_SOURCES += \
> +               modules/bluetooth/a2dp/a2dp_aac.c
> +endif
>  if HAVE_BLUEZ_5_OFONO_HEADSET
>  libbluez5_util_la_SOURCES += \
>                 modules/bluetooth/backend-ofono.c
> @@ -2140,6 +2144,10 @@ endif
>  libbluez5_util_la_LDFLAGS = -avoid-version
>  libbluez5_util_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS) $(SBC_LIBS)
>  libbluez5_util_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) $(SBC_CFLAGS)
> +if HAVE_FDK_AAC
> +libbluez5_util_la_LIBADD += $(FDK_AAC_LIBS)
> +libbluez5_util_la_CFLAGS += $(FDK_AAC_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 1492d29fd..1e601a9c5 100644
> --- a/src/modules/bluetooth/a2dp/a2dp-api.h
> +++ b/src/modules/bluetooth/a2dp/a2dp-api.h
> @@ -37,7 +37,9 @@ typedef struct pa_a2dp_codec pa_a2dp_codec_t;
>  typedef struct pa_a2dp_config pa_a2dp_config_t;
>
>  extern const pa_a2dp_codec_t pa_a2dp_sbc;
> -
> +#ifdef HAVE_FDK_AAC
> +extern const pa_a2dp_codec_t pa_a2dp_aac;
> +#endif
>
>  /* Run from <pa_a2dp_sink_t>.encode */
>
> @@ -50,9 +52,15 @@ typedef void (*pa_a2dp_source_read_buf_free_cb_t)(const void **read_buf, void *d
>  typedef enum pa_a2dp_codec_index {
>      PA_A2DP_SINK_MIN,
>      PA_A2DP_SINK_SBC,
> +#ifdef HAVE_FDK_AAC
> +    PA_A2DP_SINK_AAC,
> +#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
>      PA_A2DP_SOURCE_MAX,
>      PA_A2DP_CODEC_INDEX_UNAVAILABLE
>  } pa_a2dp_codec_index_t;
> diff --git a/src/modules/bluetooth/a2dp/a2dp_aac.c b/src/modules/bluetooth/a2dp/a2dp_aac.c
> new file mode 100644
> index 000000000..a1d7bf1d2
> --- /dev/null
> +++ b/src/modules/bluetooth/a2dp/a2dp_aac.c
> @@ -0,0 +1,762 @@
> +/***
> +  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>
> +
> +#include <fdk-aac/aacenc_lib.h>
> +#include <fdk-aac/aacdecoder_lib.h>
> +
> +#include <pulse/xmalloc.h>
> +
> +#include "a2dp-api.h"
> +
> +#define streq(a, b) (!strcmp((a),(b)))
> +
> +#define AAC_DEFAULT_BITRATE 320000u
> +
> +typedef struct aac_info {
> +    pa_a2dp_source_read_cb_t read_pcm;
> +    pa_a2dp_source_read_buf_free_cb_t read_buf_free;
> +
> +    bool is_a2dp_sink;
> +
> +    uint16_t seq_num;
> +
> +    HANDLE_AACDECODER aacdecoder_handle;
> +    bool aacdecoder_handle_opened;
> +
> +    HANDLE_AACENCODER aacenc_handle;
> +    bool aacenc_handle_opened;
> +    AACENC_InfoStruct aacenc_info;
> +
> +    uint32_t bitrate;
> +    size_t mtu;
> +
> +    /* Constant Bitrate: 0
> +     * Variable Bitrate: 1-5 (Only effects when both bluetooth devices have vbr support ) */
> +    int aac_enc_bitrate_mode;
> +    uint32_t aac_afterburner;
> +    pa_sample_format_t force_pa_fmt;
> +
> +    pa_sample_spec sample_spec;
> +
> +    size_t read_block_size;
> +    size_t write_block_size;
> +
> +} aac_info_t;
> +
> +static bool pa_aac_decoder_load() {
> +    /* AAC libs dynamically linked */
> +    return true;
> +}
> +
> +static bool pa_aac_encoder_load() {
> +    /* AAC libs dynamically linked */
> +    return true;
> +}
> +
> +static bool
> +pa_aac_decoder_init(void **codec_data) {
> +    aac_info_t *info = pa_xmalloc0(sizeof(aac_info_t));
> +    *codec_data = info;
> +    info->is_a2dp_sink = true;
> +    return true;
> +}
> +
> +static bool
> +pa_aac_encoder_init(pa_a2dp_source_read_cb_t read_cb, pa_a2dp_source_read_buf_free_cb_t free_cb, void **codec_data) {
> +    aac_info_t *info = pa_xmalloc0(sizeof(aac_info_t));
> +    *codec_data = info;
> +    info->is_a2dp_sink = false;
> +    info->read_pcm = read_cb;
> +    info->read_buf_free = free_cb;
> +    info->aacenc_handle_opened = false;
> +    info->aac_enc_bitrate_mode = 5;
> +    info->aac_afterburner = false;
> +    info->force_pa_fmt = PA_SAMPLE_INVALID;
> +    return true;
> +}
> +
> +/* KEY                 VALUE    DESC                                      DEFAULT
> + * aac_bitrate_mode    [1, 5]   Variable Bitrate (VBR) (encoder)          5
> + *                     0        Constant Bitrate (CBR) (encoder)
> + *
> + * aac_fmt             s16      16-bit signed LE (encoder)                auto
> + *                     s32      32-bit signed LE (encoder)
> + *                     auto
> + *
> + * aac_afterburner     <on/off> FDK-AAC afterburner feature (encoder)     off
> + */
> +static int pa_aac_update_user_config(pa_proplist *user_config, void **codec_data) {
> +    aac_info_t *i = *codec_data;
> +    const char *aac_bitrate_mode_str, *aac_fmt_str, *aac_afterburner_str;
> +    int aac_bitrate_mode = 0, ret = 0;
> +    pa_assert(i);
> +
> +    aac_bitrate_mode_str = pa_proplist_gets(user_config, "aac_bitrate_mode");
> +    aac_fmt_str = pa_proplist_gets(user_config, "aac_fmt");
> +    aac_afterburner_str = pa_proplist_gets(user_config, "aac_afterburner");
> +
> +    if (aac_bitrate_mode_str) {
> +        aac_bitrate_mode = atoi(aac_bitrate_mode_str);
> +
> +        if (aac_bitrate_mode >= 0 && aac_bitrate_mode <= 5) {
> +            i->aac_enc_bitrate_mode = aac_bitrate_mode;
> +            ret++;
> +        } else
> +            pa_log ("aac_bitrate_mode parameter must in range [0, 5] (found %s)", aac_bitrate_mode_str);
> +    }
> +
> +    if (aac_fmt_str) {
> +        if (streq(aac_fmt_str, "s16")) {
> +            i->force_pa_fmt = PA_SAMPLE_S16LE;
> +            ret++;
> +        } else if (streq(aac_fmt_str, "s32")) {
> +            i->force_pa_fmt = PA_SAMPLE_S32LE;
> +            ret++;
> +        } else if (streq(aac_fmt_str, "auto")) {
> +            i->force_pa_fmt = PA_SAMPLE_INVALID;
> +            ret++;
> +        } else
> +            pa_log ("aac_fmt parameter must be either s16, s32 or auto (found %s)", aac_fmt_str);
> +    }
> +
> +    if (aac_afterburner_str) {
> +        if (streq("on", aac_afterburner_str)) {
> +            i->aac_afterburner = 1;
> +            ret++;
> +        } else if (streq("off", aac_afterburner_str)) {
> +            i->aac_afterburner = 0;
> +            ret++;
> +        } else
> +            pa_log ("aac_afterburner parameter must be either on or off (found %s)", aac_afterburner_str);
> +    }
> +
> +    return ret;
> +}
> +
> +static size_t
> +pa_aac_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;
> +    const UCHAR *p;
> +    INT_PCM *d;
> +    UINT to_decode, pkt_size;
> +    UINT total_written = 0;
> +    aac_info_t *aac_info = *codec_data;
> +    pa_assert(aac_info);
> +
> +    header = read_buf;
> +    *timestamp = ntohl(header->timestamp);
> +
> +    p = (UCHAR *) read_buf + sizeof(*header);
> +    pkt_size = to_decode = (UINT) (read_buf_size - sizeof(*header));
> +
> +    d = write_buf;
> +
> +    *_decoded = 0;
> +    while (PA_LIKELY(to_decode > 0)) {
> +        CStreamInfo* info;
> +
> +        AAC_DECODER_ERROR aac_err = aacDecoder_Fill(aac_info->aacdecoder_handle,
> +                                                    (UCHAR **) &p, &pkt_size, &to_decode);
> +
> +        if (PA_UNLIKELY(aac_err != AAC_DEC_OK)) {
> +            pa_log_error("aacDecoder_Fill() error 0x%x", aac_err);
> +            *_decoded = 0;
> +            return 0;
> +        }
> +
> +        while (true) {
> +            INT written;
> +            aac_err = aacDecoder_DecodeFrame(aac_info->aacdecoder_handle, d, (INT) write_buf_size, 0);
> +            if (PA_UNLIKELY(aac_err == AAC_DEC_NOT_ENOUGH_BITS))
> +                break;
> +            if (PA_UNLIKELY(aac_err != AAC_DEC_OK)){
> +                pa_log_error("aacDecoder_DecodeFrame() error 0x%x", aac_err);
> +                break;
> +            }
> +
> +            info = aacDecoder_GetStreamInfo(aac_info->aacdecoder_handle);
> +            if(PA_UNLIKELY(!info || info->sampleRate <= 0)) {
> +                pa_log_error("Invalid stream info");
> +                break;
> +            }
> +
> +            written = info->frameSize * info->numChannels * 2;
> +            d += written;
> +            total_written += (UINT) written;
> +        }
> +    }
> +
> +    *_decoded = pkt_size;
> +
> +    return total_written;
> +}
> +
> +static size_t
> +pa_aac_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;
> +    uint8_t *d;
> +    const uint8_t *p;
> +    int to_write;
> +    unsigned frame_count;
> +    aac_info_t *aac_info = *codec_data;
> +    const size_t sample_size = pa_sample_size(&aac_info->sample_spec),
> +            frame_size = pa_frame_size(&aac_info->sample_spec);
> +    void *in_bufs[1] = {NULL};
> +    void *out_bufs[1] = {NULL};
> +    int in_bufferIdentifiers[1] = {IN_AUDIO_DATA};
> +    int out_bufferIdentifiers[1] = {OUT_BITSTREAM_DATA};
> +    int in_bufSizes[1] = {(int) (aac_info->aacenc_info.frameLength * frame_size)};
> +    int out_bufSizes[1];
> +    int bufElSizes[1] = {(int) sample_size};
> +    AACENC_BufDesc in_bufDesc = {
> +            .numBufs = 1,
> +            .bufs = in_bufs,
> +            .bufferIdentifiers = in_bufferIdentifiers,
> +            .bufSizes = in_bufSizes,
> +            .bufElSizes = bufElSizes
> +    };
> +    AACENC_BufDesc out_bufDesc = {
> +            .numBufs = 1,
> +            .bufs = out_bufs,
> +            .bufferIdentifiers = out_bufferIdentifiers,
> +            .bufSizes = out_bufSizes,
> +            .bufElSizes = bufElSizes
> +    };
> +    AACENC_InArgs in_args = {
> +            .numAncBytes = 0,
> +            .numInSamples = aac_info->aacenc_info.frameLength * aac_info->aacenc_info.inputChannels
> +    };
> +    AACENC_OutArgs out_args;
> +
> +    pa_assert(aac_info);
> +
> +    header = write_buf;
> +
> +    frame_count = 0;
> +
> +    aac_info->read_pcm((const void **) &p, (size_t) in_bufSizes[0], read_cb_data);
> +
> +    in_bufDesc.bufs[0] = (void *) p;
> +
> +    d = (uint8_t *) write_buf + sizeof(*header);
> +    to_write = (int) (write_buf_size - sizeof(*header));
> +    out_bufDesc.bufs[0] = d;
> +    out_bufSizes[0] = to_write;
> +
> +
> +    *_encoded = 0;
> +
> +    while (PA_UNLIKELY(in_args.numInSamples && to_write > 0)) {
> +        size_t encoded;
> +
> +        AACENC_ERROR aac_err = aacEncEncode(aac_info->aacenc_handle, &in_bufDesc, &out_bufDesc, &in_args, &out_args);
> +
> +        if (PA_UNLIKELY(aac_err != AACENC_OK)) {
> +            pa_log_error("AAC encoding error, 0x%x", aac_err);
> +            aac_info->read_buf_free((const void **) &p, read_cb_data);
> +            *_encoded = 0;
> +            return 0;
> +        }
> +
> +        encoded = out_args.numInSamples * sample_size;
> +
> +        in_args.numInSamples -= out_args.numInSamples;
> +        p += encoded;
> +        *_encoded += encoded;
> +
> +        to_write -= out_args.numOutBytes;
> +        d += out_args.numOutBytes;
> +
> +        frame_count++;
> +    }
> +
> +    aac_info->read_buf_free((const void **) &p, read_cb_data);
> +
> +    memset(write_buf, 0, sizeof(*header));
> +    header->v = 2;
> +    header->pt = 1;
> +    header->sequence_number = htons(aac_info->seq_num++);
> +    header->timestamp = htonl(timestamp);
> +    header->ssrc = htonl(1);
> +
> +    nbytes = d - (uint8_t *) write_buf;
> +
> +    return nbytes;
> +}
> +
> +static void
> +pa_aac_config_transport(pa_sample_spec default_sample_spec, const void *configuration, size_t configuration_size,
> +                        pa_sample_spec *sample_spec, void **codec_data) {
> +    AACENC_ERROR aac_err;
> +    aac_info_t *aac_info = *codec_data;
> +    a2dp_aac_t *config = (a2dp_aac_t *) configuration;
> +    UINT aot, sample_rate, channels;
> +    pa_sample_format_t fmt;
> +
> +    pa_assert(aac_info);
> +    pa_assert_se(configuration_size == sizeof(*config));
> +
> +    aac_info->bitrate = PA_MIN(AAC_DEFAULT_BITRATE, ((uint32_t) AAC_GET_BITRATE(*config)));
> +
> +
> +    if(aac_info->is_a2dp_sink)
> +        sample_spec->format = PA_SAMPLE_S16LE;
> +    else{
> +        if (aac_info->force_pa_fmt == PA_SAMPLE_INVALID)
> +            fmt = default_sample_spec.format;
> +        else
> +            fmt = aac_info->force_pa_fmt;
> +
> +        switch (fmt) {
> +            case PA_SAMPLE_S24LE:
> +            case PA_SAMPLE_S24BE:
> +            case PA_SAMPLE_S24_32LE:
> +            case PA_SAMPLE_S24_32BE:
> +            case PA_SAMPLE_S32LE:
> +            case PA_SAMPLE_S32BE:
> +            case PA_SAMPLE_FLOAT32LE:
> +            case PA_SAMPLE_FLOAT32BE:
> +                sample_spec->format = PA_SAMPLE_S32LE;
> +                break;
> +            default:
> +                sample_spec->format = PA_SAMPLE_S16LE;
> +        }
> +    }
> +
> +    switch (config->object_type) {
> +        case AAC_OBJECT_TYPE_MPEG2_AAC_LC:
> +            aot = AOT_AAC_LC;
> +            break;
> +        case AAC_OBJECT_TYPE_MPEG4_AAC_LC:
> +            aot = AOT_AAC_LC;
> +            break;
> +        case AAC_OBJECT_TYPE_MPEG4_AAC_LTP:
> +            aot = AOT_AAC_LTP;
> +            break;
> +        case AAC_OBJECT_TYPE_MPEG4_AAC_SCA:
> +            aot = AOT_AAC_SCAL;
> +            break;
> +        default:
> +            pa_log_error("Invalid AAC object type");
> +            pa_assert_not_reached();
> +    }
> +
> +    switch (AAC_GET_FREQUENCY(*config)) {
> +        case AAC_SAMPLING_FREQ_8000:
> +            sample_rate = 8000;
> +            sample_spec->rate = 8000;
> +            break;
> +        case AAC_SAMPLING_FREQ_11025:
> +            sample_rate = 11025;
> +            sample_spec->rate = 11025;
> +            break;
> +        case AAC_SAMPLING_FREQ_12000:
> +            sample_rate = 12000;
> +            sample_spec->rate = 12000;
> +            break;
> +        case AAC_SAMPLING_FREQ_16000:
> +            sample_rate = 16000;
> +            sample_spec->rate = 16000;
> +            break;
> +        case AAC_SAMPLING_FREQ_22050:
> +            sample_rate = 22050;
> +            sample_spec->rate = 22050;
> +            break;
> +        case AAC_SAMPLING_FREQ_24000:
> +            sample_rate = 24000;
> +            sample_spec->rate = 24000;
> +            break;
> +        case AAC_SAMPLING_FREQ_32000:
> +            sample_rate = 32000;
> +            sample_spec->rate = 32000;
> +            break;
> +        case AAC_SAMPLING_FREQ_44100:
> +            sample_rate = 44100;
> +            sample_spec->rate = 44100;
> +            break;
> +        case AAC_SAMPLING_FREQ_48000:
> +            sample_rate = 48000;
> +            sample_spec->rate = 48000;
> +            break;
> +        case AAC_SAMPLING_FREQ_64000:
> +            sample_rate = 64000;
> +            sample_spec->rate = 64000;
> +            break;
> +        case AAC_SAMPLING_FREQ_88200:
> +            sample_rate = 88200;
> +            sample_spec->rate = 88200;
> +            break;
> +        case AAC_SAMPLING_FREQ_96000:
> +            sample_rate = 96000;
> +            sample_spec->rate = 96000;
> +            break;
> +        default:
> +            pa_log_error("Invalid AAC frequency");
> +            pa_assert_not_reached();
> +    }
> +
> +    switch (config->channels) {
> +        case AAC_CHANNELS_1:
> +            channels = MODE_1;
> +            sample_spec->channels = 1;
> +            break;
> +        case AAC_CHANNELS_2:
> +            channels = MODE_2;
> +            sample_spec->channels = 2;
> +            break;
> +        default:
> +            pa_log_error("Invalid AAC channel mode");
> +            pa_assert_not_reached();
> +    }
> +
> +    aac_info->sample_spec = *sample_spec;
> +
> +    /* AAC SINK */
> +    if (aac_info->is_a2dp_sink) {
> +        if (!aac_info->aacdecoder_handle_opened) {
> +            aac_info->aacdecoder_handle = aacDecoder_Open(TT_MP4_LATM_MCP1, 1);
> +            aac_info->aacdecoder_handle_opened = true;
> +        }
> +
> +        pa_assert_se(AAC_DEC_OK == aacDecoder_SetParam(aac_info->aacdecoder_handle, AAC_PCM_MIN_OUTPUT_CHANNELS,
> +                                                       sample_spec->channels));
> +        pa_assert_se(AAC_DEC_OK == aacDecoder_SetParam(aac_info->aacdecoder_handle, AAC_PCM_MAX_OUTPUT_CHANNELS,
> +                                                       sample_spec->channels));
> +
> +        return;
> +    }
> +
> +
> +    /* AAC SOURCE */
> +
> +    if (!aac_info->aacenc_handle_opened) {
> +        aac_err = aacEncOpen(&aac_info->aacenc_handle, 0, 2);
> +
> +        if (aac_err != AACENC_OK) {
> +            pa_log_error("Cannot open AAC encoder handle: AAC error 0x%x", aac_err);
> +            pa_assert_not_reached();
> +        }
> +        aac_info->aacenc_handle_opened = true;
> +    }
> +
> +    aac_err = aacEncoder_SetParam(aac_info->aacenc_handle, AACENC_AOT, aot);
> +    if (aac_err != AACENC_OK)
> +        pa_assert_not_reached();
> +
> +    aac_err = aacEncoder_SetParam(aac_info->aacenc_handle, AACENC_SAMPLERATE, sample_rate);
> +    if (aac_err != AACENC_OK)
> +        pa_assert_not_reached();
> +
> +    aac_err = aacEncoder_SetParam(aac_info->aacenc_handle, AACENC_CHANNELMODE, channels);
> +    if (aac_err != AACENC_OK)
> +        pa_assert_not_reached();
> +
> +    if (config->vbr) {
> +        aac_err = aacEncoder_SetParam(aac_info->aacenc_handle, AACENC_BITRATEMODE,
> +                                      (UINT) aac_info->aac_enc_bitrate_mode);
> +        if (aac_err != AACENC_OK)
> +            pa_assert_not_reached();
> +    }
> +
> +    aac_err = aacEncoder_SetParam(aac_info->aacenc_handle, AACENC_AUDIOMUXVER, 2);
> +    if (aac_err != AACENC_OK)
> +        pa_assert_not_reached();
> +
> +    aac_err = aacEncoder_SetParam(aac_info->aacenc_handle, AACENC_SIGNALING_MODE, 1);
> +    if (aac_err != AACENC_OK)
> +        pa_assert_not_reached();
> +
> +    aac_err = aacEncoder_SetParam(aac_info->aacenc_handle, AACENC_BITRATE, aac_info->bitrate);
> +    if (aac_err != AACENC_OK)
> +        pa_assert_not_reached();
> +
> +    aac_err = aacEncoder_SetParam(aac_info->aacenc_handle, AACENC_TRANSMUX, TT_MP4_LATM_MCP1);
> +    if (aac_err != AACENC_OK)
> +        pa_assert_not_reached();
> +
> +    aac_err = aacEncoder_SetParam(aac_info->aacenc_handle, AACENC_HEADER_PERIOD, 1);
> +    if (aac_err != AACENC_OK)
> +        pa_assert_not_reached();
> +
> +    aac_err = aacEncoder_SetParam(aac_info->aacenc_handle, AACENC_AFTERBURNER, aac_info->aac_afterburner);
> +    if (aac_err != AACENC_OK)
> +        pa_assert_not_reached();
> +
> +    aac_err = aacEncEncode(aac_info->aacenc_handle, NULL, NULL, NULL, NULL);
> +    if (aac_err != AACENC_OK)
> +        pa_assert_not_reached();
> +
> +    pa_assert_se(AACENC_OK == aacEncInfo(aac_info->aacenc_handle, &aac_info->aacenc_info));
> +
> +    pa_assert(aac_info->aacenc_info.inputChannels == aac_info->sample_spec.channels);
> +
> +};
> +
> +static void pa_aac_get_read_block_size(size_t read_link_mtu, size_t *read_block_size, void **codec_data) {
> +    aac_info_t *aac_info = *codec_data;
> +    pa_assert(aac_info);
> +
> +    aac_info->mtu = read_link_mtu;
> +
> +    /* aacEncoder.pdf Section 3.2.1
> +     * AAC-LC audio frame contains 1024 PCM samples per channel */
> +    *read_block_size = 1024 * pa_frame_size(&aac_info->sample_spec);
> +    aac_info->read_block_size = *read_block_size;
> +};
> +
> +static void pa_aac_get_write_block_size(size_t write_link_mtu, size_t *write_block_size, void **codec_data) {
> +    aac_info_t *aac_info = *codec_data;
> +    pa_assert(aac_info);
> +
> +    aac_info->mtu = write_link_mtu;
> +
> +    /* aacEncoder.pdf section 3.2.1
> +     * AAC-LC audio frame contains 1024 PCM samples per channel */
> +    *write_block_size = 1024 * pa_frame_size(&aac_info->sample_spec);
> +    aac_info->write_block_size = *write_block_size;
> +};
> +
> +static void pa_aac_setup_stream(void **codec_data) {
> +    AACENC_ERROR aac_err;
> +    aac_info_t *aac_info = *codec_data;
> +    uint32_t max_bitrate;
> +    pa_assert(aac_info);
> +
> +    max_bitrate = (uint32_t) ((8 * (aac_info->mtu - sizeof(struct rtp_header)) * aac_info->sample_spec.rate) / 1024);
> +
> +    aac_info->bitrate = PA_MIN(max_bitrate, aac_info->bitrate);
> +
> +    pa_log_debug("Maximum AAC transmission bitrate: %d bps; Bitrate in use: %d bps", max_bitrate, aac_info->bitrate);
> +
> +    /* AAC SINK */
> +    if (aac_info->is_a2dp_sink) {
> +        return;
> +    }
> +
> +
> +    /* AAC SOURCE */
> +
> +    aac_err = aacEncoder_SetParam(aac_info->aacenc_handle, AACENC_BITRATE, aac_info->bitrate);
> +    if (aac_err != AACENC_OK)
> +        pa_assert_not_reached();
> +
> +    aac_err = aacEncoder_SetParam(aac_info->aacenc_handle, AACENC_PEAK_BITRATE, (UINT) max_bitrate);
> +    if (aac_err != AACENC_OK)
> +        pa_assert_not_reached();
> +
> +};
> +
> +static void pa_aac_free(void **codec_data) {
> +    aac_info_t *aac_info = *codec_data;
> +    if (!aac_info)
> +        return;
> +
> +    if (aac_info->aacenc_handle_opened)
> +        aacEncClose(&aac_info->aacenc_handle);
> +
> +    if (aac_info->aacdecoder_handle_opened)
> +        aacDecoder_Close(aac_info->aacdecoder_handle);
> +
> +    pa_xfree(aac_info);
> +    *codec_data = NULL;
> +
> +};
> +
> +static size_t pa_aac_get_capabilities(void **_capabilities) {
> +    a2dp_aac_t *capabilities = pa_xmalloc0(sizeof(a2dp_aac_t));
> +
> +    capabilities->object_type = AAC_OBJECT_TYPE_MPEG2_AAC_LC | AAC_OBJECT_TYPE_MPEG4_AAC_LC;
> +    capabilities->channels = AAC_CHANNELS_1 | AAC_CHANNELS_2;
> +    AAC_SET_BITRATE(*capabilities, AAC_DEFAULT_BITRATE);
> +    AAC_SET_FREQUENCY(*capabilities, (AAC_SAMPLING_FREQ_8000 | AAC_SAMPLING_FREQ_11025 | AAC_SAMPLING_FREQ_12000 |
> +                                      AAC_SAMPLING_FREQ_16000 | AAC_SAMPLING_FREQ_22050 | AAC_SAMPLING_FREQ_24000 |
> +                                      AAC_SAMPLING_FREQ_32000 | AAC_SAMPLING_FREQ_44100 | AAC_SAMPLING_FREQ_48000 |
> +                                      AAC_SAMPLING_FREQ_64000 | AAC_SAMPLING_FREQ_88200 | AAC_SAMPLING_FREQ_96000));
> +    capabilities->vbr = 1;
> +    *_capabilities = capabilities;
> +
> +    return sizeof(*capabilities);
> +};
> +
> +static size_t
> +pa_aac_select_configuration(const pa_sample_spec default_sample_spec, const uint8_t *supported_capabilities,
> +                            const size_t capabilities_size, void **configuration) {
> +    a2dp_aac_t *cap = (a2dp_aac_t *) supported_capabilities;
> +    a2dp_aac_t *config = pa_xmalloc0(sizeof(a2dp_aac_t));
> +    pa_a2dp_freq_cap_t aac_freq_cap, aac_freq_table[] = {
> +            {8000U,  AAC_SAMPLING_FREQ_8000},
> +            {11025U, AAC_SAMPLING_FREQ_11025},
> +            {12000U, AAC_SAMPLING_FREQ_12000},
> +            {16000U, AAC_SAMPLING_FREQ_16000},
> +            {22050U, AAC_SAMPLING_FREQ_22050},
> +            {24000U, AAC_SAMPLING_FREQ_24000},
> +            {32000U, AAC_SAMPLING_FREQ_32000},
> +            {44100U, AAC_SAMPLING_FREQ_44100},
> +            {48000U, AAC_SAMPLING_FREQ_48000},
> +            {64000U, AAC_SAMPLING_FREQ_64000},
> +            {88200U, AAC_SAMPLING_FREQ_88200},
> +            {96000U, AAC_SAMPLING_FREQ_96000}
> +    };
> +
> +    if (capabilities_size != sizeof(a2dp_aac_t))
> +        return 0;
> +
> +    if (!pa_a2dp_select_cap_frequency(AAC_GET_FREQUENCY(*cap), default_sample_spec, aac_freq_table,
> +                                      PA_ELEMENTSOF(aac_freq_table), &aac_freq_cap))
> +        return 0;
> +
> +    AAC_SET_FREQUENCY(*config, aac_freq_cap.cap);
> +
> +    AAC_SET_BITRATE(*config, AAC_GET_BITRATE(*cap));
> +
> +    if (default_sample_spec.channels <= 1) {
> +        if (cap->channels & AAC_CHANNELS_1)
> +            config->channels = AAC_CHANNELS_1;
> +        else if (cap->channels & AAC_CHANNELS_2)
> +            config->channels = AAC_CHANNELS_2;
> +        else {
> +            pa_log_error("No supported channel modes");
> +            return 0;
> +        }
> +    }
> +
> +    if (default_sample_spec.channels >= 2) {
> +        if (cap->channels & AAC_CHANNELS_2)
> +            config->channels = AAC_CHANNELS_2;
> +        else if (cap->channels & AAC_CHANNELS_1)
> +            config->channels = AAC_CHANNELS_1;
> +        else {
> +            pa_log_error("No supported channel modes");
> +            return 0;
> +        }
> +    }
> +
> +    if (cap->object_type & AAC_OBJECT_TYPE_MPEG4_AAC_LC)
> +        config->object_type = AAC_OBJECT_TYPE_MPEG4_AAC_LC;
> +    else if (cap->object_type & AAC_OBJECT_TYPE_MPEG2_AAC_LC)
> +        config->object_type = AAC_OBJECT_TYPE_MPEG2_AAC_LC;
> +    else {
> +        pa_log_error("No supported aac object type");
> +        return 0;
> +    }
> +
> +    config->vbr = cap->vbr;
> +
> +    *configuration = config;
> +    return sizeof(*config);
> +};
> +
> +static void pa_aac_free_capabilities(void **capabilities) {
> +    if (!capabilities || !*capabilities)
> +        return;
> +    pa_xfree(*capabilities);
> +    *capabilities = NULL;
> +}
> +
> +static bool pa_aac_validate_configuration(const uint8_t *selected_configuration, const size_t configuration_size) {
> +    a2dp_aac_t *c = (a2dp_aac_t *) selected_configuration;
> +
> +    if (configuration_size != sizeof(a2dp_aac_t)) {
> +        pa_log_error("AAC configuration array of invalid size");
> +        return false;
> +    }
> +
> +    switch (c->object_type) {
> +        case AAC_OBJECT_TYPE_MPEG2_AAC_LC:
> +        case AAC_OBJECT_TYPE_MPEG4_AAC_LC:
> +            break;
> +        default:
> +            pa_log_error("Invalid object type in AAC configuration");
> +            return false;
> +    }
> +
> +    switch (AAC_GET_FREQUENCY(*c)) {
> +        case AAC_SAMPLING_FREQ_8000:
> +        case AAC_SAMPLING_FREQ_11025:
> +        case AAC_SAMPLING_FREQ_12000:
> +        case AAC_SAMPLING_FREQ_16000:
> +        case AAC_SAMPLING_FREQ_22050:
> +        case AAC_SAMPLING_FREQ_24000:
> +        case AAC_SAMPLING_FREQ_32000:
> +        case AAC_SAMPLING_FREQ_44100:
> +        case AAC_SAMPLING_FREQ_48000:
> +        case AAC_SAMPLING_FREQ_64000:
> +        case AAC_SAMPLING_FREQ_88200:
> +        case AAC_SAMPLING_FREQ_96000:
> +            break;
> +        default:
> +            pa_log_error("Invalid sampling frequency in AAC configuration");
> +            return false;
> +    }
> +
> +    switch (c->channels) {
> +        case AAC_CHANNELS_1:
> +        case AAC_CHANNELS_2:
> +            break;
> +        default:
> +            pa_log_error("Invalid channel mode in AAC Configuration");
> +            return false;
> +    }
> +
> +    return true;
> +};
> +
> +
> +static pa_a2dp_source_t pa_aac_source = {
> +        .encoder_load = pa_aac_encoder_load,
> +        .init = pa_aac_encoder_init,
> +        .update_user_config = pa_aac_update_user_config,
> +        .encode = pa_aac_encode,
> +        .config_transport=pa_aac_config_transport,
> +        .get_block_size=pa_aac_get_write_block_size,
> +        .setup_stream = pa_aac_setup_stream,
> +        .set_tx_length = NULL,
> +        .decrease_quality = NULL,
> +        .free = pa_aac_free
> +};
> +
> +static pa_a2dp_sink_t pa_aac_sink = {
> +        .decoder_load = pa_aac_decoder_load,
> +        .init = pa_aac_decoder_init,
> +        .update_user_config = NULL,
> +        .config_transport=pa_aac_config_transport,
> +        .get_block_size=pa_aac_get_read_block_size,
> +        .setup_stream = pa_aac_setup_stream,
> +        .decode = pa_aac_decode,
> +        .free = pa_aac_free
> +};
> +
> +const pa_a2dp_codec_t pa_a2dp_aac = {
> +        .name = "AAC",
> +        .codec = A2DP_CODEC_MPEG24,
> +        .vendor_codec = NULL,
> +        .a2dp_sink = &pa_aac_sink,
> +        .a2dp_source = &pa_aac_source,
> +        .get_capabilities = pa_aac_get_capabilities,
> +        .select_configuration = pa_aac_select_configuration,
> +        .free_capabilities = pa_aac_free_capabilities,
> +        .free_configuration = pa_aac_free_capabilities,
> +        .validate_configuration = pa_aac_validate_configuration
> +};
> diff --git a/src/modules/bluetooth/a2dp/a2dp_util.c b/src/modules/bluetooth/a2dp/a2dp_util.c
> index cc9382f2e..edd28b5cc 100644
> --- a/src/modules/bluetooth/a2dp/a2dp_util.c
> +++ b/src/modules/bluetooth/a2dp/a2dp_util.c
> @@ -30,6 +30,9 @@
>  #define A2DP_SBC_SRC_ENDPOINT A2DP_SOURCE_ENDPOINT "/SBC"
>  #define A2DP_SBC_SNK_ENDPOINT A2DP_SINK_ENDPOINT "/SBC"
>
> +#define A2DP_AAC_SRC_ENDPOINT A2DP_SOURCE_ENDPOINT "/AAC"
> +#define A2DP_AAC_SNK_ENDPOINT A2DP_SINK_ENDPOINT "/AAC"
> +
>  #define A2DP_VENDOR_SRC_ENDPOINT A2DP_SOURCE_ENDPOINT "/VENDOR"
>  #define A2DP_VENDOR_SNK_ENDPOINT A2DP_SINK_ENDPOINT "/VENDOR"
>
> @@ -209,6 +212,14 @@ void pa_a2dp_codec_index_to_endpoint(pa_a2dp_codec_index_t codec_index, const ch
>          case PA_A2DP_SOURCE_SBC:
>              *endpoint = A2DP_SBC_SRC_ENDPOINT;
>              break;
> +#ifdef HAVE_FDK_AAC
> +        case PA_A2DP_SINK_AAC:
> +            *endpoint = A2DP_AAC_SNK_ENDPOINT;
> +            break;
> +        case PA_A2DP_SOURCE_AAC:
> +            *endpoint = A2DP_AAC_SRC_ENDPOINT;
> +            break;
> +#endif
>          default:
>              *endpoint = NULL;
>      }
> @@ -219,6 +230,12 @@ void pa_a2dp_endpoint_to_codec_index(const char *endpoint, pa_a2dp_codec_index_t
>          *codec_index = PA_A2DP_SINK_SBC;
>      else if (streq(endpoint, A2DP_SBC_SRC_ENDPOINT))
>          *codec_index = PA_A2DP_SOURCE_SBC;
> +#ifdef HAVE_FDK_AAC
> +    else if (streq(endpoint, A2DP_AAC_SNK_ENDPOINT))
> +        *codec_index = PA_A2DP_SINK_AAC;
> +    else if (streq(endpoint, A2DP_AAC_SRC_ENDPOINT))
> +        *codec_index = PA_A2DP_SOURCE_AAC;
> +#endif
>      else
>          *codec_index = PA_A2DP_CODEC_INDEX_UNAVAILABLE;
>  };
> @@ -229,6 +246,12 @@ void pa_a2dp_codec_index_to_a2dp_codec(pa_a2dp_codec_index_t codec_index, const
>          case PA_A2DP_SOURCE_SBC:
>              *a2dp_codec = &pa_a2dp_sbc;
>              break;
> +#ifdef HAVE_FDK_AAC
> +        case PA_A2DP_SINK_AAC:
> +        case PA_A2DP_SOURCE_AAC:
> +            *a2dp_codec = &pa_a2dp_aac;
> +            break;
> +#endif
>          default:
>              *a2dp_codec = NULL;
>      }
> @@ -244,6 +267,11 @@ void pa_a2dp_a2dp_codec_to_codec_index(const pa_a2dp_codec_t *a2dp_codec, bool i
>          case A2DP_CODEC_SBC:
>              *codec_index = is_a2dp_sink ? PA_A2DP_SINK_SBC : PA_A2DP_SOURCE_SBC;
>              return;
> +#ifdef HAVE_FDK_AAC
> +        case A2DP_CODEC_MPEG24:
> +            *codec_index = is_a2dp_sink ? PA_A2DP_SINK_AAC : PA_A2DP_SOURCE_AAC;
> +            return;
> +#endif
>          case A2DP_CODEC_VENDOR:
>              if (!a2dp_codec->vendor_codec) {
>                  *codec_index = PA_A2DP_CODEC_INDEX_UNAVAILABLE;
> @@ -262,6 +290,11 @@ pa_a2dp_get_a2dp_codec(uint8_t codec, const a2dp_vendor_codec_t *vendor_codec, c
>          case A2DP_CODEC_SBC:
>              *a2dp_codec = &pa_a2dp_sbc;
>              return;
> +#ifdef HAVE_FDK_AAC
> +        case A2DP_CODEC_MPEG24:
> +            *a2dp_codec = &pa_a2dp_aac;
> +            return;
> +#endif
>          case A2DP_CODEC_VENDOR:
>              if (!vendor_codec) {
>                  *a2dp_codec = NULL;
> --

Im little worried about the maintainance of a2dp_util.c if we keedp
the practice of #ifsef every codec, imo it would be much cleaner if we
just have a mechanism to register the codecs, maybe we load them as
separate plugins which then call a2dp_util.c to register their
'drivers', the plugin can check if the bluetooth plugin is loaded in
order to register itself, doing that way we can also pass the
parameter via default.pa.
On Friday 11 January 2019 14:23:21 Luiz Augusto von Dentz wrote:
> Im little worried about the maintainance of a2dp_util.c if we keedp
> the practice of #ifsef every codec, imo it would be much cleaner if we
> just have a mechanism to register the codecs, maybe we load them as
> separate plugins which then call a2dp_util.c to register their
> 'drivers', the plugin can check if the bluetooth plugin is loaded in
> order to register itself, doing that way we can also pass the
> parameter via default.pa.

Hi! I'm already working on a new version of my previous patch series.
And your above point above tons of #ifdefs is being addresses.

I though about it for a long time (heh :-) last version of my A2DP codec
patch series is from summer) and I think I have a good API where adding
new codec involves only adding new file (with all needed functions) plus
putting codec structure into one list.

So... I will rework my previous patch series, will add support for codec
switching and then I send it to mailing list.

Stay tuned...
Hi Pali,

On Fri, Jan 11, 2019 at 2:49 PM Pali Rohár <pali.rohar@gmail.com> wrote:
>
> On Friday 11 January 2019 14:23:21 Luiz Augusto von Dentz wrote:
> > Im little worried about the maintainance of a2dp_util.c if we keedp
> > the practice of #ifsef every codec, imo it would be much cleaner if we
> > just have a mechanism to register the codecs, maybe we load them as
> > separate plugins which then call a2dp_util.c to register their
> > 'drivers', the plugin can check if the bluetooth plugin is loaded in
> > order to register itself, doing that way we can also pass the
> > parameter via default.pa.
>
> Hi! I'm already working on a new version of my previous patch series.
> And your above point above tons of #ifdefs is being addresses.
>
> I though about it for a long time (heh :-) last version of my A2DP codec
> patch series is from summer) and I think I have a good API where adding
> new codec involves only adding new file (with all needed functions) plus
> putting codec structure into one list.
>
> So... I will rework my previous patch series, will add support for codec
> switching and then I send it to mailing list.

Are you changes compatibles with this changes or we have to competing solutions?
On Friday 11 January 2019 15:32:44 Luiz Augusto von Dentz wrote:
> Hi Pali,
> 
> On Fri, Jan 11, 2019 at 2:49 PM Pali Rohár <pali.rohar@gmail.com> wrote:
> >
> > On Friday 11 January 2019 14:23:21 Luiz Augusto von Dentz wrote:
> > > Im little worried about the maintainance of a2dp_util.c if we keedp
> > > the practice of #ifsef every codec, imo it would be much cleaner if we
> > > just have a mechanism to register the codecs, maybe we load them as
> > > separate plugins which then call a2dp_util.c to register their
> > > 'drivers', the plugin can check if the bluetooth plugin is loaded in
> > > order to register itself, doing that way we can also pass the
> > > parameter via default.pa.
> >
> > Hi! I'm already working on a new version of my previous patch series.
> > And your above point above tons of #ifdefs is being addresses.
> >
> > I though about it for a long time (heh :-) last version of my A2DP codec
> > patch series is from summer) and I think I have a good API where adding
> > new codec involves only adding new file (with all needed functions) plus
> > putting codec structure into one list.
> >
> > So... I will rework my previous patch series, will add support for codec
> > switching and then I send it to mailing list.
> 
> Are you changes compatibles with this changes or we have to competing solutions?

These Huang-Huang Bao patches are competing to my solution which I
already sent in summer.

But I can take additional codec supports in this series and convert them
into my. So no functionality would be lost.
On Saturday 29 December 2018 23:48:06 Huang-Huang Bao wrote:
> diff --git a/src/modules/bluetooth/a2dp/a2dp_aac.c b/src/modules/bluetooth/a2dp/a2dp_aac.c
> new file mode 100644
> index 000000000..a1d7bf1d2
> --- /dev/null
> +++ b/src/modules/bluetooth/a2dp/a2dp_aac.c
> @@ -0,0 +1,762 @@
> +/***
> +  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>
> +
> +#include <fdk-aac/aacenc_lib.h>
> +#include <fdk-aac/aacdecoder_lib.h>

This looks like license problem... I do not think that fdk-aac is
compatible with above declared LGPL 2.1.