mirror of
https://gitlab.freedesktop.org/pulseaudio/pulseaudio.git
synced 2026-04-06 07:15:41 -04:00
bluetooth: Add AAC support via GStreamer
This commit is contained in:
parent
5a03d84c32
commit
b243a75f55
4 changed files with 552 additions and 2 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
542
src/modules/bluetooth/a2dp-codec-aac-gst.c
Normal file
542
src/modules/bluetooth/a2dp-codec-aac-gst.c
Normal 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,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -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)) {
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue