[2/4] Add A2DP AAC codec support

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

Details

Message ID 20181213114339.9651-3-eh5@sokka.cn
State New
Series "Adds A2DP AAC, aptX(HD), LDAC codecs"
Headers show

Commit Message

Huang-Huang Bao Dec. 13, 2018, 11:43 a.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)     on
---
 configure.ac                           |  14 +-
 src/Makefile.am                        |   8 +
 src/modules/bluetooth/a2dp/a2dp-api.h  |  10 +-
 src/modules/bluetooth/a2dp/a2dp_aac.c  | 743 +++++++++++++++++++++++++
 src/modules/bluetooth/a2dp/a2dp_util.c |  33 ++
 5 files changed, 806 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 b357555d0..73fa18377 100644
--- a/src/modules/bluetooth/a2dp/a2dp-api.h
+++ b/src/modules/bluetooth/a2dp/a2dp-api.h
@@ -18,7 +18,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 */
 
@@ -31,9 +33,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..cc9437c39
--- /dev/null
+++ b/src/modules/bluetooth/a2dp/a2dp_aac.c
@@ -0,0 +1,743 @@ 
+#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)     on
+ */
+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 = {0, 0, 0};
+
+    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_set_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,
+        .set_configuration = pa_aac_set_configuration
+};
diff --git a/src/modules/bluetooth/a2dp/a2dp_util.c b/src/modules/bluetooth/a2dp/a2dp_util.c
index e73d8c8ff..297dcd940 100644
--- a/src/modules/bluetooth/a2dp/a2dp_util.c
+++ b/src/modules/bluetooth/a2dp/a2dp_util.c
@@ -12,6 +12,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"
 
@@ -191,6 +194,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;
     }
@@ -201,6 +212,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;
 };
@@ -211,6 +228,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;
     }
@@ -226,6 +249,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;
@@ -244,6 +272,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

Pali Rohár Dec. 19, 2018, 5:09 p.m.
On Thursday 13 December 2018 19:43:37 EHfive 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)     on

In my opinion, support for AAC make sense only in passthrough mode. E.g.
when your music files are already stored in AAC and you have headset
with AAC support; and you do not want to re-encode it twice.

But I do not see support for passthrough mode in this patch. It is
planned?

Maybe similar thing could be useful for MPEG1 A2DP audio codec (as lot
of people listen MP3 files), but I have not seen headset with MPEG1
support...
Huang-Huang Bao Dec. 20, 2018, 5:18 a.m.
On 12/20/18 1:09 AM, Pali Rohár wrote:
> On Thursday 13 December 2018 19:43:37 EHfive 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)     on
> In my opinion, support for AAC make sense only in passthrough mode. E.g.
> when your music files are already stored in AAC and you have headset
> with AAC support; and you do not want to re-encode it twice.

I think if we have high enough sample rate and bit depth, decode -
encode wouldn't cause sound quality loss.

>
> But I do not see support for passthrough mode in this patch. It is
> planned?

Not planned.

Besides, AAC here stands for AAC-LC, so not all AAC could do what you say.

> Maybe similar thing could be useful for MPEG1 A2DP audio codec (as lot
> of people listen MP3 files), but I have not seen headset with MPEG1
> support...
>