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; -- 2.19.2 _______________________________________________ pulseaudio-discuss mailing list pulseaudio-discuss@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss