bluetooth: Add AAC support via GStreamer

This commit is contained in:
Sanchayan Maity 2021-01-19 17:22:13 +05:30
parent 5a03d84c32
commit b243a75f55
4 changed files with 552 additions and 2 deletions

View file

@ -795,6 +795,7 @@ if bluez5_gst_dep.found() and bluez5_gstapp_dep.found()
have_bluez5_gstreamer = true have_bluez5_gstreamer = true
cdata.set('HAVE_GSTLDAC', 1) cdata.set('HAVE_GSTLDAC', 1)
cdata.set('HAVE_GSTAPTX', 1) cdata.set('HAVE_GSTAPTX', 1)
cdata.set('HAVE_GSTAAC', 1)
endif endif
# These are required for the CMake file generation # These are required for the CMake file generation

View file

@ -0,0 +1,542 @@
/***
This file is part of PulseAudio.
Copyright (C) 2020 Asymptotic <sanchayan@asymptotic.io>
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/>.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <pulsecore/log.h>
#include <pulsecore/macro.h>
#include <pulsecore/once.h>
#include <pulse/sample.h>
#include <arpa/inet.h>
#include "a2dp-codecs.h"
#include "a2dp-codec-api.h"
#include "a2dp-codec-gst.h"
#include "rtp.h"
static bool can_be_supported(bool for_encoding) {
GstElementFactory *element_factory;
if (for_encoding) {
element_factory = gst_element_factory_find("fdkaacenc");
if (element_factory == NULL) {
pa_log_info("AAC encoder element `fdkaacenc` not found");
return false;
}
gst_object_unref(element_factory);
} else {
element_factory = gst_element_factory_find("fdkaacdec");
if (element_factory == NULL) {
pa_log_info("AAC decoder element `fdkaacdec` not found");
return false;
}
gst_object_unref(element_factory);
}
return true;
}
static bool can_accept_capabilities(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding) {
const a2dp_aac_t *capabilities = (const a2dp_aac_t *) capabilities_buffer;
if (capabilities_size != sizeof(*capabilities))
return false;
if (!(capabilities->object_type & (AAC_OBJECT_TYPE_MPEG2_AAC_LC | AAC_OBJECT_TYPE_MPEG4_AAC_LC))) {
pa_log_error("Invalid object type in AAC configuration: %d", capabilities->object_type);
return false;
}
if (!(AAC_GET_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)))
return false;
if (!(capabilities->channels & (AAC_CHANNELS_1 | AAC_CHANNELS_2)))
return false;
return true;
}
static const char *choose_remote_endpoint(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding) {
const pa_a2dp_codec_capabilities *a2dp_capabilities;
const char *key;
void *state;
/* There is no preference, just choose random valid entry */
PA_HASHMAP_FOREACH_KV(key, a2dp_capabilities, capabilities_hashmap, state) {
if (can_accept_capabilities(a2dp_capabilities->buffer, a2dp_capabilities->size, for_encoding))
return key;
}
return NULL;
}
static uint8_t fill_capabilities(uint8_t capabilities_buffer[MAX_A2DP_CAPS_SIZE]) {
a2dp_aac_t *capabilities = (a2dp_aac_t *) capabilities_buffer;
pa_zero(*capabilities);
capabilities->object_type = AAC_OBJECT_TYPE_MPEG2_AAC_LC | AAC_OBJECT_TYPE_MPEG4_AAC_LC;
capabilities->channels = AAC_CHANNELS_1 | AAC_CHANNELS_2;
capabilities->vbr = 0;
AAC_SET_BITRATE(*capabilities, 0xFFFFF);
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));
return sizeof(*capabilities);
}
static bool is_configuration_valid(const uint8_t *config_buffer, uint8_t config_size) {
const a2dp_aac_t *config = (const a2dp_aac_t *) config_buffer;
uint32_t frequency;
if (config_size != sizeof(*config)) {
pa_log_error("Invalid size of config buffer");
return false;
}
switch (config->object_type) {
/*
* AAC Long Term Prediction and AAC Scalable are not supported by the
* FDK-AAC library.
*/
case AAC_OBJECT_TYPE_MPEG4_AAC_LC:
case AAC_OBJECT_TYPE_MPEG2_AAC_LC:
break;
default:
pa_log_error("Invalid object type in AAC configuration");
return false;
}
frequency = AAC_GET_FREQUENCY(*config);
if (frequency != AAC_SAMPLING_FREQ_8000 && frequency != AAC_SAMPLING_FREQ_11025 &&
frequency != AAC_SAMPLING_FREQ_12000 && frequency != AAC_SAMPLING_FREQ_16000 &&
frequency != AAC_SAMPLING_FREQ_22050 && frequency != AAC_SAMPLING_FREQ_24000 &&
frequency != AAC_SAMPLING_FREQ_32000 && frequency != AAC_SAMPLING_FREQ_44100 &&
frequency != AAC_SAMPLING_FREQ_48000 && frequency != AAC_SAMPLING_FREQ_64000 &&
frequency != AAC_SAMPLING_FREQ_88200 && frequency != AAC_SAMPLING_FREQ_96000) {
pa_log_error("Invalid sampling frequency in configuration");
return false;
}
if (config->channels != AAC_CHANNELS_1 && config->channels != AAC_CHANNELS_2) {
pa_log_error("Invalid channel number in configuration");
return false;
}
return true;
}
static uint8_t fill_preferred_configuration(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[MAX_A2DP_CAPS_SIZE]) {
a2dp_aac_t *config = (a2dp_aac_t *) config_buffer;
const a2dp_aac_t *capabilities = (const a2dp_aac_t *) capabilities_buffer;
int i;
static const struct {
uint32_t rate;
uint32_t cap;
} freq_table[] = {
{ 8000 , AAC_SAMPLING_FREQ_8000 },
{ 11025, AAC_SAMPLING_FREQ_11025 },
{ 12000, AAC_SAMPLING_FREQ_12000 },
{ 16000, AAC_SAMPLING_FREQ_16000 },
{ 22050, AAC_SAMPLING_FREQ_22050 },
{ 24000, AAC_SAMPLING_FREQ_24000 },
{ 32000, AAC_SAMPLING_FREQ_32000 },
{ 44100, AAC_SAMPLING_FREQ_44100 },
{ 48000, AAC_SAMPLING_FREQ_48000 },
{ 64000, AAC_SAMPLING_FREQ_64000 },
{ 88200, AAC_SAMPLING_FREQ_88200 },
{ 96000, AAC_SAMPLING_FREQ_96000 }
};
if (capabilities_size != sizeof(*capabilities)) {
pa_log_error("Invalid size of capabilities buffer");
return 0;
}
pa_zero(*config);
if (capabilities->object_type & AAC_OBJECT_TYPE_MPEG2_AAC_LC)
config->object_type = AAC_OBJECT_TYPE_MPEG2_AAC_LC;
else if (capabilities->object_type & AAC_OBJECT_TYPE_MPEG4_AAC_LC)
config->object_type = AAC_OBJECT_TYPE_MPEG4_AAC_LC;
else if (capabilities->object_type & AAC_OBJECT_TYPE_MPEG4_AAC_LTP)
config->object_type = AAC_OBJECT_TYPE_MPEG4_AAC_LTP;
else if (capabilities->object_type & AAC_OBJECT_TYPE_MPEG4_AAC_SCA)
config->object_type = AAC_OBJECT_TYPE_MPEG4_AAC_SCA;
else {
pa_log_error("No supported aac object type");
return 0;
}
if (!(capabilities->channels & (AAC_CHANNELS_1 | AAC_CHANNELS_2))) {
pa_log_error("No supported channel modes");
return 0;
}
switch (default_sample_spec->channels) {
case 1:
config->channels = AAC_CHANNELS_1;
break;
case 2:
config->channels = AAC_CHANNELS_2;
break;
default:
pa_log_error("Invalid channel in default sample spec");
return 0;
}
AAC_SET_BITRATE(*config, AAC_GET_BITRATE(*capabilities));
pa_log_info("AAC bitrate in preferred configuration: %d", AAC_GET_BITRATE(*capabilities));
config->vbr = 0;
/* Find the lowest freq that is at least as high as the requested sampling rate */
for (i = 0; (unsigned) i < PA_ELEMENTSOF(freq_table); i++) {
if (freq_table[i].rate >= default_sample_spec->rate && (AAC_GET_FREQUENCY(*capabilities) & freq_table[i].cap)) {
AAC_SET_FREQUENCY((*config), freq_table[i].cap);
break;
}
}
if ((unsigned) i == PA_ELEMENTSOF(freq_table)) {
for (--i; i >= 0; i--) {
if (AAC_GET_FREQUENCY(*capabilities) & freq_table[i].cap) {
AAC_SET_FREQUENCY((*config), freq_table[i].cap);
break;
}
}
if (i < 0) {
pa_log_error("Not suitable sample rate");
return 0;
}
}
return sizeof(*config);
}
GstElement *gst_init_aac(struct gst_info *info, pa_sample_spec *ss, bool for_encoding) {
GstElement *bin, *sink, *src, *capsf;
GstCaps *caps;
GstPad *pad;
unsigned int mpegversion;
ss->format = PA_SAMPLE_S16LE;
switch (AAC_GET_FREQUENCY(*(info->a2dp_codec_t.aac_config))) {
case AAC_SAMPLING_FREQ_8000:
ss->rate = 8000u;
break;
case AAC_SAMPLING_FREQ_11025:
ss->rate = 11025u;
break;
case AAC_SAMPLING_FREQ_12000:
ss->rate = 12000u;
break;
case AAC_SAMPLING_FREQ_16000:
ss->rate = 16000u;
break;
case AAC_SAMPLING_FREQ_22050:
ss->rate = 22050u;
break;
case AAC_SAMPLING_FREQ_24000:
ss->rate = 24000u;
break;
case AAC_SAMPLING_FREQ_32000:
ss->rate = 32000u;
break;
case AAC_SAMPLING_FREQ_44100:
ss->rate = 44100u;
break;
case AAC_SAMPLING_FREQ_48000:
ss->rate = 48000u;
break;
case AAC_SAMPLING_FREQ_64000:
ss->rate = 64000u;
break;
case AAC_SAMPLING_FREQ_88200:
ss->rate = 88200u;
break;
case AAC_SAMPLING_FREQ_96000:
ss->rate = 96000u;
break;
default:
pa_log_error("Invalid AAC frequency configuration");
goto fail;
}
switch (info->a2dp_codec_t.aac_config->channels) {
case AAC_CHANNELS_1:
ss->channels = 1;
break;
case AAC_CHANNELS_2:
ss->channels = 2;
break;
default:
pa_log_error("Invalid AAC channel configuration");
goto fail;
}
/*
* As per section 4.5.4 Media Payload Format of A2DP profile, MPEG-2,4
* AAC uses the media payload format defined in RFC3016. The specification
* defines the payload format only for MPEG-4 audio; in use of MPEG-2
* AAC LC, the audio stream shall be transformed to MPEG-4 AAC LC in
* the SRC by modifying the codec information and adapted into MPEG-4
* LATM format before being put into Media Payload Format. The SNK
* shall retransform the stream into MPEG-2 AAC LC, if necessary.
*
* As a result, even if we get MPEG2 AAC LC as the object type, we
* keep the MPEG version as 4 in the caps below and use LATM-MCP1.
*/
if (info->a2dp_codec_t.aac_config->object_type == AAC_OBJECT_TYPE_MPEG2_AAC_LC)
mpegversion = 2;
else if (info->a2dp_codec_t.aac_config->object_type == AAC_OBJECT_TYPE_MPEG4_AAC_LC)
mpegversion = 4;
else {
pa_log_error("Unknown codec object type %#x", info->a2dp_codec_t.aac_config->object_type);
goto fail;
}
pa_log_debug("Got object type MPEG%d AAC LC", mpegversion);
capsf = gst_element_factory_make("capsfilter", "aac_capsfilter");
if (!capsf) {
pa_log_error("Could not create AAC capsfilter element");
goto fail;
}
caps = gst_caps_new_simple("audio/mpeg",
"mpegversion", G_TYPE_INT, (int) 4,
"rate", G_TYPE_INT, (int) ss->rate,
"channels", G_TYPE_INT, (int) ss->channels,
"stream-format", G_TYPE_STRING, "latm-mcp1",
NULL);
g_object_set(capsf, "caps", caps, NULL);
gst_caps_unref(caps);
if (for_encoding) {
guint bitrate;
sink = gst_element_factory_make("fdkaacenc", "aac_enc");
src = capsf;
if (!sink) {
pa_log_error("Could not create AAC encoder element");
goto fail_enc_dec;
}
/*
* General negotiated MTU for bluetooth seems to be 894/895. Hardcode
* this for now. Ideally MTU would have been provided to us at init,
* but, the get_block_size function is called later in the current
* code flow path.
*
* We cannot handle fragmentation. Fix the bitrate to not overshoot
* the MTU. Any greater than the calculated value here or above 320
* Kbps will result in payloads > MTU = 894.
*/
bitrate = ((894 - sizeof(struct rtp_header)) * 8 * ss->rate) / 1024;
bitrate = PA_MIN(bitrate, AAC_GET_BITRATE(*(info->a2dp_codec_t.aac_config)));
/*
* Note that it has been observed that some devices do not work if
* header-period is not set to this value. We enable afterburner here
* for better quality.
*
* For a value of '0', for the bitrate, the GStreamer fdkaac element
* will decide the bitrate based on the recommended bitrate and
* sampling combinations as per below.
* http://wiki.hydrogenaud.io/index.php?title=Fraunhofer_FDK_AAC#Recommended_Sampling_Rate_and_Bitrate_Combinations
*
* We set peak bitrate to fix the maximum bits per audio frame. While
* the library mentions this will affect the audio quality by a large
* amount, considering bluetooth bandwidth we need to set this. We do
* not handle fragmentation and this combined with the bitrate
* calculation above, should make sure we not do overshoot above MTU.
*/
g_object_set(sink, "bitrate", bitrate /* CBR */, "peak-bitrate", bitrate, "header-period", 1, "afterburner", TRUE, NULL);
bin = gst_bin_new("aac_enc_bin");
} else {
sink = capsf;
src = gst_element_factory_make("fdkaacdec", "aac_dec");
if (!src) {
pa_log_error("Could not create AAC decoder element");
goto fail_enc_dec;
}
bin = gst_bin_new("aac_dec_bin");
}
pa_assert(bin);
gst_bin_add_many(GST_BIN(bin), sink, src, NULL);
pa_assert_se(gst_element_link_many(sink, src, NULL));
pad = gst_element_get_static_pad(sink, "sink");
pa_assert_se(gst_element_add_pad(bin, gst_ghost_pad_new("sink", pad)));
gst_object_unref(GST_OBJECT(pad));
pad = gst_element_get_static_pad(src, "src");
pa_assert_se(gst_element_add_pad(bin, gst_ghost_pad_new("src", pad)));
gst_object_unref(GST_OBJECT(pad));
return bin;
fail_enc_dec:
gst_object_unref(GST_OBJECT(capsf));
fail:
pa_log_error("AAC encoder initialisation failed");
return NULL;
}
static void *init(bool for_encoding, bool for_backchannel, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *sample_spec, pa_core *core) {
GstElement *bin;
struct gst_info *info = NULL;
info = pa_xnew0(struct gst_info, 1);
pa_assert(info);
info->core = core;
info->ss = sample_spec;
info->codec_type = AAC;
info->a2dp_codec_t.aac_config = (const a2dp_aac_t *) config_buffer;
pa_assert(config_size == sizeof(*(info->a2dp_codec_t.aac_config)));
if (!(bin = gst_init_aac(info, sample_spec, for_encoding)))
goto fail;
if (!gst_codec_init(info, for_encoding, bin))
goto fail;
return info;
fail:
if (info)
pa_xfree(info);
return NULL;
}
static void deinit(void *codec_info) {
return gst_codec_deinit(codec_info);
}
static int reset(void *codec_info) {
struct gst_info *info = (struct gst_info *) codec_info;
info->seq_num = 0;
return 0;
}
static size_t get_block_size(void *codec_info, size_t link_mtu) {
struct gst_info *info = (struct gst_info *) codec_info;
/* aacEncoder.pdf Section 3.2.1
* AAC-LC audio frame contains 1024 PCM samples per channel */
return 1024 * pa_frame_size(info->ss);
}
static size_t reduce_encoder_bitrate(void *codec_info, size_t write_link_mtu) {
return 0;
}
static size_t encode_buffer(void *codec_info, uint32_t timestamp, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size, size_t *processed) {
struct gst_info *info = (struct gst_info *) codec_info;
struct rtp_header *header;
size_t written;
if (PA_UNLIKELY(output_size < sizeof(*header))) {
*processed = 0;
return 0;
}
written = gst_transcode_buffer(codec_info, input_buffer, input_size, output_buffer + sizeof(*header), output_size - sizeof(*header), processed);
if (PA_LIKELY(written > 0)) {
header = (struct rtp_header *) output_buffer;
pa_zero(*header);
header->v = 2;
header->pt = 96;
header->sequence_number = htons(info->seq_num++);
header->timestamp = htonl(timestamp);
header->ssrc = htonl(1);
written += sizeof(*header);
}
return written;
}
static size_t decode_buffer(void *codec_info, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size, size_t *processed) {
struct rtp_header *header;
size_t written;
if (PA_UNLIKELY(input_size < sizeof(*header))) {
*processed = 0;
return 0;
}
header = (struct rtp_header *) input_buffer;
written = gst_transcode_buffer(codec_info, input_buffer + sizeof(*header), input_size - sizeof(*header), output_buffer, output_size, processed);
*processed += sizeof(*header);
return written;
}
const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_aac = {
.id = { A2DP_CODEC_MPEG24, 0, 0 },
.support_backchannel = false,
.can_be_supported = can_be_supported,
.can_accept_capabilities = can_accept_capabilities,
.choose_remote_endpoint = choose_remote_endpoint,
.fill_capabilities = fill_capabilities,
.is_configuration_valid = is_configuration_valid,
.fill_preferred_configuration = fill_preferred_configuration,
.bt_codec = {
.name = "aac",
.description = "Advanced Audio Coding (AAC)",
.init = init,
.deinit = deinit,
.reset = reset,
.get_read_block_size = get_block_size,
.get_write_block_size = get_block_size,
.reduce_encoder_bitrate = reduce_encoder_bitrate,
.encode_buffer = encode_buffer,
.decode_buffer = decode_buffer,
},
};

View file

@ -23,7 +23,7 @@
#include <pulsecore/core.h> #include <pulsecore/core.h>
#include <pulsecore/core-util.h> #include <pulsecore/core-util.h>
#if defined(HAVE_GSTAPTX) || defined(HAVE_GSTLDAC) #if defined(HAVE_GSTAPTX) || defined(HAVE_GSTLDAC) || defined(HAVE_GSTAAC)
#include <gst/gst.h> #include <gst/gst.h>
#endif #endif
@ -47,6 +47,9 @@ extern const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_sbc_xq_552;
extern const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_aptx; extern const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_aptx;
extern const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_aptx_hd; extern const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_aptx_hd;
#endif #endif
#ifdef HAVE_GSTAAC
extern const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_aac;
#endif
#ifdef HAVE_GSTLDAC #ifdef HAVE_GSTLDAC
extern const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_ldac_eqmid_hq; extern const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_ldac_eqmid_hq;
extern const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_ldac_eqmid_sq; extern const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_ldac_eqmid_sq;
@ -64,6 +67,9 @@ static const pa_a2dp_endpoint_conf *pa_a2dp_endpoint_configurations[] = {
#ifdef HAVE_GSTAPTX #ifdef HAVE_GSTAPTX
&pa_a2dp_endpoint_conf_aptx_hd, &pa_a2dp_endpoint_conf_aptx_hd,
&pa_a2dp_endpoint_conf_aptx, &pa_a2dp_endpoint_conf_aptx,
#endif
#ifdef HAVE_GSTAAC
&pa_a2dp_endpoint_conf_aac,
#endif #endif
&pa_a2dp_endpoint_conf_sbc, &pa_a2dp_endpoint_conf_sbc,
&pa_a2dp_endpoint_conf_sbc_xq_453, &pa_a2dp_endpoint_conf_sbc_xq_453,
@ -113,7 +119,7 @@ const pa_a2dp_endpoint_conf *pa_bluetooth_get_a2dp_endpoint_conf(const char *nam
} }
void pa_bluetooth_a2dp_codec_gst_init(void) { void pa_bluetooth_a2dp_codec_gst_init(void) {
#if defined(HAVE_GSTAPTX) || defined(HAVE_GSTLDAC) #if defined(HAVE_GSTAPTX) || defined(HAVE_GSTLDAC) || defined(HAVE_GSTAAC)
GError *error = NULL; GError *error = NULL;
if (!gst_init_check(NULL, NULL, &error)) { if (!gst_init_check(NULL, NULL, &error)) {

View file

@ -27,6 +27,7 @@ if have_bluez5_gstreamer
libbluez5_util_sources += [ 'a2dp-codec-gst.c' ] libbluez5_util_sources += [ 'a2dp-codec-gst.c' ]
libbluez5_util_sources += [ 'a2dp-codec-ldac-gst.c' ] libbluez5_util_sources += [ 'a2dp-codec-ldac-gst.c' ]
libbluez5_util_sources += [ 'a2dp-codec-aptx-gst.c' ] libbluez5_util_sources += [ 'a2dp-codec-aptx-gst.c' ]
libbluez5_util_sources += [ 'a2dp-codec-aac-gst.c' ]
endif endif
libbluez5_util = shared_library('bluez5-util', libbluez5_util = shared_library('bluez5-util',