Optional (build with --disable-bluez5-aptx-codec) --- configure.ac | 18 +- src/Makefile.am | 8 + src/modules/bluetooth/a2dp/a2dp-api.h | 12 + src/modules/bluetooth/a2dp/a2dp-codecs.h | 3 + src/modules/bluetooth/a2dp/a2dp_aptx.c | 609 +++++++++++++++++++++++ src/modules/bluetooth/a2dp/a2dp_util.c | 60 +++ 6 files changed, 709 insertions(+), 1 deletion(-) create mode 100644 src/modules/bluetooth/a2dp/a2dp_aptx.c diff --git a/configure.ac b/configure.ac index 0f9d7fb6c..586077e5b 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 and FDK-AAC) #### +#### BlueZ support (optional, dependent on D-Bus, SBC, FDK-AAC and FFmpeg) #### AC_ARG_ENABLE([bluez5], AS_HELP_STRING([--disable-bluez5],[Disable optional BlueZ 5 support])) @@ -1093,6 +1093,20 @@ 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])) +## aptX, aptX HD support (FFmpeg libavcodec) ## +AC_ARG_ENABLE([bluez5-aptx-codec], + AS_HELP_STRING([--disable-bluez5-aptx-codec],[Disable optional A2DP aptX, aptX HD codecs support (Bluez 5)])) +AS_IF([test "x$HAVE_BLUEZ_5" = "x1" && test "x$enable_bluez5_aptx_codec" != "xno"], + [PKG_CHECK_MODULES(FF_AVCODEC, [ libavcodec >= 58.18.100 ], HAVE_FF_AVCODEC=1, HAVE_FF_AVCODEC=0)], + HAVE_FF_AVCODEC=0) +AS_IF([test "x$HAVE_BLUEZ_5" = "x1" && test "x$enable_bluez5_aptx_codec" != "xno"], + [PKG_CHECK_MODULES(FF_AVUTIL, [ libavcodec >= 56.14.100 ], HAVE_FF_AVUTIL=1, HAVE_FF_AVUTIL=0)], + HAVE_FF_AVUTIL=0) +AS_IF([test "x$HAVE_FF_AVCODEC" = "x1" && test "x$HAVE_FF_AVUTIL" = "x1"], HAVE_FF_APTX=1, HAVE_FF_APTX=0) +AC_SUBST(HAVE_FF_APTX) +AM_CONDITIONAL([HAVE_FF_APTX], [test "x$HAVE_FF_APTX" = x1]) +AS_IF([test "x$HAVE_FF_APTX" = "x1"], AC_DEFINE([HAVE_FF_APTX], 1, [Bluez 5 A2DP aptX, aptX HD codecs enabled])) + ## Bluetooth Headset profiles backend ## AC_ARG_ENABLE([bluez5-ofono-headset], @@ -1598,6 +1612,7 @@ AS_IF([test "x$HAVE_SYSTEMD_LOGIN" = "x1"], ENABLE_SYSTEMD_LOGIN=yes, ENABLE_SYS 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_FF_APTX" = "x1"], ENABLE_BLUEZ_5_APTX_CODEC=yes, ENABLE_BLUEZ_5_APTX_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) @@ -1657,6 +1672,7 @@ echo " Enable D-Bus: ${ENABLE_DBUS} Enable BlueZ 5: ${ENABLE_BLUEZ_5} Enable A2DP AAC codec: ${ENABLE_BLUEZ_5_AAC_CODEC} + Enable A2DP aptX(HD): ${ENABLE_BLUEZ_5_APTX_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 c44a65f05..5bb43c3a6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -2132,6 +2132,10 @@ if HAVE_FDK_AAC libbluez5_util_la_SOURCES += \ modules/bluetooth/a2dp/a2dp_aac.c endif +if HAVE_FF_APTX +libbluez5_util_la_SOURCES += \ + modules/bluetooth/a2dp/a2dp_aptx.c +endif if HAVE_BLUEZ_5_OFONO_HEADSET libbluez5_util_la_SOURCES += \ modules/bluetooth/backend-ofono.c @@ -2148,6 +2152,10 @@ if HAVE_FDK_AAC libbluez5_util_la_LIBADD += $(FDK_AAC_LIBS) libbluez5_util_la_CFLAGS += $(FDK_AAC_CFLAGS) endif +if HAVE_FF_APTX +libbluez5_util_la_LIBADD += $(FF_AVCODEC_LIBS) $(FF_AVUTIL_LIBS) +libbluez5_util_la_CFLAGS += $(FF_AVCODEC_CFLAGS) $(FF_AVUTIL_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 73fa18377..8fa7c79d5 100644 --- a/src/modules/bluetooth/a2dp/a2dp-api.h +++ b/src/modules/bluetooth/a2dp/a2dp-api.h @@ -21,6 +21,10 @@ extern const pa_a2dp_codec_t pa_a2dp_sbc; #ifdef HAVE_FDK_AAC extern const pa_a2dp_codec_t pa_a2dp_aac; #endif +#ifdef HAVE_FF_APTX +extern const pa_a2dp_codec_t pa_a2dp_aptx; +extern const pa_a2dp_codec_t pa_a2dp_aptx_hd; +#endif /* Run from <pa_a2dp_sink_t>.encode */ @@ -35,12 +39,20 @@ typedef enum pa_a2dp_codec_index { PA_A2DP_SINK_SBC, #ifdef HAVE_FDK_AAC PA_A2DP_SINK_AAC, +#endif +#ifdef HAVE_FF_APTX + PA_A2DP_SINK_APTX, + PA_A2DP_SINK_APTX_HD, #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 +#ifdef HAVE_FF_APTX + PA_A2DP_SOURCE_APTX, + PA_A2DP_SOURCE_APTX_HD, #endif PA_A2DP_SOURCE_MAX, PA_A2DP_CODEC_INDEX_UNAVAILABLE diff --git a/src/modules/bluetooth/a2dp/a2dp-codecs.h b/src/modules/bluetooth/a2dp/a2dp-codecs.h index aefafb635..a6f054aec 100644 --- a/src/modules/bluetooth/a2dp/a2dp-codecs.h +++ b/src/modules/bluetooth/a2dp/a2dp-codecs.h @@ -141,6 +141,9 @@ #define APTX_SAMPLING_FREQ_44100 0x02 #define APTX_SAMPLING_FREQ_48000 0x01 +#define APTX_HD_VENDOR_ID 0x000000D7 +#define APTX_HD_CODEC_ID 0x0024 + #define LDAC_VENDOR_ID 0x0000012d #define LDAC_CODEC_ID 0x00aa diff --git a/src/modules/bluetooth/a2dp/a2dp_aptx.c b/src/modules/bluetooth/a2dp/a2dp_aptx.c new file mode 100644 index 000000000..a6e65d04a --- /dev/null +++ b/src/modules/bluetooth/a2dp/a2dp_aptx.c @@ -0,0 +1,609 @@ + +#include <arpa/inet.h> +#include <string.h> + +#include <libavcodec/avcodec.h> +#include <libavutil/samplefmt.h> + +#include <pulse/xmalloc.h> + +#include "a2dp-api.h" + + +#define streq(a, b) (!strcmp((a),(b))) + + +typedef struct aptx_info { + pa_a2dp_source_read_cb_t read_pcm; + pa_a2dp_source_read_buf_free_cb_t read_buf_free; + + bool is_a2dp_sink; + bool is_hd; + + size_t aptx_frame_size; + int nb_samples; + const AVCodec *av_codec; + AVCodecContext *av_codec_ctx; + int channel_mode; + uint16_t seq_num; + + + size_t block_size; + + +} aptx_info_t; + +static const AVCodec *av_codec_aptx_decoder = NULL; +static const AVCodec *av_codec_aptx_encoder = NULL; + +static const AVCodec *av_codec_aptx_hd_decoder = NULL; +static const AVCodec *av_codec_aptx_hd_encoder = NULL; + + +static bool pa_aptx_decoder_load() { + if (av_codec_aptx_decoder) + return true; + av_codec_aptx_decoder = avcodec_find_decoder(AV_CODEC_ID_APTX); + if (!av_codec_aptx_decoder) { + pa_log_debug("Cannot find aptX decoder in FFmpeg avcodec library"); + return false; + } + + return true; +} + +static bool pa_aptx_encoder_load() { + if (av_codec_aptx_encoder) + return true; + av_codec_aptx_encoder = avcodec_find_encoder(AV_CODEC_ID_APTX); + if (!av_codec_aptx_encoder) { + pa_log_debug("Cannot find aptX encoder in FFmpeg avcodec library"); + return false; + } + + return true; +} + +static bool pa_aptx_hd_decoder_load() { + if (av_codec_aptx_hd_decoder) + return true; + av_codec_aptx_hd_decoder = avcodec_find_decoder(AV_CODEC_ID_APTX_HD); + if (!av_codec_aptx_hd_decoder) { + pa_log_debug("Cannot find aptX HD decoder in FFmpeg avcodec library"); + return false; + } + + return true; +} + +static bool pa_aptx_hd_encoder_load() { + if (av_codec_aptx_hd_encoder) + return true; + av_codec_aptx_hd_encoder = avcodec_find_encoder(AV_CODEC_ID_APTX_HD); + if (!av_codec_aptx_hd_encoder) { + pa_log_debug("Cannot find aptX HD encoder in FFmpeg avcodec library"); + return false; + } + + return true; +} + +static bool _internal_pa_dual_decoder_init(bool is_hd, void **codec_data) { + aptx_info_t *info = pa_xmalloc0(sizeof(aptx_info_t)); + *codec_data = info; + info->is_hd = is_hd; + info->is_a2dp_sink = true; + info->aptx_frame_size = is_hd ? 6 : 4; + info->av_codec = is_hd ? av_codec_aptx_hd_decoder : av_codec_aptx_decoder; + return true; +} + +static bool +_internal_pa_dual_encoder_init(bool is_hd, pa_a2dp_source_read_cb_t read_cb, pa_a2dp_source_read_buf_free_cb_t free_cb, + void **codec_data) { + aptx_info_t *info = pa_xmalloc0(sizeof(aptx_info_t)); + *codec_data = info; + info->is_hd = is_hd; + info->is_a2dp_sink = false; + info->read_pcm = read_cb; + info->read_buf_free = free_cb; + info->aptx_frame_size = is_hd ? 6 : 4; + info->av_codec = is_hd ? av_codec_aptx_hd_encoder : av_codec_aptx_encoder; + return true; +} + +static int pa_dual_update_user_config(pa_proplist *user_config, void **codec_data) { + return 0; +} + +static size_t +pa_dual_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; + void *p; + int ret; + size_t i; + AVPacket *pkt; + size_t to_decode; + size_t total_written = 0; + AVFrame *av_frame = NULL; + aptx_info_t *aptx_info = *codec_data; + + + pa_assert(aptx_info); + pa_assert(aptx_info->av_codec); + pa_assert(aptx_info->av_codec_ctx); + + if (aptx_info->is_hd) { + header = read_buf; + + *timestamp = ntohl(header->timestamp); + + p = (uint8_t *) read_buf + sizeof(*header); + to_decode = read_buf_size - sizeof(*header); + } else { + *timestamp = (uint32_t) - 1; + + p = (uint8_t *) read_buf; + to_decode = read_buf_size; + } + + av_frame = av_frame_alloc(); + pkt = av_packet_alloc(); + pkt->data = p; + pkt->size = (int) to_decode; + + + *_decoded = 0; + + ret = avcodec_send_packet(aptx_info->av_codec_ctx, pkt); + if (PA_UNLIKELY(ret < 0)) { + pa_log_debug("Error submitting the packet to the aptX decoder"); + goto done; + } + ret = avcodec_receive_frame(aptx_info->av_codec_ctx, av_frame); + if (PA_UNLIKELY(ret < 0)) { + pa_log_debug("Error during aptX decoding"); + goto done; + } + + *_decoded = aptx_info->aptx_frame_size * av_frame->nb_samples / 4; + + total_written = (size_t) av_frame->nb_samples * (4 * 2); + + pa_assert_fp(_decoded <= read_buf_size); + pa_assert_fp(total_written <= write_buf_size); + + for (i = 0; i < av_frame->nb_samples * sizeof(uint32_t); i += sizeof(uint32_t)) { + memcpy((uint8_t *) write_buf + i * 2, av_frame->data[0] + i, sizeof(uint32_t)); + memcpy((uint8_t *) write_buf + i * 2 + sizeof(uint32_t), av_frame->data[1] + i, sizeof(uint32_t)); + } + +done: + av_frame_free(&av_frame); + av_packet_free(&pkt); + return total_written; +} + +static size_t +pa_dual_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; + void *d; + const void *p; + AVFrame *av_frame; + AVPacket *pkt; + aptx_info_t *aptx_info = *codec_data; + int ret; + size_t i; + + pa_assert(aptx_info); + pa_assert(aptx_info->av_codec); + pa_assert(aptx_info->av_codec_ctx); + + pa_assert_fp(aptx_info->av_codec_ctx->frame_size <= write_buf_size); + + aptx_info->read_pcm(&p, (size_t) aptx_info->block_size, read_cb_data); + + if (aptx_info->is_hd) { + header = write_buf; + memset(write_buf, 0, sizeof(*header)); + header->v = 2; + header->pt = 1; + header->sequence_number = htons(aptx_info->seq_num++); + header->timestamp = htonl(timestamp); + header->ssrc = htonl(1); + d = (uint8_t *) write_buf + sizeof(*header); + } else { + d = (uint8_t *) write_buf; + } + + av_frame = av_frame_alloc(); + av_frame->nb_samples = aptx_info->nb_samples; + av_frame->format = aptx_info->av_codec_ctx->sample_fmt; + av_frame->channel_layout = aptx_info->av_codec_ctx->channel_layout; + + pkt = av_packet_alloc(); + + pa_assert_se(av_frame_get_buffer(av_frame, 0) >= 0); + pa_assert_se(av_frame_make_writable(av_frame) >= 0); + + + for (i = 0; i < av_frame->nb_samples * sizeof(uint32_t); i += sizeof(uint32_t)) { + memcpy(av_frame->data[0] + i, (uint8_t *) p + i * 2, sizeof(uint32_t)); + memcpy(av_frame->data[1] + i, (uint8_t *) p + i * 2 + sizeof(uint32_t), sizeof(uint32_t)); + } + *_encoded = 0; + + ret = avcodec_send_frame(aptx_info->av_codec_ctx, av_frame); + + if (PA_UNLIKELY(ret < 0)) { + fprintf(stderr, "Error sending the frame to the aptX encoder\n"); + nbytes = 0; + goto done; + } + + ret = avcodec_receive_packet(aptx_info->av_codec_ctx, pkt); + + if (PA_UNLIKELY(ret != 0)) { + fprintf(stderr, "Error receiving the packet from the aptX encoder\n"); + nbytes = 0; + goto done; + } + + memcpy(d, pkt->data, (size_t) pkt->size); + d = (uint8_t *) d + pkt->size; + + nbytes = (uint8_t *) d - (uint8_t *) write_buf; + *_encoded += aptx_info->block_size; + +done: + av_frame_free(&av_frame); + av_packet_free(&pkt); + aptx_info->read_buf_free(&p, read_cb_data); + return nbytes; +} + +static void +pa_dual_config_transport(pa_sample_spec default_sample_spec, const void *configuration, size_t configuration_size, + pa_sample_spec *sample_spec, void **codec_data) { + aptx_info_t *aptx_info = *codec_data; + a2dp_aptx_t *config = (a2dp_aptx_t *) configuration; + AVCodecContext *aptx_ctx; + pa_assert(aptx_info); + pa_assert(aptx_info->av_codec); + pa_assert_se(configuration_size == (aptx_info->is_hd ? sizeof(a2dp_aptxhd_t) : sizeof(a2dp_aptx_t))); + + if (aptx_info->av_codec_ctx) + avcodec_free_context(&aptx_info->av_codec_ctx); + + aptx_info->av_codec_ctx = avcodec_alloc_context3(aptx_info->av_codec); + aptx_ctx = aptx_info->av_codec_ctx; + + aptx_ctx->sample_fmt = AV_SAMPLE_FMT_S32P; + sample_spec->format = PA_SAMPLE_S32LE; + + switch (config->frequency) { + case APTX_SAMPLING_FREQ_16000: + aptx_ctx->sample_rate = 16000; + aptx_ctx->bit_rate = 16000; + sample_spec->rate = 16000U; + break; + case APTX_SAMPLING_FREQ_32000: + aptx_ctx->sample_rate = 32000; + aptx_ctx->bit_rate = 32000; + sample_spec->rate = 32000U; + break; + case APTX_SAMPLING_FREQ_44100: + aptx_ctx->sample_rate = 44100; + aptx_ctx->bit_rate = 44100; + sample_spec->rate = 44100U; + break; + case APTX_SAMPLING_FREQ_48000: + aptx_ctx->sample_rate = 48000; + aptx_ctx->bit_rate = 48000; + sample_spec->rate = 48000U; + break; + default: + pa_assert_not_reached(); + } + + switch (config->channel_mode) { + case APTX_CHANNEL_MODE_STEREO: + aptx_ctx->channel_layout = AV_CH_LAYOUT_STEREO; + aptx_ctx->channels = 2; + sample_spec->channels = 2; + break; + default: + pa_assert_not_reached(); + } + + pa_assert_se(avcodec_open2(aptx_info->av_codec_ctx, aptx_info->av_codec, NULL) == 0); + +}; + +static void pa_dual_get_read_block_size(size_t read_link_mtu, size_t *read_block_size, void **codec_data) { + aptx_info_t *aptx_info = *codec_data; + size_t aptx_frame_size = aptx_info->aptx_frame_size; + size_t rtp_use_size = aptx_info->is_hd ? sizeof(struct rtp_header) : 0; + pa_assert(aptx_info); + + /* + * PCM 32-bit, 2 channel (4 bytes * 2) + * PCM frames/APTX frames == 4 + * */ + *read_block_size = (read_link_mtu - rtp_use_size) / aptx_frame_size * (4 * 2) * 4; + aptx_info->block_size = *read_block_size; +}; + +static void pa_dual_get_write_block_size(size_t write_link_mtu, size_t *write_block_size, void **codec_data) { + aptx_info_t *aptx_info = *codec_data; + size_t aptx_frame_size = aptx_info->aptx_frame_size; + size_t rtp_use_size = aptx_info->is_hd ? sizeof(struct rtp_header) : 0; + pa_assert(aptx_info); + + /* + * PCM 32-bit, 2 channel (4 bytes * 2) + * PCM frames/APTX frames == 4 + * */ + *write_block_size = (write_link_mtu - rtp_use_size) / aptx_frame_size * (4 * 2) * 4; + aptx_info->block_size = *write_block_size; +}; + + +static void pa_dual_setup_stream(void **codec_data) { + aptx_info_t *aptx_info = *codec_data; + pa_assert(aptx_info); + aptx_info->nb_samples = (int) (aptx_info->block_size / (4 * 2)); + aptx_info->av_codec_ctx->frame_size = (int) (aptx_info->aptx_frame_size * aptx_info->nb_samples / 4); +}; + +static void pa_dual_free(void **codec_data) { + aptx_info_t *aptx_info = *codec_data; + if (!aptx_info) + return; + if (aptx_info->av_codec_ctx) + avcodec_free_context(&aptx_info->av_codec_ctx); + pa_xfree(aptx_info); + *codec_data = NULL; + +}; + +static size_t _internal_pa_dual_get_capabilities(bool is_hd, void **_capabilities) { + + const size_t cap_size = is_hd ? sizeof(a2dp_aptxhd_t) : sizeof(a2dp_aptx_t); + a2dp_aptx_t *capabilities = (a2dp_aptx_t *) pa_xmalloc0(cap_size); + + if (is_hd) { + capabilities->info.vendor_id = APTX_HD_VENDOR_ID; + capabilities->info.codec_id = APTX_HD_CODEC_ID; + } else { + capabilities->info.vendor_id = APTX_VENDOR_ID; + capabilities->info.codec_id = APTX_CODEC_ID; + } + + capabilities->channel_mode = APTX_CHANNEL_MODE_STEREO; + capabilities->frequency = APTX_SAMPLING_FREQ_16000 | APTX_SAMPLING_FREQ_32000 | APTX_SAMPLING_FREQ_44100 | + APTX_SAMPLING_FREQ_48000; + *_capabilities = capabilities; + + return cap_size; +}; + + +static size_t +_internal_pa_dual_select_configuration(bool is_hd, const pa_sample_spec default_sample_spec, + const uint8_t *supported_capabilities, + const size_t capabilities_size, void **configuration) { + a2dp_aptx_t *cap; + a2dp_aptx_t *config; + const size_t cap_size = is_hd ? sizeof(a2dp_aptxhd_t) : sizeof(a2dp_aptx_t); + pa_a2dp_freq_cap_t aptx_freq_cap, aptx_freq_table[] = { + {16000U, APTX_SAMPLING_FREQ_16000}, + {32000U, APTX_SAMPLING_FREQ_32000}, + {44100U, APTX_SAMPLING_FREQ_44100}, + {48000U, APTX_SAMPLING_FREQ_48000} + }; + + cap = (a2dp_aptx_t *) supported_capabilities; + + if (capabilities_size != cap_size) + return 0; + + config = (a2dp_aptx_t *) pa_xmalloc0(cap_size); + + if (is_hd) { + config->info.vendor_id = APTX_HD_VENDOR_ID; + config->info.codec_id = APTX_HD_CODEC_ID; + } else { + config->info.vendor_id = APTX_VENDOR_ID; + config->info.codec_id = APTX_CODEC_ID; + } + + if (!pa_a2dp_select_cap_frequency(cap->frequency, default_sample_spec, aptx_freq_table, + PA_ELEMENTSOF(aptx_freq_table), &aptx_freq_cap)) + return 0; + + config->frequency = (uint8_t) aptx_freq_cap.cap; + + if (cap->channel_mode & APTX_CHANNEL_MODE_STEREO) + config->channel_mode = APTX_CHANNEL_MODE_STEREO; + else { + pa_log_error("No supported channel modes"); + return 0; + } + + *configuration = config; + return cap_size; +}; + +static void pa_dual_free_capabilities(void **capabilities) { + if (!capabilities || !*capabilities) + return; + pa_xfree(*capabilities); + *capabilities = NULL; +} + +static bool _internal_pa_dual_set_configuration(bool is_hd, const uint8_t *selected_configuration, + const size_t configuration_size) { + a2dp_aptx_t *c = (a2dp_aptx_t *) selected_configuration; + + if (configuration_size != (is_hd ? sizeof(a2dp_aptxhd_t) : sizeof(a2dp_aptx_t))) { + pa_log_error("APTX configuration array of invalid size"); + return false; + } + + switch (c->frequency) { + case APTX_SAMPLING_FREQ_16000: + case APTX_SAMPLING_FREQ_32000: + case APTX_SAMPLING_FREQ_44100: + case APTX_SAMPLING_FREQ_48000: + break; + default: + pa_log_error("Invalid sampling frequency in aptX configuration"); + return false; + } + + switch (c->channel_mode) { + case APTX_CHANNEL_MODE_STEREO: + break; + default: + pa_log_error("Invalid channel mode in aptX Configuration"); + return false; + } + return true; +}; + + +static bool pa_aptx_decoder_init(void **codec_data) { + return _internal_pa_dual_decoder_init(false, codec_data); +} + +static bool pa_aptx_hd_decoder_init(void **codec_data) { + return _internal_pa_dual_decoder_init(true, codec_data); +} + +static bool +pa_aptx_encoder_init(pa_a2dp_source_read_cb_t read_cb, pa_a2dp_source_read_buf_free_cb_t free_cb, void **codec_data) { + return _internal_pa_dual_encoder_init(false, read_cb, free_cb, codec_data); +} + +static bool +pa_aptx_hd_encoder_init(pa_a2dp_source_read_cb_t read_cb, pa_a2dp_source_read_buf_free_cb_t free_cb, + void **codec_data) { + return _internal_pa_dual_encoder_init(true, read_cb, free_cb, codec_data); +} + + +static size_t pa_aptx_get_capabilities(void **_capabilities) { + return _internal_pa_dual_get_capabilities(false, _capabilities); +} + +static size_t pa_aptx_select_configuration(const pa_sample_spec default_sample_spec, + const uint8_t *supported_capabilities, + const size_t capabilities_size, void **configuration) { + return _internal_pa_dual_select_configuration(false, default_sample_spec, supported_capabilities, capabilities_size, + configuration); +} + +static bool pa_aptx_set_configuration(const uint8_t *selected_configuration, + const size_t configuration_size) { + return _internal_pa_dual_set_configuration(false, selected_configuration, configuration_size); +} + +static size_t pa_aptx_hd_get_capabilities(void **_capabilities) { + return _internal_pa_dual_get_capabilities(true, _capabilities); +} + +static size_t pa_aptx_hd_select_configuration(const pa_sample_spec default_sample_spec, + const uint8_t *supported_capabilities, + const size_t capabilities_size, void **configuration) { + return _internal_pa_dual_select_configuration(true, default_sample_spec, supported_capabilities, capabilities_size, + configuration); +} + +static bool pa_aptx_hd_set_configuration(const uint8_t *selected_configuration, + const size_t configuration_size) { + return _internal_pa_dual_set_configuration(true, selected_configuration, configuration_size); +} + +static pa_a2dp_source_t pa_aptx_source = { + .encoder_load = pa_aptx_encoder_load, + .init = pa_aptx_encoder_init, + .update_user_config = pa_dual_update_user_config, + .encode = pa_dual_encode, + .config_transport = pa_dual_config_transport, + .get_block_size = pa_dual_get_write_block_size, + .setup_stream = pa_dual_setup_stream, + .set_tx_length = NULL, + .decrease_quality = NULL, + .free = pa_dual_free +}; + +static pa_a2dp_sink_t pa_aptx_sink = { + .decoder_load = pa_aptx_decoder_load, + .init = pa_aptx_decoder_init, + .update_user_config = pa_dual_update_user_config, + .config_transport = pa_dual_config_transport, + .get_block_size = pa_dual_get_read_block_size, + .setup_stream = pa_dual_setup_stream, + .decode = pa_dual_decode, + .free = pa_dual_free +}; + +const pa_a2dp_codec_t pa_a2dp_aptx = { + .name = "aptX", + .codec = A2DP_CODEC_VENDOR, + .vendor_codec = &((a2dp_vendor_codec_t) { + .vendor_id = APTX_VENDOR_ID, + .codec_id = APTX_CODEC_ID + }), + .a2dp_sink = &pa_aptx_sink, + .a2dp_source = &pa_aptx_source, + .get_capabilities = pa_aptx_get_capabilities, + .select_configuration = pa_aptx_select_configuration, + .free_capabilities = pa_dual_free_capabilities, + .free_configuration = pa_dual_free_capabilities, + .set_configuration = pa_aptx_set_configuration +}; + +static pa_a2dp_source_t pa_aptx_hd_source = { + .encoder_load = pa_aptx_hd_encoder_load, + .init = pa_aptx_hd_encoder_init, + .update_user_config = pa_dual_update_user_config, + .encode = pa_dual_encode, + .config_transport = pa_dual_config_transport, + .get_block_size = pa_dual_get_write_block_size, + .setup_stream = pa_dual_setup_stream, + .set_tx_length = NULL, + .decrease_quality = NULL, + .free = pa_dual_free +}; + +static pa_a2dp_sink_t pa_aptx_hd_sink = { + .decoder_load = pa_aptx_hd_decoder_load, + .init = pa_aptx_hd_decoder_init, + .update_user_config = pa_dual_update_user_config, + .config_transport = pa_dual_config_transport, + .get_block_size = pa_dual_get_read_block_size, + .setup_stream = pa_dual_setup_stream, + .decode = pa_dual_decode, + .free = pa_dual_free +}; + +const pa_a2dp_codec_t pa_a2dp_aptx_hd = { + .name = "aptX_HD", + .codec = A2DP_CODEC_VENDOR, + .vendor_codec = &((a2dp_vendor_codec_t) { + .vendor_id = APTX_HD_VENDOR_ID, + .codec_id = APTX_HD_CODEC_ID + }), + .a2dp_sink = &pa_aptx_hd_sink, + .a2dp_source = &pa_aptx_hd_source, + .get_capabilities = pa_aptx_hd_get_capabilities, + .select_configuration = pa_aptx_hd_select_configuration, + .free_capabilities = pa_dual_free_capabilities, + .free_configuration = pa_dual_free_capabilities, + .set_configuration = pa_aptx_hd_set_configuration +}; diff --git a/src/modules/bluetooth/a2dp/a2dp_util.c b/src/modules/bluetooth/a2dp/a2dp_util.c index 297dcd940..68db66d81 100644 --- a/src/modules/bluetooth/a2dp/a2dp_util.c +++ b/src/modules/bluetooth/a2dp/a2dp_util.c @@ -18,6 +18,12 @@ #define A2DP_VENDOR_SRC_ENDPOINT A2DP_SOURCE_ENDPOINT "/VENDOR" #define A2DP_VENDOR_SNK_ENDPOINT A2DP_SINK_ENDPOINT "/VENDOR" +#define A2DP_APTX_SRC_ENDPOINT A2DP_VENDOR_SRC_ENDPOINT "/APTX" +#define A2DP_APTX_SNK_ENDPOINT A2DP_VENDOR_SNK_ENDPOINT "/APTX" + +#define A2DP_APTX_HD_SRC_ENDPOINT A2DP_VENDOR_SRC_ENDPOINT "/APTXHD" +#define A2DP_APTX_HD_SNK_ENDPOINT A2DP_VENDOR_SNK_ENDPOINT "/APTXHD" + #define PA_A2DP_PRIORITY_DISABLE 0 #define PA_A2DP_PRIORITY_MIN 1 @@ -201,6 +207,20 @@ void pa_a2dp_codec_index_to_endpoint(pa_a2dp_codec_index_t codec_index, const ch case PA_A2DP_SOURCE_AAC: *endpoint = A2DP_AAC_SRC_ENDPOINT; break; +#endif +#ifdef HAVE_FF_APTX + case PA_A2DP_SINK_APTX: + *endpoint = A2DP_APTX_SNK_ENDPOINT; + break; + case PA_A2DP_SOURCE_APTX: + *endpoint = A2DP_APTX_SRC_ENDPOINT; + break; + case PA_A2DP_SINK_APTX_HD: + *endpoint = A2DP_APTX_HD_SNK_ENDPOINT; + break; + case PA_A2DP_SOURCE_APTX_HD: + *endpoint = A2DP_APTX_HD_SRC_ENDPOINT; + break; #endif default: *endpoint = NULL; @@ -217,6 +237,16 @@ void pa_a2dp_endpoint_to_codec_index(const char *endpoint, pa_a2dp_codec_index_t *codec_index = PA_A2DP_SINK_AAC; else if (streq(endpoint, A2DP_AAC_SRC_ENDPOINT)) *codec_index = PA_A2DP_SOURCE_AAC; +#endif +#ifdef HAVE_FF_APTX + else if (streq(endpoint, A2DP_APTX_SNK_ENDPOINT)) + *codec_index = PA_A2DP_SINK_APTX; + else if (streq(endpoint, A2DP_APTX_SRC_ENDPOINT)) + *codec_index = PA_A2DP_SOURCE_APTX; + else if (streq(endpoint, A2DP_APTX_HD_SNK_ENDPOINT)) + *codec_index = PA_A2DP_SINK_APTX_HD; + else if (streq(endpoint, A2DP_APTX_HD_SRC_ENDPOINT)) + *codec_index = PA_A2DP_SOURCE_APTX_HD; #endif else *codec_index = PA_A2DP_CODEC_INDEX_UNAVAILABLE; @@ -233,6 +263,16 @@ void pa_a2dp_codec_index_to_a2dp_codec(pa_a2dp_codec_index_t codec_index, const case PA_A2DP_SOURCE_AAC: *a2dp_codec = &pa_a2dp_aac; break; +#endif +#ifdef HAVE_FF_APTX + case PA_A2DP_SINK_APTX: + case PA_A2DP_SOURCE_APTX: + *a2dp_codec = &pa_a2dp_aptx; + break; + case PA_A2DP_SINK_APTX_HD: + case PA_A2DP_SOURCE_APTX_HD: + *a2dp_codec = &pa_a2dp_aptx_hd; + break; #endif default: *a2dp_codec = NULL; @@ -259,6 +299,17 @@ void pa_a2dp_a2dp_codec_to_codec_index(const pa_a2dp_codec_t *a2dp_codec, bool i *codec_index = PA_A2DP_CODEC_INDEX_UNAVAILABLE; return; } +#ifdef HAVE_FF_APTX + else if (a2dp_codec->vendor_codec->vendor_id == APTX_VENDOR_ID && + a2dp_codec->vendor_codec->codec_id == APTX_CODEC_ID) { + *codec_index = is_a2dp_sink ? PA_A2DP_SINK_APTX : PA_A2DP_SOURCE_APTX; + return; + } else if (a2dp_codec->vendor_codec->vendor_id == APTX_HD_VENDOR_ID && + a2dp_codec->vendor_codec->codec_id == APTX_HD_CODEC_ID) { + *codec_index = is_a2dp_sink ? PA_A2DP_SINK_APTX_HD : PA_A2DP_SOURCE_APTX_HD; + return; + } +#endif *codec_index = PA_A2DP_CODEC_INDEX_UNAVAILABLE; break; default: @@ -282,6 +333,15 @@ pa_a2dp_get_a2dp_codec(uint8_t codec, const a2dp_vendor_codec_t *vendor_codec, c *a2dp_codec = NULL; pa_assert_not_reached(); } +#ifdef HAVE_FF_APTX + else if (vendor_codec->vendor_id == APTX_VENDOR_ID && vendor_codec->codec_id == APTX_CODEC_ID) { + *a2dp_codec = &pa_a2dp_aptx; + return; + } else if (vendor_codec->vendor_id == APTX_HD_VENDOR_ID && vendor_codec->codec_id == APTX_HD_CODEC_ID) { + *a2dp_codec = &pa_a2dp_aptx_hd; + return; + } +#endif *a2dp_codec = NULL; break; default: -- 2.19.2 _______________________________________________ pulseaudio-discuss mailing list pulseaudio-discuss@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss