[pulseaudio-commits] [Git][pulseaudio/pulseaudio][master] 14 commits: bluetooth: a2dp-codec-util: Use lower index for higher priority

PulseAudio Marge Bot gitlab at gitlab.freedesktop.org
Tue Jan 19 15:33:54 UTC 2021



PulseAudio Marge Bot pushed to branch master at PulseAudio / pulseaudio


Commits:
fca6a2fd by Sanchayan Maity at 2021-01-19T13:43:42+05:30
bluetooth: a2dp-codec-util: Use lower index for higher priority

Instead of letting a codec with higher index have higher priority,
just use a lower index for high priority. This allows the for loop
iterating over the codecs to be written in a straightforward manner
and not have to iterate from the end. FWIW Pipewire does the same.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/440>

- - - - -
5284b450 by Sanchayan Maity at 2021-01-19T13:43:42+05:30
bluez5-util: Add support for using RegisterApplication D-Bus API

A2DP codec switching needs new version of bluez as older version does not
provide needed org.freedesktop.DBus.ObjectManager and RegisterApplication
DBus APIs. As a preparation for the next step of adding codecs and then
codec switching, add support for using the new API.

Getting list of supported codecs by remote device is supported only by new
version of bluez daemon. If the RegisterApplication API fails, we
fallback to the older RegisterEndpoint API and only register the SBC
codec.

For more information, see
https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager
https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/media-api.txt

This changeset has been taken from Pali Rohár's A2DP codec patch
series but separated out to only include the D-Bus specific changes.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/440>

- - - - -
3447335d by Sanchayan Maity at 2021-01-19T13:43:42+05:30
bluetooth: Support A2DP codec switching via messaging API

This uses the messaging API to initiate a codec switch.

While a particular codec might be applicable only for a particular
profile, for eg. aptX can only be applicable for A2DP sink or source
and not for let's say HSP, the codec switching logic has not been
tied to the logic for switching profiles.

Codec can be switched by running the following on the command line.

pacmd send-message /card/bluez_card.XX_XX_XX_XX_XX_XX/bluez switch-codec{"ldac_hq"}
pacmd send-message /card/bluez_card.XX_XX_XX_XX_XX_XX/bluez switch-codec {"ldac_mq"}
pacmd send-message /card/bluez_card.XX_XX_XX_XX_XX_XX/bluez switch-codec {"ldac_sq"}
pacmd send-message /card/bluez_card.XX_XX_XX_XX_XX_XX/bluez switch-codec {"aptx_hd"}
pacmd send-message /card/bluez_card.XX_XX_XX_XX_XX_XX/bluez switch-codec {"aptx"}
pacmd send-message /card/bluez_card.XX_XX_XX_XX_XX_XX/bluez switch-codec {"sbc"}

Codec name passed above is matched against pa_a2dp_codec->name. Note that
the match is case sensitive. XX_XX_XX_XX_XX_XX needs to be substituted with
the actual bluetooth device id.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/440>

- - - - -
a407e9aa by Sanchayan Maity at 2021-01-19T13:43:42+05:30
bluetooth: Add a generic GStreamer codec module

This adds a generic gstreamer codec module based on which other
bluetooth codecs viz. aptX, aptX-HD, LDAC and AAC can be supported.

The GStreamer codec plugins used here themselves depend on the native
codec implementation.

aptX/aptX-HD -> libopenaptx
LDAC         -> libldac
AAC          -> Fraunhofer FDK AAC

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/440>

- - - - -
ef4762a4 by Sanchayan Maity at 2021-01-19T13:43:42+05:30
bluetooth: Add LDAC support via GStreamer

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/440>

- - - - -
33779648 by Sanchayan Maity at 2021-01-19T13:43:42+05:30
bluetooth: Add aptX support via GStreamer

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/440>

- - - - -
2fea838e by Sanchayan Maity at 2021-01-19T13:43:42+05:30
bluetooth: Add support for getting list of negotiated codecs

For example, using the following on the command line will return the
list of possible codecs for a bluetooth device

pacmd send-message /card/bluez_card.4C_BC_98_80_01_9B/bluez list-codecs

where 4C_BC_98_80_01_9B is the bluetooth device.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/440>

- - - - -
4ce996b7 by Sanchayan Maity at 2021-01-19T13:43:42+05:30
bluetooth: Add currently active codec name to card/sink/source proplist

This exposes the currently active codec on the source or sink via the
proplist and can be seen in output of pacmd list-sinks/list-sources.
Also set it on the card. In case of a bi-directional codec, the codec
for the sink and source could be different. For example, for aptX-LL,
the codec name on card, sink and source would be aptx-ll, aptx and sbc
respectively.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/440>

- - - - -
60441697 by Sanchayan Maity at 2021-01-19T13:43:42+05:30
bluetooth: Add support for getting current active codec

For example, using the following on the command line will return the
current codec for a bluetooth device

pacmd send-message /card/bluez_card.4C_BC_98_80_01_9B/bluez get-codec

where 4C_BC_98_80_01_9B is the bluetooth device.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/440>

- - - - -
d6149364 by Sanchayan Maity at 2021-01-19T13:43:42+05:30
bluetooth: Introduce a can_be_supported API for A2DP codecs

This API internally checks if a requested codec can be supported on the
system. This is especially required for codecs supported via GStreamer
where the availability of a plugin decides if the said codec can be
supported.

This will be used to prevent registration of a codec which the remote
endpoint device might be able to support, but, PulseAudio can't as the
codec is not available on the system due to the absence of a plugin.
We can also prevent listing or switching to an unavailable codec.

Note that the codec negotiation happens with the bluez stack even before
a device is connected. Because of this, we need to make sure that gst_init
is called before checking for the availability of a plugin. Since
module-bluez5-device gets loaded only after a connection to the device
has been established, doing the gst_init in that or one of the bluetooth
modules is not feasible.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/440>

- - - - -
8289bdb7 by Sanchayan Maity at 2021-01-19T13:43:42+05:30
bluetooth: Prevent registration of an unavailable codec at runtime

When it comes to codecs provided via GStreamer, we register all codecs
if GStreamer option is enabled for bluez5 via meson. However, the
GStreamer plugin required for the codec might not be present on the
system. This results in the codec being available for registration with
the bluez stack or selection by the user, but, trying to use the said
codec then fails.

To prevent the above, we now use the can_be_supported codec API to check
if the codec is usable and if not, we do not register the said codec and
also prevent users from switching to it.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/440>

- - - - -
366bd561 by Igor V. Kovalenko at 2021-01-19T13:43:42+05:30
bluetooth: Make GStreamer threads realtime

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/440>

- - - - -
ed44fa18 by Sanchayan Maity at 2021-01-19T13:43:42+05:30
bluetooth: Register an endpoint according to encode or decode support

As we now support codecs other than SBC, we might have codec which does
not have an encode or a decode capability. Specifically, in the case of
LDAC there isn't a known decoder implementation available. For such a
case, we should not register the corresponding endpoint.

In case of LDAC, as decoding cannot be supported, we should not register
a sink endpoint or vice versa in the other scenario.

To do this, we check if encode_buffer or decode_buffer entry for a codec
has been set in pa_a2dp_codec and accordingly prevent or allow it's
registration.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/440>

- - - - -
cdbb73f9 by Sanchayan Maity at 2021-01-19T13:43:42+05:30
bluetooth: Move codec specific bits to their own respective files

We move the codec specific bits to their own respective files and now
make the codec specific initialisation use a GstBin, which the generic
GStreamer module now uses in the pipeline.

It is job of the codec specific function to add elements in the GstBin
and link the added elements in the bin. It should also set up the ghost
pads as a GstBin has no pads of it's own and without which the bin
cannot be linked to the appsrc/appsink.

Also, we now only initialise either the encoding or the decoding
pipeline and not both. The codec init API already gets passed the
for_encoding flag. We pass and use the same to codec specific init
functions.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/440>

- - - - -


16 changed files:

- meson.build
- meson_options.txt
- src/modules/bluetooth/a2dp-codec-api.h
- + src/modules/bluetooth/a2dp-codec-aptx-gst.c
- + src/modules/bluetooth/a2dp-codec-gst.c
- + src/modules/bluetooth/a2dp-codec-gst.h
- + src/modules/bluetooth/a2dp-codec-ldac-gst.c
- src/modules/bluetooth/a2dp-codec-sbc.c
- src/modules/bluetooth/a2dp-codec-util.c
- src/modules/bluetooth/a2dp-codec-util.h
- src/modules/bluetooth/bluez5-util.c
- src/modules/bluetooth/bluez5-util.h
- src/modules/bluetooth/meson.build
- src/modules/bluetooth/module-bluez5-device.c
- src/modules/bluetooth/module-bluez5-discover.c
- src/pulse/proplist.h


Changes:

=====================================
meson.build
=====================================
@@ -742,6 +742,15 @@ if gst_dep.found() and gstapp_dep.found() and gstrtp_dep.found()
   have_gstreamer = true
 endif
 
+bluez5_gst_dep = dependency('gstreamer-1.0', version : '>= 1.14', required : get_option('bluez5-gstreamer'))
+bluez5_gstapp_dep = dependency('gstreamer-app-1.0', required : get_option('bluez5-gstreamer'))
+have_bluez5_gstreamer = false
+if bluez5_gst_dep.found() and bluez5_gstapp_dep.found()
+  have_bluez5_gstreamer = true
+  cdata.set('HAVE_GSTLDAC', 1)
+  cdata.set('HAVE_GSTAPTX', 1)
+endif
+
 # These are required for the CMake file generation
 cdata.set('PA_LIBDIR', libdir)
 cdata.set('PA_INCDIR', includedir)
@@ -882,6 +891,7 @@ summary = [
   '  Enable BlueZ 5:              @0@'.format(get_option('bluez5')),
   '    Enable native headsets:    @0@'.format(get_option('bluez5-native-headset')),
   '    Enable  ofono headsets:    @0@'.format(get_option('bluez5-ofono-headset')),
+  '    Enable GStreamer based codecs: @0@'.format(have_bluez5_gstreamer),
   'Enable udev:                   @0@'.format(udev_dep.found()),
   '  Enable HAL->udev compat:     @0@'.format(get_option('hal-compat')),
   'Enable systemd:                @0@'.format(libsystemd_dep.found()),


=====================================
meson_options.txt
=====================================
@@ -81,6 +81,9 @@ option('avahi',
 option('bluez5',
        type : 'boolean', value : 'true',
        description : 'Optional BlueZ 5 support')
+option('bluez5-gstreamer',
+       type : 'feature', value: 'auto',
+       description : 'Optional BlueZ 5 GStreamer support')
 option('bluez5-native-headset',
        type : 'boolean',
        description : 'Optional native headset backend support (BlueZ 5)')


=====================================
src/modules/bluetooth/a2dp-codec-api.h
=====================================
@@ -48,6 +48,9 @@ typedef struct pa_a2dp_codec {
     /* True if codec is bi-directional and supports backchannel */
     bool support_backchannel;
 
+    /* Returns true if the codec can be supported on the system */
+    bool (*can_be_supported)(void);
+
     /* Returns true if codec accepts capabilities, for_encoding is true when
      * capabilities are used for encoding */
     bool (*can_accept_capabilities)(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding);
@@ -66,7 +69,7 @@ typedef struct pa_a2dp_codec {
     /* Initialize codec, returns codec info data and set sample_spec,
      * for_encoding is true when codec_info is used for encoding,
      * for_backchannel is true when codec_info is used for backchannel */
-    void *(*init)(bool for_encoding, bool for_backchannel, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *sample_spec);
+    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);
     /* Deinitialize and release codec info data in codec_info */
     void (*deinit)(void *codec_info);
     /* Reset internal state of codec info data in codec_info, returns


=====================================
src/modules/bluetooth/a2dp-codec-aptx-gst.c
=====================================
@@ -0,0 +1,621 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2020 Sanchayan Maity <sanchayan at 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(void) {
+    GstElementFactory *element_factory;
+
+    element_factory = gst_element_factory_find("openaptxenc");
+    if (element_factory == NULL) {
+        pa_log_info("aptX encoder not found");
+        return false;
+    }
+
+    gst_object_unref(element_factory);
+
+    element_factory = gst_element_factory_find("openaptxdec");
+    if (element_factory == NULL) {
+        pa_log_info("aptX decoder not found");
+        return false;
+    }
+
+    gst_object_unref(element_factory);
+
+    return true;
+}
+
+static bool can_accept_capabilities_common(const a2dp_aptx_t *capabilities, uint32_t vendor_id, uint16_t codec_id) {
+    if (A2DP_GET_VENDOR_ID(capabilities->info) != vendor_id || A2DP_GET_CODEC_ID(capabilities->info) != codec_id)
+        return false;
+
+    if (!(capabilities->frequency & (APTX_SAMPLING_FREQ_16000 | APTX_SAMPLING_FREQ_32000 |
+                                     APTX_SAMPLING_FREQ_44100 | APTX_SAMPLING_FREQ_48000)))
+        return false;
+
+    if (!(capabilities->channel_mode & APTX_CHANNEL_MODE_STEREO))
+        return false;
+
+    return true;
+}
+
+static bool can_accept_capabilities(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding) {
+    const a2dp_aptx_t *capabilities = (const a2dp_aptx_t *) capabilities_buffer;
+
+    if (capabilities_size != sizeof(*capabilities))
+        return false;
+
+    return can_accept_capabilities_common(capabilities, APTX_VENDOR_ID, APTX_CODEC_ID);
+}
+
+static bool can_accept_capabilities_hd(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding) {
+    const a2dp_aptx_hd_t *capabilities = (const a2dp_aptx_hd_t *) capabilities_buffer;
+
+    if (capabilities_size != sizeof(*capabilities))
+        return false;
+
+    return can_accept_capabilities_common(&capabilities->aptx, APTX_HD_VENDOR_ID, APTX_HD_CODEC_ID);
+}
+
+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 const char *choose_remote_endpoint_hd(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_hd(a2dp_capabilities->buffer, a2dp_capabilities->size, for_encoding))
+            return key;
+    }
+
+    return NULL;
+}
+
+static void fill_capabilities_common(a2dp_aptx_t *capabilities, uint32_t vendor_id, uint16_t codec_id) {
+    capabilities->info = A2DP_SET_VENDOR_ID_CODEC_ID(vendor_id, 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;
+}
+
+static uint8_t fill_capabilities(uint8_t capabilities_buffer[MAX_A2DP_CAPS_SIZE]) {
+    a2dp_aptx_t *capabilities = (a2dp_aptx_t *) capabilities_buffer;
+
+    pa_zero(*capabilities);
+    fill_capabilities_common(capabilities, APTX_VENDOR_ID, APTX_CODEC_ID);
+    return sizeof(*capabilities);
+}
+
+static uint8_t fill_capabilities_hd(uint8_t capabilities_buffer[MAX_A2DP_CAPS_SIZE]) {
+    a2dp_aptx_hd_t *capabilities = (a2dp_aptx_hd_t *) capabilities_buffer;
+
+    pa_zero(*capabilities);
+    fill_capabilities_common(&capabilities->aptx, APTX_HD_VENDOR_ID, APTX_HD_CODEC_ID);
+    return sizeof(*capabilities);
+}
+
+static bool is_configuration_valid_common(const a2dp_aptx_t *config, uint32_t vendor_id, uint16_t codec_id) {
+    if (A2DP_GET_VENDOR_ID(config->info) != vendor_id || A2DP_GET_CODEC_ID(config->info) != codec_id) {
+        pa_log_error("Invalid vendor codec information in configuration");
+        return false;
+    }
+
+    if (config->frequency != APTX_SAMPLING_FREQ_16000 && config->frequency != APTX_SAMPLING_FREQ_32000 &&
+        config->frequency != APTX_SAMPLING_FREQ_44100 && config->frequency != APTX_SAMPLING_FREQ_48000) {
+        pa_log_error("Invalid sampling frequency in configuration");
+        return false;
+    }
+
+    if (config->channel_mode != APTX_CHANNEL_MODE_STEREO) {
+        pa_log_error("Invalid channel mode in configuration");
+        return false;
+    }
+
+    return true;
+}
+
+static bool is_configuration_valid(const uint8_t *config_buffer, uint8_t config_size) {
+    const a2dp_aptx_t *config = (const a2dp_aptx_t *) config_buffer;
+
+    if (config_size != sizeof(*config)) {
+        pa_log_error("Invalid size of config buffer");
+        return false;
+    }
+
+    return is_configuration_valid_common(config, APTX_VENDOR_ID, APTX_CODEC_ID);
+}
+
+static bool is_configuration_valid_hd(const uint8_t *config_buffer, uint8_t config_size) {
+    const a2dp_aptx_hd_t *config = (const a2dp_aptx_hd_t *) config_buffer;
+
+    if (config_size != sizeof(*config)) {
+        pa_log_error("Invalid size of config buffer");
+        return false;
+    }
+
+    return is_configuration_valid_common(&config->aptx, APTX_HD_VENDOR_ID, APTX_HD_CODEC_ID);
+}
+
+static int fill_preferred_configuration_common(const pa_sample_spec *default_sample_spec, const a2dp_aptx_t *capabilities, a2dp_aptx_t *config, uint32_t vendor_id, uint16_t codec_id) {
+    int i;
+
+    static const struct {
+        uint32_t rate;
+        uint8_t cap;
+    } freq_table[] = {
+        { 16000U, APTX_SAMPLING_FREQ_16000 },
+        { 32000U, APTX_SAMPLING_FREQ_32000 },
+        { 44100U, APTX_SAMPLING_FREQ_44100 },
+        { 48000U, APTX_SAMPLING_FREQ_48000 }
+    };
+
+    if (A2DP_GET_VENDOR_ID(capabilities->info) != vendor_id || A2DP_GET_CODEC_ID(capabilities->info) != codec_id) {
+        pa_log_error("No supported vendor codec information");
+        return -1;
+    }
+
+    config->info = A2DP_SET_VENDOR_ID_CODEC_ID(vendor_id, codec_id);
+
+    if (!(capabilities->channel_mode & APTX_CHANNEL_MODE_STEREO)) {
+        pa_log_error("No supported channel modes");
+        return -1;
+    }
+
+    config->channel_mode = APTX_CHANNEL_MODE_STEREO;
+
+    /* 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 && (capabilities->frequency & freq_table[i].cap)) {
+            config->frequency = freq_table[i].cap;
+            break;
+        }
+    }
+
+    if ((unsigned) i == PA_ELEMENTSOF(freq_table)) {
+        for (--i; i >= 0; i--) {
+            if (capabilities->frequency & freq_table[i].cap) {
+                config->frequency = freq_table[i].cap;
+                break;
+            }
+        }
+
+        if (i < 0) {
+            pa_log_error("Not suitable sample rate");
+            return false;
+        }
+    }
+
+    return 0;
+}
+
+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_aptx_t *config = (a2dp_aptx_t *) config_buffer;
+    const a2dp_aptx_t *capabilities = (const a2dp_aptx_t *) capabilities_buffer;
+
+    if (capabilities_size != sizeof(*capabilities)) {
+        pa_log_error("Invalid size of capabilities buffer");
+        return 0;
+    }
+
+    pa_zero(*config);
+
+    if (fill_preferred_configuration_common(default_sample_spec, capabilities, config, APTX_VENDOR_ID, APTX_CODEC_ID) < 0)
+        return 0;
+
+    return sizeof(*config);
+}
+
+static uint8_t fill_preferred_configuration_hd(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_aptx_hd_t *config = (a2dp_aptx_hd_t *) config_buffer;
+    const a2dp_aptx_hd_t *capabilities = (const a2dp_aptx_hd_t *) capabilities_buffer;
+
+    if (capabilities_size != sizeof(*capabilities)) {
+        pa_log_error("Invalid size of capabilities buffer");
+        return 0;
+    }
+
+    pa_zero(*config);
+
+    if (fill_preferred_configuration_common(default_sample_spec, &capabilities->aptx, &config->aptx, APTX_HD_VENDOR_ID, APTX_HD_CODEC_ID) < 0)
+        return 0;
+
+    return sizeof(*config);
+}
+
+bool gst_init_aptx(struct gst_info *info, pa_sample_spec *ss, bool for_encoding) {
+    GstElement *enc, *dec;
+    GstCaps *caps;
+    GstPad *pad;
+    const char *aptx_codec_media_type;
+
+    ss->format = PA_SAMPLE_S24LE;
+
+    if (info->codec_type == APTX_HD) {
+        switch (info->a2dp_codec_t.aptx_hd_config->aptx.frequency) {
+            case APTX_SAMPLING_FREQ_16000:
+                ss->rate = 16000u;
+                break;
+            case APTX_SAMPLING_FREQ_32000:
+                ss->rate = 32000u;
+                break;
+            case APTX_SAMPLING_FREQ_44100:
+                ss->rate = 44100u;
+                break;
+            case APTX_SAMPLING_FREQ_48000:
+                ss->rate = 48000u;
+                break;
+            default:
+                pa_log_error("aptX HD invalid frequency %d", info->a2dp_codec_t.aptx_hd_config->aptx.frequency);
+                goto fail;
+        }
+
+        switch (info->a2dp_codec_t.aptx_hd_config->aptx.channel_mode) {
+            case APTX_CHANNEL_MODE_STEREO:
+                ss->channels = 2;
+                break;
+            default:
+                pa_log_error("aptX HD invalid channel mode %d", info->a2dp_codec_t.aptx_hd_config->aptx.frequency);
+                goto fail;
+        }
+    } else {
+        switch (info->a2dp_codec_t.aptx_config->frequency) {
+            case APTX_SAMPLING_FREQ_16000:
+                ss->rate = 16000u;
+                break;
+            case APTX_SAMPLING_FREQ_32000:
+                ss->rate = 32000u;
+                break;
+            case APTX_SAMPLING_FREQ_44100:
+                ss->rate = 44100u;
+                break;
+            case APTX_SAMPLING_FREQ_48000:
+                ss->rate = 48000u;
+                break;
+            default:
+                pa_log_error("aptX invalid frequency %d", info->a2dp_codec_t.aptx_config->frequency);
+                goto fail;
+        }
+
+        switch (info->a2dp_codec_t.aptx_config->channel_mode) {
+            case APTX_CHANNEL_MODE_STEREO:
+                ss->channels = 2;
+                break;
+            default:
+                pa_log_error("aptX invalid channel mode %d", info->a2dp_codec_t.aptx_config->frequency);
+                goto fail;
+        }
+    }
+
+    aptx_codec_media_type = info->codec_type == APTX_HD ? "audio/aptx-hd" : "audio/aptx";
+
+    if (for_encoding) {
+        enc = gst_element_factory_make("openaptxenc", "aptx_encoder");
+
+        if (enc == NULL) {
+            pa_log_error("Could not create aptX encoder element");
+            goto fail;
+        }
+
+        caps = gst_caps_new_simple("audio/x-raw",
+                "format", G_TYPE_STRING, "S24LE",
+                "rate", G_TYPE_INT, (int) ss->rate,
+                "channels", G_TYPE_INT, (int) ss->channels,
+                "channel-mask", G_TYPE_INT, 0,
+                "layout", G_TYPE_STRING, "interleaved",
+                NULL);
+        g_object_set(info->enc_src, "caps", caps, NULL);
+        gst_caps_unref(caps);
+
+        caps = gst_caps_new_simple(aptx_codec_media_type,
+                "rate", G_TYPE_INT, (int) ss->rate,
+                "channels", G_TYPE_INT, (int) ss->channels,
+                NULL);
+        g_object_set(info->enc_sink, "caps", caps, NULL);
+        gst_caps_unref(caps);
+
+        info->enc_bin = gst_bin_new("aptx_enc_bin");
+        pa_assert(info->enc_bin);
+
+        gst_bin_add(GST_BIN(info->enc_bin), enc);
+
+        pad = gst_element_get_static_pad(enc, "sink");
+        gst_element_add_pad(info->enc_bin, gst_ghost_pad_new("sink", pad));
+        gst_object_unref(GST_OBJECT(pad));
+
+        pad = gst_element_get_static_pad(enc, "src");
+        gst_element_add_pad(info->enc_bin, gst_ghost_pad_new("src", pad));
+        gst_object_unref(GST_OBJECT(pad));
+    } else {
+        dec = gst_element_factory_make("openaptxdec", "aptx_decoder");
+
+        if (dec == NULL) {
+            pa_log_error("Could not create aptX decoder element");
+            goto fail;
+        }
+
+        caps = gst_caps_new_simple(aptx_codec_media_type,
+                "rate", G_TYPE_INT, (int) ss->rate,
+                "channels", G_TYPE_INT, (int) ss->channels,
+                NULL);
+        g_object_set(info->dec_src, "caps", caps, NULL);
+        gst_caps_unref(caps);
+
+        caps = gst_caps_new_simple("audio/x-raw",
+                "format", G_TYPE_STRING, "S24LE",
+                "rate", G_TYPE_INT, (int) ss->rate,
+                "channels", G_TYPE_INT, (int) ss->channels,
+                "layout", G_TYPE_STRING, "interleaved",
+                NULL);
+        g_object_set(info->dec_sink, "caps", caps, NULL);
+        gst_caps_unref(caps);
+
+        info->dec_bin = gst_bin_new("aptx_dec_bin");
+        pa_assert(info->dec_bin);
+
+        gst_bin_add(GST_BIN(info->dec_bin), dec);
+
+        pad = gst_element_get_static_pad(dec, "sink");
+        gst_element_add_pad(info->dec_bin, gst_ghost_pad_new("sink", pad));
+        gst_object_unref(GST_OBJECT(pad));
+
+        pad = gst_element_get_static_pad(dec, "src");
+        gst_element_add_pad(info->dec_bin, gst_ghost_pad_new("src", pad));
+        gst_object_unref(GST_OBJECT(pad));
+    }
+
+    return true;
+
+fail:
+    pa_log_error("aptX initialisation failed");
+    return false;
+}
+
+static void *init_common(enum a2dp_codec_type codec_type, bool for_encoding, bool for_backchannel, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *sample_spec, pa_core *core) {
+    struct gst_info *info = NULL;
+
+    info = pa_xnew0(struct gst_info, 1);
+    pa_assert(info);
+
+    info->core = core;
+    info->ss = sample_spec;
+
+    if (codec_type == APTX) {
+        info->codec_type = APTX;
+        info->a2dp_codec_t.aptx_config = (const a2dp_aptx_t *) config_buffer;
+        pa_assert(config_size == sizeof(*(info->a2dp_codec_t.aptx_config)));
+    } else if (codec_type == APTX_HD) {
+        info->codec_type = APTX_HD;
+        info->a2dp_codec_t.aptx_hd_config = (const a2dp_aptx_hd_t *) config_buffer;
+        pa_assert(config_size == sizeof(*(info->a2dp_codec_t.aptx_hd_config)));
+    } else
+        pa_assert_not_reached();
+
+    /*
+     * The common encoder/decoder initialisation functions need to be called
+     * before the codec specific ones, as the codec specific initialisation
+     * function needs to set the caps specific property appropriately on the
+     * appsrc and appsink as per the sample spec and the codec.
+     */
+    if (for_encoding) {
+        if (!gst_init_enc_common(info))
+            goto fail;
+    } else {
+        if (!gst_init_dec_common(info))
+            goto fail;
+    }
+
+    if (!gst_init_aptx(info, sample_spec, for_encoding))
+        goto enc_dec_fail;
+
+    if (!gst_codec_init(info, for_encoding))
+        goto enc_dec_fail;
+
+    return info;
+
+enc_dec_fail:
+    if (for_encoding)
+        gst_deinit_enc_common(info);
+    else
+        gst_deinit_dec_common(info);
+fail:
+    if (info)
+        pa_xfree(info);
+
+    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) {
+    return init_common(APTX, for_encoding, for_backchannel, config_buffer, config_size, sample_spec, core);
+}
+
+static void *init_hd(bool for_encoding, bool for_backchannel, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *sample_spec, pa_core *core) {
+    return init_common(APTX_HD, for_encoding, for_backchannel, config_buffer, config_size, sample_spec, core);
+}
+
+static void deinit(void *codec_info) {
+    return gst_codec_deinit(codec_info);
+}
+
+static int reset(void *codec_info) {
+    return 0;
+}
+
+static int reset_hd(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) {
+    /* aptX compression ratio is 6:1 and we need to process one aptX frame (4 bytes) at once */
+    size_t frame_count = (link_mtu / 4);
+
+    return frame_count * 4 * 6;
+}
+
+static size_t get_block_size_hd(void *codec_info, size_t link_mtu) {
+    /* aptX HD compression ratio is 4:1 and we need to process one aptX HD frame (6 bytes) at once, plus aptX HD frames are encapsulated in RTP */
+    size_t rtp_size = sizeof(struct rtp_header);
+    size_t frame_count = (link_mtu - rtp_size) / 6;
+
+    return frame_count * 6 * 4;
+}
+
+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) {
+    size_t written;
+
+    written = gst_encode_buffer(codec_info, timestamp, input_buffer, input_size, output_buffer, output_size, processed);
+    if (PA_UNLIKELY(*processed == 0 || *processed != input_size))
+        pa_log_error("aptX encoding error");
+
+    return written;
+}
+
+static size_t encode_buffer_hd(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_encode_buffer(codec_info, timestamp, 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) {
+    size_t written;
+
+    written = gst_decode_buffer(codec_info, input_buffer, input_size, output_buffer, output_size, processed);
+
+    /* Due to aptX latency, aptx_decode starts filling output buffer after 90 input samples.
+     * If input buffer contains less than 90 samples, aptx_decode returns zero (=no output)
+     * but set *processed to non zero as input samples were processed. So do not check for
+     * return value of aptx_decode, zero is valid. Decoding error is indicating by fact that
+     * not all input samples were processed. */
+    if (PA_UNLIKELY(*processed != input_size))
+        pa_log_error("aptX decoding error");
+
+    return written;
+}
+
+static size_t decode_buffer_hd(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 = decode_buffer(codec_info, input_buffer + sizeof(*header), input_size - sizeof(*header), output_buffer, output_size, processed);
+    *processed += sizeof(*header);
+    return written;
+}
+
+const pa_a2dp_codec pa_a2dp_codec_aptx = {
+    .name = "aptx",
+    .description = "aptX",
+    .id = { A2DP_CODEC_VENDOR, APTX_VENDOR_ID, APTX_CODEC_ID },
+    .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,
+    .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,
+};
+
+const pa_a2dp_codec pa_a2dp_codec_aptx_hd = {
+    .name = "aptx_hd",
+    .description = "aptX HD",
+    .id = { A2DP_CODEC_VENDOR, APTX_HD_VENDOR_ID, APTX_HD_CODEC_ID },
+    .support_backchannel = false,
+    .can_be_supported = can_be_supported,
+    .can_accept_capabilities = can_accept_capabilities_hd,
+    .choose_remote_endpoint = choose_remote_endpoint_hd,
+    .fill_capabilities = fill_capabilities_hd,
+    .is_configuration_valid = is_configuration_valid_hd,
+    .fill_preferred_configuration = fill_preferred_configuration_hd,
+    .init = init_hd,
+    .deinit = deinit,
+    .reset = reset_hd,
+    .get_read_block_size = get_block_size_hd,
+    .get_write_block_size = get_block_size_hd,
+    .reduce_encoder_bitrate = reduce_encoder_bitrate,
+    .encode_buffer = encode_buffer_hd,
+    .decode_buffer = decode_buffer_hd,
+};


=====================================
src/modules/bluetooth/a2dp-codec-gst.c
=====================================
@@ -0,0 +1,483 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright (C) 2020 Asymptotic <sanchayan at 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 <arpa/inet.h>
+
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/once.h>
+#include <pulsecore/core-util.h>
+#include <pulse/sample.h>
+#include <pulse/util.h>
+
+#include "a2dp-codecs.h"
+#include "a2dp-codec-api.h"
+#include "a2dp-codec-gst.h"
+
+/* Called from the GStreamer streaming thread */
+static void enc_sink_eos(GstAppSink *appsink, gpointer userdata) {
+    pa_log_debug("Encoder got EOS");
+}
+
+/* Called from the GStreamer streaming thread */
+static GstFlowReturn enc_sink_new_sample(GstAppSink *appsink, gpointer userdata) {
+    struct gst_info *info = (struct gst_info *) userdata;
+    GstSample *sample = NULL;
+    GstBuffer *buf;
+
+    sample = gst_app_sink_pull_sample(GST_APP_SINK(info->enc_sink));
+    if (!sample)
+        return GST_FLOW_OK;
+
+    buf = gst_sample_get_buffer(sample);
+    gst_buffer_ref(buf);
+    gst_adapter_push(info->enc_adapter, buf);
+    gst_sample_unref(sample);
+    pa_fdsem_post(info->enc_fdsem);
+
+    return GST_FLOW_OK;
+}
+
+/* Called from the GStreamer streaming thread */
+static void dec_sink_eos(GstAppSink *appsink, gpointer userdata) {
+    pa_log_debug("Decoder got EOS");
+}
+
+/* Called from the GStreamer streaming thread */
+static GstFlowReturn dec_sink_new_sample(GstAppSink *appsink, gpointer userdata) {
+    struct gst_info *info = (struct gst_info *) userdata;
+    GstSample *sample = NULL;
+    GstBuffer *buf;
+
+    sample = gst_app_sink_pull_sample(GST_APP_SINK(info->dec_sink));
+    if (!sample)
+        return GST_FLOW_OK;
+
+    buf = gst_sample_get_buffer(sample);
+    gst_buffer_ref(buf);
+    gst_adapter_push(info->dec_adapter, buf);
+    gst_sample_unref(sample);
+    pa_fdsem_post(info->dec_fdsem);
+
+    return GST_FLOW_OK;
+}
+
+void gst_deinit_enc_common(struct gst_info *info) {
+    if (!info)
+        return;
+    if (info->enc_fdsem)
+        pa_fdsem_free(info->enc_fdsem);
+    if (info->enc_src)
+        gst_object_unref(info->enc_src);
+    if (info->enc_sink)
+        gst_object_unref(info->enc_sink);
+    if (info->enc_adapter)
+        g_object_unref(info->enc_adapter);
+    if (info->enc_pipeline)
+        gst_object_unref(info->enc_pipeline);
+}
+
+void gst_deinit_dec_common(struct gst_info *info) {
+    if (!info)
+        return;
+    if (info->dec_fdsem)
+        pa_fdsem_free(info->dec_fdsem);
+    if (info->dec_src)
+        gst_object_unref(info->dec_src);
+    if (info->dec_sink)
+        gst_object_unref(info->dec_sink);
+    if (info->dec_adapter)
+        g_object_unref(info->dec_adapter);
+    if (info->dec_pipeline)
+        gst_object_unref(info->dec_pipeline);
+}
+
+static GstBusSyncReply sync_bus_handler (GstBus *bus, GstMessage *message, struct gst_info *info) {
+    GstStreamStatusType type;
+    GstElement *owner;
+
+    switch (GST_MESSAGE_TYPE (message)) {
+        case GST_MESSAGE_STREAM_STATUS:
+
+            gst_message_parse_stream_status (message, &type, &owner);
+
+            switch (type) {
+            case GST_STREAM_STATUS_TYPE_ENTER:
+                pa_log_debug("GStreamer pipeline thread starting up");
+                if (info->core->realtime_scheduling)
+                    pa_thread_make_realtime(info->core->realtime_priority);
+                break;
+            case GST_STREAM_STATUS_TYPE_LEAVE:
+                pa_log_debug("GStreamer pipeline thread shutting down");
+                break;
+            default:
+                break;
+            }
+        break;
+        default:
+            break;
+    }
+
+    /* pass all messages on the async queue */
+    return GST_BUS_PASS;
+}
+
+bool gst_init_enc_common(struct gst_info *info) {
+    GstElement *pipeline = NULL;
+    GstElement *appsrc = NULL, *appsink = NULL;
+    GstAdapter *adapter;
+    GstAppSinkCallbacks callbacks = { 0, };
+    GstBus *bus;
+
+    appsrc = gst_element_factory_make("appsrc", "enc_source");
+    if (!appsrc) {
+        pa_log_error("Could not create appsrc element");
+        goto fail;
+    }
+    g_object_set(appsrc, "is-live", FALSE, "format", GST_FORMAT_TIME, "stream-type", 0, "max-bytes", 0, NULL);
+
+    appsink = gst_element_factory_make("appsink", "enc_sink");
+    if (!appsink) {
+        pa_log_error("Could not create appsink element");
+        goto fail;
+    }
+    g_object_set(appsink, "sync", FALSE, "async", FALSE, "enable-last-sample", FALSE, NULL);
+
+    callbacks.eos = enc_sink_eos;
+    callbacks.new_sample = enc_sink_new_sample;
+    gst_app_sink_set_callbacks(GST_APP_SINK(appsink), &callbacks, info, NULL);
+
+    adapter = gst_adapter_new();
+    pa_assert(adapter);
+
+    pipeline = gst_pipeline_new(NULL);
+    pa_assert(pipeline);
+
+    bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
+    gst_bus_set_sync_handler (bus, (GstBusSyncHandler) sync_bus_handler, info, NULL);
+    gst_object_unref (bus);
+
+    info->enc_src = appsrc;
+    info->enc_sink = appsink;
+    info->enc_adapter = adapter;
+    info->enc_pipeline = pipeline;
+    info->enc_fdsem = pa_fdsem_new();
+
+    return true;
+
+fail:
+    if (appsrc)
+        gst_object_unref(appsrc);
+    if (appsink)
+        gst_object_unref(appsink);
+
+    return false;
+}
+
+bool gst_init_dec_common(struct gst_info *info) {
+    GstElement *pipeline = NULL;
+    GstElement *appsrc = NULL, *appsink = NULL;
+    GstAdapter *adapter;
+    GstAppSinkCallbacks callbacks = { 0, };
+    GstBus *bus;
+
+    appsrc = gst_element_factory_make("appsrc", "dec_source");
+    if (!appsrc) {
+        pa_log_error("Could not create decoder appsrc element");
+        goto fail;
+    }
+    g_object_set(appsrc, "is-live", FALSE, "format", GST_FORMAT_TIME, "stream-type", 0, "max-bytes", 0, NULL);
+
+    appsink = gst_element_factory_make("appsink", "dec_sink");
+    if (!appsink) {
+        pa_log_error("Could not create decoder appsink element");
+        goto fail;
+    }
+    g_object_set(appsink, "sync", FALSE, "async", FALSE, "enable-last-sample", FALSE, NULL);
+
+    callbacks.eos = dec_sink_eos;
+    callbacks.new_sample = dec_sink_new_sample;
+    gst_app_sink_set_callbacks(GST_APP_SINK(appsink), &callbacks, info, NULL);
+
+    adapter = gst_adapter_new();
+    pa_assert(adapter);
+
+    pipeline = gst_pipeline_new(NULL);
+    pa_assert(pipeline);
+
+    bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
+    gst_bus_set_sync_handler (bus, (GstBusSyncHandler) sync_bus_handler, info, NULL);
+    gst_object_unref (bus);
+
+    info->dec_src = appsrc;
+    info->dec_sink = appsink;
+    info->dec_adapter = adapter;
+    info->dec_pipeline = pipeline;
+    info->dec_fdsem = pa_fdsem_new();
+
+    return true;
+
+fail:
+    if (appsrc)
+        gst_object_unref(appsrc);
+    if (appsink)
+        gst_object_unref(appsink);
+
+    return false;
+}
+
+/*
+ * The idea of using buffer probes is as follows. We set a buffer probe on the
+ * encoder sink pad. In the buffer probe, we set an idle probe on the upstream
+ * source pad. In encode_buffer, we wait on the fdsem. The fdsem gets posted
+ * when either new_sample or idle probe gets called. We do this, to make the
+ * appsink behave synchronously.
+ *
+ * For buffer probes, see
+ * https://gstreamer.freedesktop.org/documentation/additional/design/probes.html?gi-language=c
+ */
+static GstPadProbeReturn gst_enc_appsink_buffer_probe(GstPad *pad, GstPadProbeInfo *probe_info, gpointer userdata)
+{
+    struct gst_info *info = (struct gst_info *)userdata;
+
+    pa_assert(probe_info->type & GST_PAD_PROBE_TYPE_IDLE);
+
+    pa_fdsem_post(info->enc_fdsem);
+
+    return GST_PAD_PROBE_REMOVE;
+}
+
+static GstPadProbeReturn gst_encoder_buffer_probe(GstPad *pad, GstPadProbeInfo *probe_info, gpointer userdata)
+{
+    struct gst_info *info = (struct gst_info *)userdata;
+    GstPad *peer_pad;
+
+    pa_assert(probe_info->type & GST_PAD_PROBE_TYPE_BUFFER);
+
+    peer_pad = gst_pad_get_peer(pad);
+    gst_pad_add_probe(peer_pad, GST_PAD_PROBE_TYPE_IDLE, gst_enc_appsink_buffer_probe, info, NULL);
+    gst_object_unref(peer_pad);
+
+    return GST_PAD_PROBE_OK;
+}
+
+static GstPadProbeReturn gst_dec_appsink_buffer_probe(GstPad *pad, GstPadProbeInfo *probe_info, gpointer userdata)
+{
+    struct gst_info *info = (struct gst_info *)userdata;
+
+    pa_assert(probe_info->type & GST_PAD_PROBE_TYPE_IDLE);
+
+    pa_fdsem_post(info->dec_fdsem);
+
+    return GST_PAD_PROBE_REMOVE;
+}
+
+static GstPadProbeReturn gst_decoder_buffer_probe(GstPad *pad, GstPadProbeInfo *probe_info, gpointer userdata)
+{
+    struct gst_info *info = (struct gst_info *)userdata;
+    GstPad *peer_pad;
+
+    pa_assert(probe_info->type & GST_PAD_PROBE_TYPE_BUFFER);
+
+    peer_pad = gst_pad_get_peer(pad);
+    gst_pad_add_probe(peer_pad, GST_PAD_PROBE_TYPE_IDLE, gst_dec_appsink_buffer_probe, info, NULL);
+    gst_object_unref(peer_pad);
+
+    return GST_PAD_PROBE_OK;
+}
+
+bool gst_codec_init(struct gst_info *info, bool for_encoding) {
+    GstPad *pad;
+
+    info->seq_num = 0;
+
+    /* In case if we ever have a codec which supports decoding but not encoding */
+    if (for_encoding && info->enc_bin) {
+        gst_bin_add_many(GST_BIN(info->enc_pipeline), info->enc_src, info->enc_bin, info->enc_sink, NULL);
+
+        if (!gst_element_link_many(info->enc_src, info->enc_bin, info->enc_sink, NULL)) {
+            pa_log_error("Failed to link encoder elements");
+            goto enc_dec_fail;
+        }
+
+        if (gst_element_set_state(info->enc_pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
+            pa_log_error("Could not start encoder pipeline");
+            goto enc_dec_fail;
+        }
+
+        /* See the comment on buffer probe functions */
+        pad = gst_element_get_static_pad(info->enc_bin, "sink");
+        gst_pad_add_probe(pad, GST_PAD_PROBE_TYPE_BUFFER, gst_encoder_buffer_probe, info, NULL);
+        gst_object_unref(pad);
+    } else if (!for_encoding && info->dec_bin) {
+        gst_bin_add_many(GST_BIN(info->dec_pipeline), info->dec_src, info->dec_bin, info->dec_sink, NULL);
+
+        if (!gst_element_link_many(info->dec_src, info->dec_bin, info->dec_sink, NULL)) {
+            pa_log_error("Failed to link decoder elements");
+            goto enc_dec_fail;
+        }
+
+        if (gst_element_set_state(info->dec_pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
+            pa_log_error("Could not start decoder pipeline");
+            goto enc_dec_fail;
+        }
+
+        /* See the comment on buffer probe functions */
+        pad = gst_element_get_static_pad(info->dec_bin, "sink");
+        gst_pad_add_probe(pad, GST_PAD_PROBE_TYPE_BUFFER, gst_decoder_buffer_probe, info, NULL);
+        gst_object_unref(pad);
+    } else
+        pa_assert_not_reached();
+
+    pa_log_info("Gstreamer pipeline initialisation succeeded");
+
+    return true;
+
+enc_dec_fail:
+    if (for_encoding)
+        gst_deinit_enc_common(info);
+    else
+        gst_deinit_dec_common(info);
+
+    pa_log_error("Gstreamer pipeline initialisation failed");
+
+    return false;
+}
+
+size_t gst_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;
+    gsize available, encoded;
+    GstBuffer *in_buf;
+    GstMapInfo map_info;
+    GstFlowReturn ret;
+    size_t written = 0;
+
+    in_buf = gst_buffer_new_allocate(NULL, input_size, NULL);
+    pa_assert(in_buf);
+
+    pa_assert_se(gst_buffer_map(in_buf, &map_info, GST_MAP_WRITE));
+    memcpy(map_info.data, input_buffer, input_size);
+    gst_buffer_unmap(in_buf, &map_info);
+
+    ret = gst_app_src_push_buffer(GST_APP_SRC(info->enc_src), in_buf);
+    if (ret != GST_FLOW_OK) {
+        pa_log_error("failed to push buffer for encoding %d", ret);
+        goto fail;
+    }
+
+    pa_fdsem_wait(info->enc_fdsem);
+
+    available = gst_adapter_available(info->enc_adapter);
+
+    if (available) {
+        encoded = PA_MIN(available, output_size);
+
+        gst_adapter_copy(info->enc_adapter, output_buffer, 0, encoded);
+        gst_adapter_flush(info->enc_adapter, encoded);
+
+        written += encoded;
+    } else
+        pa_log_debug("No encoded data available in adapter");
+
+    *processed = input_size;
+
+    return written;
+
+fail:
+    *processed = 0;
+
+    return written;
+}
+
+size_t gst_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 gst_info *info = (struct gst_info *) codec_info;
+    gsize available, decoded;
+    GstBuffer *in_buf;
+    GstMapInfo map_info;
+    GstFlowReturn ret;
+    size_t written = 0;
+
+    in_buf = gst_buffer_new_allocate(NULL, input_size, NULL);
+    pa_assert(in_buf);
+
+    pa_assert_se(gst_buffer_map(in_buf, &map_info, GST_MAP_WRITE));
+    memcpy(map_info.data, input_buffer, input_size);
+    gst_buffer_unmap(in_buf, &map_info);
+
+    ret = gst_app_src_push_buffer(GST_APP_SRC(info->dec_src), in_buf);
+    if (ret != GST_FLOW_OK) {
+        pa_log_error("failed to push buffer for decoding %d", ret);
+        goto fail;
+    }
+
+    pa_fdsem_wait(info->dec_fdsem);
+
+    available = gst_adapter_available(info->dec_adapter);
+
+    if (available) {
+        decoded = PA_MIN(available, output_size);
+
+        gst_adapter_copy(info->dec_adapter, output_buffer, 0, decoded);
+        gst_adapter_flush(info->dec_adapter, decoded);
+
+        written += decoded;
+    } else
+        pa_log_debug("No decoded data available in adapter");
+
+    *processed = input_size;
+
+    return written;
+
+fail:
+    *processed = 0;
+
+    return written;
+}
+
+void gst_codec_deinit(void *codec_info) {
+    struct gst_info *info = (struct gst_info *) codec_info;
+
+    if (info->enc_fdsem)
+        pa_fdsem_free(info->enc_fdsem);
+
+    if (info->dec_fdsem)
+        pa_fdsem_free(info->dec_fdsem);
+
+    if (info->enc_pipeline) {
+        gst_element_set_state(info->enc_pipeline, GST_STATE_NULL);
+        gst_object_unref(info->enc_pipeline);
+    }
+
+    if (info->dec_pipeline) {
+        gst_element_set_state(info->dec_pipeline, GST_STATE_NULL);
+        gst_object_unref(info->dec_pipeline);
+    }
+
+    if (info->enc_adapter)
+        g_object_unref(info->enc_adapter);
+
+    if (info->dec_adapter)
+        g_object_unref(info->dec_adapter);
+
+    pa_xfree(info);
+}


=====================================
src/modules/bluetooth/a2dp-codec-gst.h
=====================================
@@ -0,0 +1,66 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright (C) 2020 Asymptotic <sanchayan at 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/>.
+***/
+
+#include <gst/gst.h>
+#include <gst/app/gstappsrc.h>
+#include <gst/app/gstappsink.h>
+#include <gst/base/gstadapter.h>
+#include <pulsecore/fdsem.h>
+
+enum a2dp_codec_type {
+    AAC = 0,
+    APTX,
+    APTX_HD,
+    LDAC_EQMID_HQ,
+    LDAC_EQMID_SQ,
+    LDAC_EQMID_MQ
+};
+
+struct gst_info {
+    pa_core *core;
+    pa_sample_spec *ss;
+    enum a2dp_codec_type codec_type;
+    union {
+        const a2dp_aac_t *aac_config;
+        const a2dp_aptx_t *aptx_config;
+        const a2dp_aptx_hd_t *aptx_hd_config;
+        const a2dp_ldac_t *ldac_config;
+    } a2dp_codec_t;
+
+    GstElement *enc_bin, *dec_bin;
+    GstElement *enc_src, *enc_sink;
+    GstElement *dec_src, *dec_sink;
+    GstElement *enc_pipeline, *dec_pipeline;
+    GstAdapter *enc_adapter, *dec_adapter;
+
+    pa_fdsem *enc_fdsem;
+    pa_fdsem *dec_fdsem;
+
+    uint16_t seq_num;
+};
+
+bool gst_codec_init(struct gst_info *info, bool for_encoding);
+size_t gst_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);
+size_t gst_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);
+void gst_codec_deinit(void *codec_info);
+
+bool gst_init_enc_common(struct gst_info *info);
+bool gst_init_dec_common(struct gst_info *info);
+void gst_deinit_enc_common(struct gst_info *info);
+void gst_deinit_dec_common(struct gst_info *info);


=====================================
src/modules/bluetooth/a2dp-codec-ldac-gst.c
=====================================
@@ -0,0 +1,491 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright (C) 2020 Asymptotic <sanchayan at 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(void) {
+    GstElementFactory *element_factory;
+
+    element_factory = gst_element_factory_find("ldacenc");
+    if (element_factory == NULL) {
+        pa_log_info("LDAC encoder not found");
+        return false;
+    }
+
+    gst_object_unref(element_factory);
+
+    return true;
+}
+
+static bool can_accept_capabilities_common(const a2dp_ldac_t *capabilities, uint32_t vendor_id, uint16_t codec_id) {
+    if (A2DP_GET_VENDOR_ID(capabilities->info) != vendor_id || A2DP_GET_CODEC_ID(capabilities->info) != codec_id)
+        return false;
+
+    if (!(capabilities->frequency & (LDAC_SAMPLING_FREQ_44100 | LDAC_SAMPLING_FREQ_48000 |
+                                     LDAC_SAMPLING_FREQ_88200 | LDAC_SAMPLING_FREQ_96000)))
+        return false;
+
+    if (!(capabilities->channel_mode & LDAC_CHANNEL_MODE_STEREO))
+        return false;
+
+    return true;
+}
+
+static bool can_accept_capabilities(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding) {
+    const a2dp_ldac_t *capabilities = (const a2dp_ldac_t *) capabilities_buffer;
+
+    if (capabilities_size != sizeof(*capabilities))
+        return false;
+
+    return can_accept_capabilities_common(capabilities, LDAC_VENDOR_ID, LDAC_CODEC_ID);
+}
+
+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 void fill_capabilities_common(a2dp_ldac_t *capabilities, uint32_t vendor_id, uint16_t codec_id) {
+    capabilities->info = A2DP_SET_VENDOR_ID_CODEC_ID(vendor_id, codec_id);
+    capabilities->channel_mode = LDAC_CHANNEL_MODE_STEREO;
+    capabilities->frequency = LDAC_SAMPLING_FREQ_44100 | LDAC_SAMPLING_FREQ_48000 |
+                              LDAC_SAMPLING_FREQ_88200 | LDAC_SAMPLING_FREQ_96000;
+}
+
+static uint8_t fill_capabilities(uint8_t capabilities_buffer[MAX_A2DP_CAPS_SIZE]) {
+    a2dp_ldac_t *capabilities = (a2dp_ldac_t *) capabilities_buffer;
+
+    pa_zero(*capabilities);
+    fill_capabilities_common(capabilities, LDAC_VENDOR_ID, LDAC_CODEC_ID);
+    return sizeof(*capabilities);
+}
+
+static bool is_configuration_valid(const uint8_t *config_buffer, uint8_t config_size) {
+    const a2dp_ldac_t *config = (const a2dp_ldac_t *) config_buffer;
+
+    if (config_size != sizeof(*config)) {
+        pa_log_error("Invalid size of config buffer");
+        return false;
+    }
+
+    if (A2DP_GET_VENDOR_ID(config->info) != LDAC_VENDOR_ID || A2DP_GET_CODEC_ID(config->info) != LDAC_CODEC_ID) {
+        pa_log_error("Invalid vendor codec information in configuration");
+        return false;
+    }
+
+    if (config->frequency != LDAC_SAMPLING_FREQ_44100 && config->frequency != LDAC_SAMPLING_FREQ_48000 &&
+        config->frequency != LDAC_SAMPLING_FREQ_88200 && config->frequency != LDAC_SAMPLING_FREQ_96000) {
+        pa_log_error("Invalid sampling frequency in configuration");
+        return false;
+    }
+
+    if (config->channel_mode != LDAC_CHANNEL_MODE_STEREO) {
+        pa_log_error("Invalid channel mode in configuration");
+        return false;
+    }
+
+    return true;
+}
+
+static int fill_preferred_configuration_common(const pa_sample_spec *default_sample_spec, const a2dp_ldac_t *capabilities, a2dp_ldac_t *config, uint32_t vendor_id, uint16_t codec_id) {
+    int i;
+
+    static const struct {
+        uint32_t rate;
+        uint8_t cap;
+    } freq_table[] = {
+        { 44100U, LDAC_SAMPLING_FREQ_44100 },
+        { 48000U, LDAC_SAMPLING_FREQ_48000 },
+        { 88200U, LDAC_SAMPLING_FREQ_88200 },
+        { 96000U, LDAC_SAMPLING_FREQ_96000 }
+    };
+
+    if (A2DP_GET_VENDOR_ID(capabilities->info) != LDAC_VENDOR_ID || A2DP_GET_CODEC_ID(capabilities->info) != LDAC_CODEC_ID) {
+        pa_log_error("No supported vendor codec information");
+        return -1;
+    }
+
+    config->info = A2DP_SET_VENDOR_ID_CODEC_ID(vendor_id, codec_id);
+
+    if (!(capabilities->channel_mode & LDAC_CHANNEL_MODE_STEREO)) {
+        pa_log_error("No supported channel modes");
+        return -1;
+    }
+
+    config->channel_mode = LDAC_CHANNEL_MODE_STEREO;
+
+    /* 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 && (capabilities->frequency & freq_table[i].cap)) {
+            config->frequency = freq_table[i].cap;
+            break;
+        }
+    }
+
+    if ((unsigned) i == PA_ELEMENTSOF(freq_table)) {
+        for (--i; i >= 0; i--) {
+            if (capabilities->frequency & freq_table[i].cap) {
+                config->frequency = freq_table[i].cap;
+                break;
+            }
+        }
+
+        if (i < 0) {
+            pa_log_error("Not suitable sample rate");
+            return false;
+        }
+    }
+
+    return 0;
+}
+
+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_ldac_t *config = (a2dp_ldac_t *) config_buffer;
+    const a2dp_ldac_t *capabilities = (const a2dp_ldac_t *) capabilities_buffer;
+
+    if (capabilities_size != sizeof(*capabilities)) {
+        pa_log_error("Invalid size of capabilities buffer");
+        return 0;
+    }
+
+    pa_zero(*config);
+
+    if (fill_preferred_configuration_common(default_sample_spec, capabilities, config, LDAC_VENDOR_ID, LDAC_CODEC_ID) < 0)
+        return 0;
+
+    return sizeof(*config);
+}
+
+bool gst_init_ldac(struct gst_info *info, pa_sample_spec *ss, bool for_encoding) {
+    GstElement *rtpldacpay;
+    GstElement *enc;
+    GstCaps *caps;
+    GstPad *pad;
+
+    if (!for_encoding) {
+        pa_log_error("LDAC does not support decoding");
+        return false;
+    }
+
+    ss->format = PA_SAMPLE_S32LE;
+
+    switch (info->a2dp_codec_t.ldac_config->frequency) {
+        case LDAC_SAMPLING_FREQ_44100:
+            ss->rate = 44100u;
+            break;
+        case LDAC_SAMPLING_FREQ_48000:
+            ss->rate = 48000u;
+            break;
+        case LDAC_SAMPLING_FREQ_88200:
+            ss->rate = 88200;
+            break;
+        case LDAC_SAMPLING_FREQ_96000:
+            ss->rate = 96000;
+            break;
+        default:
+            pa_log_error("LDAC invalid frequency %d", info->a2dp_codec_t.ldac_config->frequency);
+            goto fail;
+    }
+
+    switch (info->a2dp_codec_t.ldac_config->channel_mode) {
+        case LDAC_CHANNEL_MODE_STEREO:
+            ss->channels = 2;
+            break;
+        case LDAC_CHANNEL_MODE_MONO:
+            ss->channels = 1;
+            break;
+        case LDAC_CHANNEL_MODE_DUAL:
+            ss->channels = 1;
+            break;
+        default:
+            pa_log_error("LDAC invalid channel mode %d", info->a2dp_codec_t.ldac_config->channel_mode);
+            goto fail;
+    }
+
+    enc = gst_element_factory_make("ldacenc", "ldac_enc");
+    if (!enc) {
+        pa_log_error("Could not create LDAC encoder element");
+        goto fail;
+    }
+
+    switch (info->codec_type) {
+        case LDAC_EQMID_HQ:
+            g_object_set(enc, "eqmid", 0, NULL);
+            break;
+        case LDAC_EQMID_SQ:
+            g_object_set(enc, "eqmid", 1, NULL);
+            break;
+        case LDAC_EQMID_MQ:
+            g_object_set(enc, "eqmid", 2, NULL);
+            break;
+        default:
+            goto fail;
+    }
+
+    caps = gst_caps_new_simple("audio/x-raw",
+            "format", G_TYPE_STRING, "S32LE",
+            "rate", G_TYPE_INT, (int) ss->rate,
+            "channels", G_TYPE_INT, (int) ss->channels,
+            "channel-mask", G_TYPE_INT, 0,
+            "layout", G_TYPE_STRING, "interleaved",
+            NULL);
+    g_object_set(info->enc_src, "caps", caps, NULL);
+    gst_caps_unref(caps);
+
+    rtpldacpay = gst_element_factory_make("rtpldacpay", "rtp_ldac_pay");
+    if (!rtpldacpay) {
+        pa_log_error("Could not create RTP LDAC payloader element");
+        goto fail;
+    }
+
+    info->enc_bin = gst_bin_new("ldac_enc_bin");
+    pa_assert(info->enc_bin);
+
+    gst_bin_add_many(GST_BIN(info->enc_bin), enc, rtpldacpay, NULL);
+
+    if (!gst_element_link(enc, rtpldacpay)) {
+        pa_log_error("Failed to link LDAC encoder to LDAC RTP payloader");
+        return false;
+    }
+
+    pad = gst_element_get_static_pad(enc, "sink");
+    gst_element_add_pad(info->enc_bin, gst_ghost_pad_new("sink", pad));
+    gst_object_unref(GST_OBJECT(pad));
+
+    pad = gst_element_get_static_pad(rtpldacpay, "src");
+    gst_element_add_pad(info->enc_bin, gst_ghost_pad_new("src", pad));
+    gst_object_unref(GST_OBJECT(pad));
+
+    return true;
+
+fail:
+    pa_log_error("LDAC encoder initialisation failed");
+    return false;
+}
+
+static void *init_common(enum a2dp_codec_type codec_type, bool for_encoding, bool for_backchannel, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *sample_spec, pa_core *core) {
+    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 = codec_type;
+    info->a2dp_codec_t.ldac_config = (const a2dp_ldac_t *) config_buffer;
+    pa_assert(config_size == sizeof(*(info->a2dp_codec_t.ldac_config)));
+
+    /*
+     * The common encoder/decoder initialisation functions need to be called
+     * before the codec specific ones, as the codec specific initialisation
+     * function needs to set the caps specific property appropriately on the
+     * appsrc and appsink as per the sample spec and the codec.
+     */
+    if (!gst_init_enc_common(info))
+        goto fail;
+
+    if (!gst_init_ldac(info, sample_spec, for_encoding))
+        goto enc_fail;
+
+    if (!gst_codec_init(info, for_encoding))
+        goto enc_fail;
+
+    return info;
+
+enc_fail:
+    gst_deinit_enc_common(info);
+fail:
+    if (info)
+        pa_xfree(info);
+
+    return NULL;
+}
+
+static void *init_hq(bool for_encoding, bool for_backchannel, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *sample_spec, pa_core *core) {
+    return init_common(LDAC_EQMID_HQ, for_encoding, for_backchannel, config_buffer, config_size, sample_spec, core);
+}
+
+static void *init_sq(bool for_encoding, bool for_backchannel, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *sample_spec, pa_core *core) {
+    return init_common(LDAC_EQMID_SQ, for_encoding, for_backchannel, config_buffer, config_size, sample_spec, core);
+}
+
+static void *init_mq(bool for_encoding, bool for_backchannel, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *sample_spec, pa_core *core) {
+    return init_common(LDAC_EQMID_MQ, for_encoding, for_backchannel, config_buffer, config_size, sample_spec, core);
+}
+
+static void deinit(void *codec_info) {
+    return gst_codec_deinit(codec_info);
+}
+
+static int reset(void *codec_info) {
+    return 0;
+}
+
+static uint32_t get_ldac_num_samples(void *codec_info) {
+    struct gst_info *info = (struct gst_info *) codec_info;
+
+    switch (info->a2dp_codec_t.ldac_config->frequency) {
+        case LDAC_SAMPLING_FREQ_44100:
+        case LDAC_SAMPLING_FREQ_48000:
+            return 128;
+            break;
+        case LDAC_SAMPLING_FREQ_88200:
+        case LDAC_SAMPLING_FREQ_96000:
+            return 256;
+            break;
+        default:
+            break;
+    }
+
+    return 128;
+}
+
+static uint8_t get_ldac_num_frames(void *codec_info, enum a2dp_codec_type codec_type) {
+    struct gst_info *info = (struct gst_info *) codec_info;
+    uint8_t channels;
+
+    switch (info->a2dp_codec_t.ldac_config->channel_mode) {
+        case LDAC_CHANNEL_MODE_STEREO:
+            channels = 2;
+            break;
+        case LDAC_CHANNEL_MODE_MONO:
+        case LDAC_CHANNEL_MODE_DUAL:
+            channels = 1;
+            break;
+        default:
+            break;
+    }
+
+    switch (codec_type) {
+        case LDAC_EQMID_HQ:
+            return 4 / channels;
+        case LDAC_EQMID_SQ:
+            return 6 / channels;
+        case LDAC_EQMID_MQ:
+            return 12 / channels;
+        default:
+            break;
+    }
+
+    return 6 / channels;
+}
+
+static size_t get_block_size(void *codec_info, size_t link_mtu) {
+    struct gst_info *info = (struct gst_info *) codec_info;
+
+    return get_ldac_num_samples(codec_info) * get_ldac_num_frames(codec_info, info->codec_type) * 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) {
+    size_t written;
+
+    written = gst_encode_buffer(codec_info, timestamp, input_buffer, input_size, output_buffer, output_size, processed);
+    if (PA_UNLIKELY(*processed != input_size))
+        pa_log_error("LDAC encoding error");
+
+    return written;
+}
+
+const pa_a2dp_codec pa_a2dp_codec_ldac_eqmid_hq = {
+    .name = "ldac_hq",
+    .description = "LDAC (High Quality)",
+    .id = { A2DP_CODEC_VENDOR, LDAC_VENDOR_ID, LDAC_CODEC_ID },
+    .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,
+    .init = init_hq,
+    .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,
+};
+
+const pa_a2dp_codec pa_a2dp_codec_ldac_eqmid_sq = {
+    .name = "ldac_sq",
+    .description = "LDAC (Standard Quality)",
+    .id = { A2DP_CODEC_VENDOR, LDAC_VENDOR_ID, LDAC_CODEC_ID },
+    .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,
+    .init = init_sq,
+    .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,
+};
+
+const pa_a2dp_codec pa_a2dp_codec_ldac_eqmid_mq = {
+    .name = "ldac_mq",
+    .description = "LDAC (Mobile Quality)",
+    .id = { A2DP_CODEC_VENDOR, LDAC_VENDOR_ID, LDAC_CODEC_ID },
+    .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,
+    .init = init_mq,
+    .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,
+};


=====================================
src/modules/bluetooth/a2dp-codec-sbc.c
=====================================
@@ -53,6 +53,10 @@ struct sbc_info {
     uint8_t max_bitpool;
 };
 
+static bool can_be_supported(void) {
+    return true;
+}
+
 static bool can_accept_capabilities(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding) {
     const a2dp_sbc_t *capabilities = (const a2dp_sbc_t *) capabilities_buffer;
 
@@ -323,7 +327,7 @@ static void set_params(struct sbc_info *sbc_info) {
     sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc);
 }
 
-static void *init(bool for_encoding, bool for_backchannel, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *sample_spec) {
+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) {
     struct sbc_info *sbc_info;
     const a2dp_sbc_t *config = (const a2dp_sbc_t *) config_buffer;
     int ret;
@@ -666,6 +670,7 @@ const pa_a2dp_codec pa_a2dp_codec_sbc = {
     .description = "SBC",
     .id = { A2DP_CODEC_SBC, 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,


=====================================
src/modules/bluetooth/a2dp-codec-util.c
=====================================
@@ -23,14 +23,35 @@
 
 #include <pulsecore/core.h>
 #include <pulsecore/core-util.h>
+#if defined(HAVE_GSTAPTX) || defined(HAVE_GSTLDAC)
+#include <gst/gst.h>
+#endif
 
 #include "a2dp-codec-util.h"
 
 extern const pa_a2dp_codec pa_a2dp_codec_sbc;
+#ifdef HAVE_GSTAPTX
+extern const pa_a2dp_codec pa_a2dp_codec_aptx;
+extern const pa_a2dp_codec pa_a2dp_codec_aptx_hd;
+#endif
+#ifdef HAVE_GSTLDAC
+extern const pa_a2dp_codec pa_a2dp_codec_ldac_eqmid_hq;
+extern const pa_a2dp_codec pa_a2dp_codec_ldac_eqmid_sq;
+extern const pa_a2dp_codec pa_a2dp_codec_ldac_eqmid_mq;
+#endif
 
 /* This is list of supported codecs. Their order is important.
- * Codec with higher index has higher priority. */
-const pa_a2dp_codec *pa_a2dp_codecs[] = {
+ * Codec with lower index has higher priority. */
+static const pa_a2dp_codec *pa_a2dp_codecs[] = {
+#ifdef HAVE_GSTLDAC
+    &pa_a2dp_codec_ldac_eqmid_hq,
+    &pa_a2dp_codec_ldac_eqmid_sq,
+    &pa_a2dp_codec_ldac_eqmid_mq,
+#endif
+#ifdef HAVE_GSTAPTX
+    &pa_a2dp_codec_aptx_hd,
+    &pa_a2dp_codec_aptx,
+#endif
     &pa_a2dp_codec_sbc,
 };
 
@@ -54,3 +75,30 @@ const pa_a2dp_codec *pa_bluetooth_get_a2dp_codec(const char *name) {
 
     return NULL;
 }
+
+void pa_bluetooth_a2dp_codec_gst_init(void) {
+#if defined(HAVE_GSTAPTX) || defined(HAVE_GSTLDAC)
+    GError *error = NULL;
+
+    if (!gst_init_check(NULL, NULL, &error)) {
+        pa_log_error("Could not initialise GStreamer: %s", error->message);
+        g_error_free(error);
+        return;
+    }
+    pa_log_info("GStreamer initialisation done");
+#endif
+}
+
+bool pa_bluetooth_a2dp_codec_is_codec_available(const pa_a2dp_codec_id *id, bool is_a2dp_sink) {
+    unsigned int i;
+    unsigned int count = pa_bluetooth_a2dp_codec_count();
+
+    for (i = 0; i < count; i++) {
+        if (memcmp(id, &pa_a2dp_codecs[i]->id, sizeof(pa_a2dp_codec_id)) == 0) {
+            return (is_a2dp_sink && pa_a2dp_codecs[i]->encode_buffer != NULL)
+                || (!is_a2dp_sink && pa_a2dp_codecs[i]->decode_buffer != NULL);
+        }
+    }
+
+    return false;
+}


=====================================
src/modules/bluetooth/a2dp-codec-util.h
=====================================
@@ -31,4 +31,10 @@ const pa_a2dp_codec *pa_bluetooth_a2dp_codec_iter(unsigned int i);
 /* Get codec by name */
 const pa_a2dp_codec *pa_bluetooth_get_a2dp_codec(const char *name);
 
+/* Check if the given codec can be supported in A2DP_SINK or A2DP_SOURCE */
+bool pa_bluetooth_a2dp_codec_is_codec_available(const pa_a2dp_codec_id *id, bool is_a2dp_sink);
+
+/* Initialise GStreamer */
+void pa_bluetooth_a2dp_codec_gst_init(void);
+
 #endif


=====================================
src/modules/bluetooth/bluez5-util.c
=====================================
@@ -34,6 +34,7 @@
 #include <pulsecore/refcnt.h>
 #include <pulsecore/shared.h>
 
+#include "a2dp-codec-api.h"
 #include "a2dp-codec-util.h"
 #include "a2dp-codecs.h"
 
@@ -50,8 +51,34 @@
 
 #define BLUEZ_ERROR_NOT_SUPPORTED "org.bluez.Error.NotSupported"
 
-#define A2DP_SOURCE_ENDPOINT "/MediaEndpoint/A2DPSource"
-#define A2DP_SINK_ENDPOINT "/MediaEndpoint/A2DPSink"
+#define A2DP_OBJECT_MANAGER_PATH "/MediaEndpoint"
+#define A2DP_SOURCE_ENDPOINT A2DP_OBJECT_MANAGER_PATH "/A2DPSource"
+#define A2DP_SINK_ENDPOINT A2DP_OBJECT_MANAGER_PATH "/A2DPSink"
+
+#define OBJECT_MANAGER_INTROSPECT_XML                                          \
+    DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                                  \
+    "<node>\n"                                                                 \
+    " <interface name=\"org.freedesktop.DBus.ObjectManager\">\n"               \
+    "  <method name=\"GetManagedObjects\">\n"                                  \
+    "   <arg name=\"objects\" direction=\"out\" type=\"a{oa{sa{sv}}}\"/>\n"    \
+    "  </method>\n"                                                            \
+    "  <signal name=\"InterfacesAdded\">\n"                                    \
+    "   <arg name=\"object\" type=\"o\"/>\n"                                   \
+    "   <arg name=\"interfaces\" type=\"a{sa{sv}}\"/>\n"                       \
+    "  </signal>\n"                                                            \
+    "  <signal name=\"InterfacesRemoved\">\n"                                  \
+    "   <arg name=\"object\" type=\"o\"/>\n"                                   \
+    "   <arg name=\"interfaces\" type=\"as\"/>\n"                              \
+    "  </signal>\n"                                                            \
+    " </interface>\n"                                                          \
+    " <interface name=\"org.freedesktop.DBus.Introspectable\">\n"              \
+    "  <method name=\"Introspect\">\n"                                         \
+    "   <arg name=\"data\" direction=\"out\" type=\"s\"/>\n"                   \
+    "  </method>\n"                                                            \
+    " </interface>\n"                                                          \
+    " <node name=\"A2DPSink\"/>\n"                                             \
+    " <node name=\"A2DPSource\"/>\n"                                           \
+    "</node>\n"
 
 #define ENDPOINT_INTROSPECT_XML                                         \
     DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                           \
@@ -260,6 +287,123 @@ static void device_start_waiting_for_profiles(pa_bluetooth_device *device) {
                                                          wait_for_profiles_cb, device);
 }
 
+struct switch_codec_data {
+    char *pa_endpoint;
+    char *device_path;
+    pa_bluetooth_profile_t profile;
+    void (*cb)(bool, pa_bluetooth_profile_t profile, void *);
+    void *userdata;
+};
+
+static void pa_bluetooth_switch_codec_reply(DBusPendingCall *pending, void *userdata) {
+    DBusMessage *r;
+    pa_dbus_pending *p;
+    pa_bluetooth_discovery *y;
+    pa_bluetooth_device *device;
+    struct switch_codec_data *data;
+
+    pa_assert(pending);
+    pa_assert_se(p = userdata);
+    pa_assert_se(y = p->context_data);
+    pa_assert_se(data = p->call_data);
+    pa_assert_se(r = dbus_pending_call_steal_reply(pending));
+
+    PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
+    pa_dbus_pending_free(p);
+
+    device = pa_hashmap_get(y->devices, data->device_path);
+    if (!device) {
+        pa_log_error("Changing codec for device %s with profile %s failed. Device is not connected anymore",
+                data->device_path, pa_bluetooth_profile_to_string(data->profile));
+        data->cb(false, data->profile, data->userdata);
+    } else if (dbus_message_get_type(r) != DBUS_MESSAGE_TYPE_ERROR) {
+        pa_log_info("Changing codec for device %s with profile %s succeeded",
+                data->device_path, pa_bluetooth_profile_to_string(data->profile));
+        data->cb(true, data->profile, data->userdata);
+    } else if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
+        pa_log_error("Changing codec for device %s with profile %s failed. Error: %s",
+                data->device_path, pa_bluetooth_profile_to_string(data->profile),
+                dbus_message_get_error_name(r));
+    }
+
+    dbus_message_unref(r);
+
+    pa_xfree(data->pa_endpoint);
+    pa_xfree(data->device_path);
+    pa_xfree(data);
+
+    device->codec_switching_in_progress = false;
+}
+
+bool pa_bluetooth_switch_codec(pa_bluetooth_device *device, pa_bluetooth_profile_t profile,
+        pa_hashmap *capabilities_hashmap, const pa_a2dp_codec *a2dp_codec,
+        void (*codec_switch_cb)(bool, pa_bluetooth_profile_t profile, void *), void *userdata) {
+    DBusMessageIter iter, dict;
+    DBusMessage *m;
+    struct switch_codec_data *data;
+    pa_a2dp_codec_capabilities *capabilities;
+    uint8_t config[MAX_A2DP_CAPS_SIZE];
+    uint8_t config_size;
+    bool is_a2dp_sink;
+    pa_hashmap *all_endpoints;
+    char *pa_endpoint;
+    const char *endpoint;
+
+    pa_assert(device);
+    pa_assert(capabilities_hashmap);
+    pa_assert(a2dp_codec);
+
+    if (device->codec_switching_in_progress) {
+        pa_log_error("Codec switching operation already in progress");
+        return false;
+    }
+
+    is_a2dp_sink = profile == PA_BLUETOOTH_PROFILE_A2DP_SINK;
+
+    all_endpoints = NULL;
+    all_endpoints = pa_hashmap_get(is_a2dp_sink ? device->a2dp_sink_endpoints : device->a2dp_source_endpoints,
+            &a2dp_codec->id);
+    pa_assert(all_endpoints);
+
+    pa_assert_se(endpoint = a2dp_codec->choose_remote_endpoint(capabilities_hashmap, &device->discovery->core->default_sample_spec, is_a2dp_sink));
+    pa_assert_se(capabilities = pa_hashmap_get(all_endpoints, endpoint));
+
+    config_size = a2dp_codec->fill_preferred_configuration(&device->discovery->core->default_sample_spec,
+            capabilities->buffer, capabilities->size, config);
+    if (config_size == 0)
+        return false;
+
+    pa_endpoint = pa_sprintf_malloc("%s/%s", is_a2dp_sink ? A2DP_SOURCE_ENDPOINT : A2DP_SINK_ENDPOINT,
+            a2dp_codec->name);
+
+    pa_assert_se(m = dbus_message_new_method_call(BLUEZ_SERVICE, endpoint,
+                BLUEZ_MEDIA_ENDPOINT_INTERFACE, "SetConfiguration"));
+
+    dbus_message_iter_init_append(m, &iter);
+    pa_assert_se(dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &pa_endpoint));
+    dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+            DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+            DBUS_TYPE_STRING_AS_STRING
+            DBUS_TYPE_VARIANT_AS_STRING
+            DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
+            &dict);
+    pa_dbus_append_basic_array_variant_dict_entry(&dict, "Capabilities", DBUS_TYPE_BYTE, &config, config_size);
+    dbus_message_iter_close_container(&iter, &dict);
+
+    device->codec_switching_in_progress = true;
+
+    data = pa_xnew0(struct switch_codec_data, 1);
+    data->pa_endpoint = pa_endpoint;
+    data->device_path = pa_xstrdup(device->path);
+    data->profile = profile;
+    data->cb = codec_switch_cb;
+    data->userdata = userdata;
+
+    send_and_add_to_pending(device->discovery, m, pa_bluetooth_switch_codec_reply, data);
+
+    return true;
+}
+
 void pa_bluetooth_transport_set_state(pa_bluetooth_transport *t, pa_bluetooth_transport_state_t state) {
     bool old_any_connected;
     unsigned n_disconnected_profiles;
@@ -510,6 +654,59 @@ static int parse_transport_properties(pa_bluetooth_transport *t, DBusMessageIter
     return 0;
 }
 
+static unsigned pa_a2dp_codec_id_hash_func(const void *_p) {
+    unsigned hash;
+    const pa_a2dp_codec_id *p = _p;
+
+    hash = p->codec_id;
+    hash = 31 * hash + ((p->vendor_id >>  0) & 0xFF);
+    hash = 31 * hash + ((p->vendor_id >>  8) & 0xFF);
+    hash = 31 * hash + ((p->vendor_id >> 16) & 0xFF);
+    hash = 31 * hash + ((p->vendor_id >> 24) & 0xFF);
+    hash = 31 * hash + ((p->vendor_codec_id >> 0) & 0xFF);
+    hash = 31 * hash + ((p->vendor_codec_id >> 8) & 0xFF);
+    return hash;
+}
+
+static int pa_a2dp_codec_id_compare_func(const void *_a, const void *_b) {
+    const pa_a2dp_codec_id *a = _a;
+    const pa_a2dp_codec_id *b = _b;
+
+    if (a->codec_id < b->codec_id)
+        return -1;
+    if (a->codec_id > b->codec_id)
+        return 1;
+
+    if (a->vendor_id < b->vendor_id)
+        return -1;
+    if (a->vendor_id > b->vendor_id)
+        return 1;
+
+    if (a->vendor_codec_id < b->vendor_codec_id)
+        return -1;
+    if (a->vendor_codec_id > b->vendor_codec_id)
+        return 1;
+
+    return 0;
+}
+
+static void remote_endpoint_remove(pa_bluetooth_discovery *y, const char *path) {
+    pa_bluetooth_device *device;
+    pa_hashmap *endpoints;
+    void *devices_state;
+    void *state;
+
+    PA_HASHMAP_FOREACH(device, y->devices, devices_state) {
+        PA_HASHMAP_FOREACH(endpoints, device->a2dp_sink_endpoints, state)
+            pa_hashmap_remove_and_free(endpoints, path);
+
+        PA_HASHMAP_FOREACH(endpoints, device->a2dp_source_endpoints, state)
+            pa_hashmap_remove_and_free(endpoints, path);
+    }
+
+    pa_log_debug("Remote endpoint %s was removed", path);
+}
+
 static pa_bluetooth_device* device_create(pa_bluetooth_discovery *y, const char *path) {
     pa_bluetooth_device *d;
 
@@ -520,6 +717,8 @@ static pa_bluetooth_device* device_create(pa_bluetooth_discovery *y, const char
     d->discovery = y;
     d->path = pa_xstrdup(path);
     d->uuids = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, pa_xfree);
+    d->a2dp_sink_endpoints = pa_hashmap_new_full(pa_a2dp_codec_id_hash_func, pa_a2dp_codec_id_compare_func, pa_xfree, (pa_free_cb_t)pa_hashmap_free);
+    d->a2dp_source_endpoints = pa_hashmap_new_full(pa_a2dp_codec_id_hash_func, pa_a2dp_codec_id_compare_func, pa_xfree, (pa_free_cb_t)pa_hashmap_free);
 
     pa_hashmap_put(y->devices, d->path, d);
 
@@ -575,6 +774,10 @@ static void device_free(pa_bluetooth_device *d) {
 
     if (d->uuids)
         pa_hashmap_free(d->uuids);
+    if (d->a2dp_sink_endpoints)
+        pa_hashmap_free(d->a2dp_sink_endpoints);
+    if (d->a2dp_source_endpoints)
+        pa_hashmap_free(d->a2dp_source_endpoints);
 
     pa_xfree(d->path);
     pa_xfree(d->alias);
@@ -857,7 +1060,7 @@ static void parse_adapter_properties(pa_bluetooth_adapter *a, DBusMessageIter *i
     }
 }
 
-static void register_endpoint_reply(DBusPendingCall *pending, void *userdata) {
+static void register_legacy_sbc_endpoint_reply(DBusPendingCall *pending, void *userdata) {
     DBusMessage *r;
     pa_dbus_pending *p;
     pa_bluetooth_discovery *y;
@@ -889,7 +1092,7 @@ finish:
     pa_xfree(endpoint);
 }
 
-static void register_endpoint(pa_bluetooth_discovery *y, const pa_a2dp_codec *a2dp_codec, const char *path, const char *endpoint, const char *uuid) {
+static void register_legacy_sbc_endpoint(pa_bluetooth_discovery *y, const pa_a2dp_codec *a2dp_codec, const char *path, const char *endpoint, const char *uuid) {
     DBusMessage *m;
     DBusMessageIter i, d;
     uint8_t capabilities[MAX_A2DP_CAPS_SIZE];
@@ -914,7 +1117,242 @@ static void register_endpoint(pa_bluetooth_discovery *y, const pa_a2dp_codec *a2
 
     dbus_message_iter_close_container(&i, &d);
 
-    send_and_add_to_pending(y, m, register_endpoint_reply, pa_xstrdup(endpoint));
+    send_and_add_to_pending(y, m, register_legacy_sbc_endpoint_reply, pa_xstrdup(endpoint));
+}
+
+static void register_application_reply(DBusPendingCall *pending, void *userdata) {
+    DBusMessage *r;
+    pa_dbus_pending *p;
+    pa_bluetooth_adapter *a;
+    pa_bluetooth_discovery *y;
+    char *path;
+    bool fallback = true;
+
+    pa_assert(pending);
+    pa_assert_se(p = userdata);
+    pa_assert_se(y = p->context_data);
+    pa_assert_se(path = p->call_data);
+    pa_assert_se(r = dbus_pending_call_steal_reply(pending));
+
+    if (dbus_message_is_error(r, BLUEZ_ERROR_NOT_SUPPORTED)) {
+        pa_log_info("Couldn't register media application for adapter %s because it is disabled in BlueZ", path);
+        goto finish;
+    }
+
+    if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
+        pa_log_warn(BLUEZ_MEDIA_INTERFACE ".RegisterApplication() failed: %s: %s",
+                dbus_message_get_error_name(r), pa_dbus_get_error_message(r));
+        pa_log_warn("Couldn't register media application for adapter %s", path);
+        goto finish;
+    }
+
+    a = pa_hashmap_get(y->adapters, path);
+    if (!a) {
+        pa_log_error("Couldn't register media application for adapter %s because it does not exist anymore", path);
+        goto finish;
+    }
+
+    fallback = false;
+    a->application_registered = true;
+    pa_log_debug("Media application for adapter %s was successfully registered", path);
+
+finish:
+    dbus_message_unref(r);
+
+    PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
+    pa_dbus_pending_free(p);
+
+    if (fallback) {
+        /* If bluez does not support RegisterApplication, fallback to old legacy API with just one SBC codec */
+        const pa_a2dp_codec *a2dp_codec_sbc;
+        a2dp_codec_sbc = pa_bluetooth_get_a2dp_codec("sbc");
+        pa_assert(a2dp_codec_sbc);
+        register_legacy_sbc_endpoint(y, a2dp_codec_sbc, path, A2DP_SINK_ENDPOINT "/sbc",
+                PA_BLUETOOTH_UUID_A2DP_SINK);
+        register_legacy_sbc_endpoint(y, a2dp_codec_sbc, path, A2DP_SOURCE_ENDPOINT "/sbc",
+                PA_BLUETOOTH_UUID_A2DP_SOURCE);
+        pa_log_warn("Only SBC codec is available for A2DP profiles");
+        a->application_registered = false;
+    }
+
+    pa_xfree(path);
+}
+
+static void register_application(pa_bluetooth_adapter *a) {
+    DBusMessage *m;
+    DBusMessageIter i, d;
+    const char *object_manager_path = A2DP_OBJECT_MANAGER_PATH;
+
+    if (a->application_registered) {
+        pa_log_info("Media application is already registered for adapter %s", a->path);
+        return;
+    }
+
+    pa_log_debug("Registering media application for adapter %s", a->path);
+
+    pa_assert_se(m = dbus_message_new_method_call(BLUEZ_SERVICE, a->path,
+                BLUEZ_MEDIA_INTERFACE, "RegisterApplication"));
+
+    dbus_message_iter_init_append(m, &i);
+    pa_assert_se(dbus_message_iter_append_basic(&i, DBUS_TYPE_OBJECT_PATH, &object_manager_path));
+    dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY,
+            DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+            DBUS_TYPE_STRING_AS_STRING
+            DBUS_TYPE_VARIANT_AS_STRING
+            DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
+            &d);
+    dbus_message_iter_close_container(&i, &d);
+
+    send_and_add_to_pending(a->discovery, m, register_application_reply, pa_xstrdup(a->path));
+}
+
+static void parse_remote_endpoint_properties(pa_bluetooth_discovery *y, const char *endpoint, DBusMessageIter *i) {
+    DBusMessageIter element_i;
+    pa_bluetooth_device *device;
+    pa_hashmap *codec_endpoints;
+    pa_hashmap *endpoints;
+    pa_a2dp_codec_id *a2dp_codec_id;
+    pa_a2dp_codec_capabilities *a2dp_codec_capabilities;
+    const char *uuid = NULL;
+    const char *device_path = NULL;
+    uint8_t codec_id = 0;
+    bool have_codec_id = false;
+    const uint8_t *capabilities = NULL;
+    int capabilities_size = 0;
+
+    pa_log_debug("Parsing remote endpoint %s", endpoint);
+
+    dbus_message_iter_recurse(i, &element_i);
+
+    while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
+        DBusMessageIter dict_i, variant_i;
+        const char *key;
+
+        dbus_message_iter_recurse(&element_i, &dict_i);
+
+        key = check_variant_property(&dict_i);
+        if (key == NULL) {
+            pa_log_error("Received invalid property for remote endpoint %s", endpoint);
+            return;
+        }
+
+        dbus_message_iter_recurse(&dict_i, &variant_i);
+
+        if (pa_streq(key, "UUID")) {
+            if (dbus_message_iter_get_arg_type(&variant_i) != DBUS_TYPE_STRING) {
+                pa_log_warn("Remote endpoint %s property 'UUID' is not string, ignoring", endpoint);
+                return;
+            }
+
+            dbus_message_iter_get_basic(&variant_i, &uuid);
+        } else if (pa_streq(key, "Codec")) {
+            if (dbus_message_iter_get_arg_type(&variant_i) != DBUS_TYPE_BYTE) {
+                pa_log_warn("Remote endpoint %s property 'Codec' is not byte, ignoring", endpoint);
+                return;
+            }
+
+            dbus_message_iter_get_basic(&variant_i, &codec_id);
+            have_codec_id = true;
+        } else if (pa_streq(key, "Capabilities")) {
+            DBusMessageIter array;
+
+            if (dbus_message_iter_get_arg_type(&variant_i) != DBUS_TYPE_ARRAY) {
+                pa_log_warn("Remote endpoint %s property 'Capabilities' is not array, ignoring", endpoint);
+                return;
+            }
+
+            dbus_message_iter_recurse(&variant_i, &array);
+            if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_BYTE) {
+                pa_log_warn("Remote endpoint %s property 'Capabilities' is not array of bytes, ignoring", endpoint);
+                return;
+            }
+
+            dbus_message_iter_get_fixed_array(&array, &capabilities, &capabilities_size);
+        } else if (pa_streq(key, "Device")) {
+            if (dbus_message_iter_get_arg_type(&variant_i) != DBUS_TYPE_OBJECT_PATH) {
+                pa_log_warn("Remote endpoint %s property 'Device' is not path, ignoring", endpoint);
+                return;
+            }
+
+            dbus_message_iter_get_basic(&variant_i, &device_path);
+        }
+
+        dbus_message_iter_next(&element_i);
+    }
+
+    if (!uuid) {
+        pa_log_warn("Remote endpoint %s does not have property 'UUID', ignoring", endpoint);
+        return;
+    }
+
+    if (!have_codec_id) {
+        pa_log_warn("Remote endpoint %s does not have property 'Codec', ignoring", endpoint);
+        return;
+    }
+
+    if (!capabilities || !capabilities_size) {
+        pa_log_warn("Remote endpoint %s does not have property 'Capabilities', ignoring", endpoint);
+        return;
+    }
+
+    if (!device_path) {
+        pa_log_warn("Remote endpoint %s does not have property 'Device', ignoring", endpoint);
+        return;
+    }
+
+    device = pa_hashmap_get(y->devices, device_path);
+    if (!device) {
+        pa_log_warn("Device for remote endpoint %s was not found", endpoint);
+        return;
+    }
+
+    if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SINK)) {
+        codec_endpoints = device->a2dp_sink_endpoints;
+    } else if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SOURCE)) {
+        codec_endpoints = device->a2dp_source_endpoints;
+    } else {
+        pa_log_warn("Remote endpoint %s does not have valid property 'UUID', ignoring", endpoint);
+        return;
+    }
+
+    if (capabilities_size < 0 || capabilities_size > MAX_A2DP_CAPS_SIZE) {
+        pa_log_warn("Remote endpoint %s does not have valid property 'Capabilities', ignoring", endpoint);
+        return;
+    }
+
+    a2dp_codec_id = pa_xmalloc0(sizeof(*a2dp_codec_id));
+    a2dp_codec_id->codec_id = codec_id;
+    if (codec_id == A2DP_CODEC_VENDOR) {
+        if ((size_t)capabilities_size < sizeof(a2dp_vendor_codec_t)) {
+            pa_log_warn("Remote endpoint %s does not have valid property 'Capabilities', ignoring", endpoint);
+            pa_xfree(a2dp_codec_id);
+            return;
+        }
+        a2dp_codec_id->vendor_id = A2DP_GET_VENDOR_ID(*(a2dp_vendor_codec_t *)capabilities);
+        a2dp_codec_id->vendor_codec_id = A2DP_GET_CODEC_ID(*(a2dp_vendor_codec_t *)capabilities);
+    } else {
+        a2dp_codec_id->vendor_id = 0;
+        a2dp_codec_id->vendor_codec_id = 0;
+    }
+
+    if (!pa_bluetooth_a2dp_codec_is_codec_available(a2dp_codec_id, pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SINK))) {
+        pa_xfree(a2dp_codec_id);
+        return;
+    }
+
+    a2dp_codec_capabilities = pa_xmalloc0(sizeof(*a2dp_codec_capabilities) + capabilities_size);
+    a2dp_codec_capabilities->size = capabilities_size;
+    memcpy(a2dp_codec_capabilities->buffer, capabilities, capabilities_size);
+
+    endpoints = pa_hashmap_get(codec_endpoints, a2dp_codec_id);
+    if (!endpoints) {
+        endpoints = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, pa_xfree, pa_xfree);
+        pa_hashmap_put(codec_endpoints, a2dp_codec_id, endpoints);
+    }
+
+    if (pa_hashmap_remove_and_free(endpoints, endpoint) >= 0)
+        pa_log_debug("Replacing existing remote endpoint %s", endpoint);
+    pa_hashmap_put(endpoints, pa_xstrdup(endpoint), a2dp_codec_capabilities);
 }
 
 static void parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessageIter *dict_i) {
@@ -944,8 +1382,6 @@ static void parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessa
         pa_assert(dbus_message_iter_get_arg_type(&iface_i) == DBUS_TYPE_ARRAY);
 
         if (pa_streq(interface, BLUEZ_ADAPTER_INTERFACE)) {
-
-            const pa_a2dp_codec *a2dp_codec_sbc;
             pa_bluetooth_adapter *a;
 
             if ((a = pa_hashmap_get(y->adapters, path))) {
@@ -961,14 +1397,7 @@ static void parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessa
             if (!a->valid)
                 return;
 
-            /* Currently only one A2DP codec is supported, so register only SBC
-             * Support for multiple codecs needs to use a new Bluez API which
-             * pulseaudio does not implement yet, patches are waiting in queue */
-            a2dp_codec_sbc = pa_bluetooth_get_a2dp_codec("sbc");
-            pa_assert(a2dp_codec_sbc);
-            register_endpoint(y, a2dp_codec_sbc, path, A2DP_SINK_ENDPOINT "/sbc", PA_BLUETOOTH_UUID_A2DP_SINK);
-            register_endpoint(y, a2dp_codec_sbc, path, A2DP_SOURCE_ENDPOINT "/sbc", PA_BLUETOOTH_UUID_A2DP_SOURCE);
-
+            register_application(a);
         } else if (pa_streq(interface, BLUEZ_DEVICE_INTERFACE)) {
 
             if ((d = pa_hashmap_get(y->devices, path))) {
@@ -982,7 +1411,8 @@ static void parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessa
             pa_log_debug("Device %s found", d->path);
 
             parse_device_properties(d, &iface_i);
-
+        } else if (pa_streq(interface, BLUEZ_MEDIA_ENDPOINT_INTERFACE)) {
+            parse_remote_endpoint_properties(y, path, &iface_i);
         } else
             pa_log_debug("Unknown interface %s found, skipping", interface);
 
@@ -1190,6 +1620,8 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
                 device_remove(y, p);
             else if (pa_streq(iface, BLUEZ_ADAPTER_INTERFACE))
                 adapter_remove(y, p);
+            else if (pa_streq(iface, BLUEZ_MEDIA_ENDPOINT_INTERFACE))
+                remote_endpoint_remove(y, p);
 
             dbus_message_iter_next(&element_i);
         }
@@ -1248,6 +1680,10 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 
             parse_transport_properties(t, &arg_i);
+        } else if (pa_streq(iface, BLUEZ_MEDIA_ENDPOINT_INTERFACE)) {
+            pa_log_info("Properties changed in remote endpoint %s", dbus_message_get_path(m));
+
+            parse_remote_endpoint_properties(y, dbus_message_get_path(m), &arg_i);
         }
 
         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
@@ -1419,6 +1855,7 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
     pa_bluetooth_transport_put(t);
 
     pa_log_debug("Transport %s available for profile %s", t->path, pa_bluetooth_profile_to_string(t->profile));
+    pa_log_info("Selected codec: %s", a2dp_codec->name);
 
     return NULL;
 
@@ -1582,6 +2019,141 @@ static void endpoint_done(pa_bluetooth_discovery *y, const char *endpoint) {
     dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), endpoint);
 }
 
+static void append_a2dp_object(DBusMessageIter *iter, const char *endpoint, const char *uuid, uint8_t codec_id, uint8_t *capabilities, uint8_t capabilities_size) {
+    const char *interface_name = BLUEZ_MEDIA_ENDPOINT_INTERFACE;
+    DBusMessageIter object, array, entry, dict;
+
+    dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &object);
+    pa_assert_se(dbus_message_iter_append_basic(&object, DBUS_TYPE_OBJECT_PATH, &endpoint));
+
+    dbus_message_iter_open_container(&object, DBUS_TYPE_ARRAY,
+                                     DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+                                     DBUS_TYPE_STRING_AS_STRING
+                                     DBUS_TYPE_ARRAY_AS_STRING
+                                     DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+                                     DBUS_TYPE_STRING_AS_STRING
+                                     DBUS_TYPE_VARIANT_AS_STRING
+                                     DBUS_DICT_ENTRY_END_CHAR_AS_STRING
+                                     DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
+                                     &array);
+
+    dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, NULL, &entry);
+    pa_assert_se(dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &interface_name));
+
+    dbus_message_iter_open_container(&entry, DBUS_TYPE_ARRAY,
+                                     DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+                                     DBUS_TYPE_STRING_AS_STRING
+                                     DBUS_TYPE_VARIANT_AS_STRING
+                                     DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
+                                     &dict);
+
+    pa_dbus_append_basic_variant_dict_entry(&dict, "UUID", DBUS_TYPE_STRING, &uuid);
+    pa_dbus_append_basic_variant_dict_entry(&dict, "Codec", DBUS_TYPE_BYTE, &codec_id);
+    pa_dbus_append_basic_array_variant_dict_entry(&dict, "Capabilities", DBUS_TYPE_BYTE,
+            capabilities, capabilities_size);
+
+    dbus_message_iter_close_container(&entry, &dict);
+    dbus_message_iter_close_container(&array, &entry);
+    dbus_message_iter_close_container(&object, &array);
+    dbus_message_iter_close_container(iter, &object);
+}
+
+static DBusHandlerResult object_manager_handler(DBusConnection *c, DBusMessage *m, void *userdata) {
+    struct pa_bluetooth_discovery *y = userdata;
+    DBusMessage *r;
+    const char *path, *interface, *member;
+
+    pa_assert(y);
+
+    path = dbus_message_get_path(m);
+    interface = dbus_message_get_interface(m);
+    member = dbus_message_get_member(m);
+
+    pa_log_debug("dbus: path=%s, interface=%s, member=%s", path, interface, member);
+
+    if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
+        const char *xml = OBJECT_MANAGER_INTROSPECT_XML;
+
+        pa_assert_se(r = dbus_message_new_method_return(m));
+        pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID));
+    } else if (dbus_message_is_method_call(m, "org.freedesktop.DBus.ObjectManager", "GetManagedObjects")) {
+        DBusMessageIter iter, array;
+        int i;
+
+        pa_assert_se(r = dbus_message_new_method_return(m));
+
+        dbus_message_iter_init_append(r, &iter);
+        dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+                                         DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+                                         DBUS_TYPE_OBJECT_PATH_AS_STRING
+                                         DBUS_TYPE_ARRAY_AS_STRING
+                                         DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+                                         DBUS_TYPE_STRING_AS_STRING
+                                         DBUS_TYPE_ARRAY_AS_STRING
+                                         DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+                                         DBUS_TYPE_STRING_AS_STRING
+                                         DBUS_TYPE_VARIANT_AS_STRING
+                                         DBUS_DICT_ENTRY_END_CHAR_AS_STRING
+                                         DBUS_DICT_ENTRY_END_CHAR_AS_STRING
+                                         DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
+                                         &array);
+
+        for (i = 0; i < pa_bluetooth_a2dp_codec_count(); i++) {
+            const pa_a2dp_codec *a2dp_codec;
+            uint8_t capabilities[MAX_A2DP_CAPS_SIZE];
+            uint8_t capabilities_size;
+            uint8_t codec_id;
+            char *endpoint;
+
+            a2dp_codec = pa_bluetooth_a2dp_codec_iter(i);
+            if (!a2dp_codec->can_be_supported())
+                continue;
+
+            codec_id = a2dp_codec->id.codec_id;
+            capabilities_size = a2dp_codec->fill_capabilities(capabilities);
+            pa_assert(capabilities_size != 0);
+
+            if (a2dp_codec->decode_buffer != NULL) {
+                endpoint = pa_sprintf_malloc("%s/%s", A2DP_SINK_ENDPOINT, a2dp_codec->name);
+                append_a2dp_object(&array, endpoint, PA_BLUETOOTH_UUID_A2DP_SINK, codec_id,
+                        capabilities, capabilities_size);
+                pa_xfree(endpoint);
+            }
+
+            if (a2dp_codec->encode_buffer != NULL) {
+                endpoint = pa_sprintf_malloc("%s/%s", A2DP_SOURCE_ENDPOINT, a2dp_codec->name);
+                append_a2dp_object(&array, endpoint, PA_BLUETOOTH_UUID_A2DP_SOURCE, codec_id,
+                        capabilities, capabilities_size);
+                pa_xfree(endpoint);
+            }
+        }
+
+        dbus_message_iter_close_container(&iter, &array);
+    } else
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+    pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), r, NULL));
+    dbus_message_unref(r);
+
+    return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static void object_manager_init(pa_bluetooth_discovery *y) {
+    static const DBusObjectPathVTable vtable = {
+        .message_function = object_manager_handler,
+    };
+
+    pa_assert(y);
+    pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection),
+                A2DP_OBJECT_MANAGER_PATH, &vtable, y));
+}
+
+static void object_manager_done(pa_bluetooth_discovery *y) {
+    pa_assert(y);
+    dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection),
+            A2DP_OBJECT_MANAGER_PATH);
+}
+
 pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c, int headset_backend) {
     pa_bluetooth_discovery *y;
     DBusError err;
@@ -1590,6 +2162,7 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c, int headset_backe
     const pa_a2dp_codec *a2dp_codec;
     char *endpoint;
 
+    pa_bluetooth_a2dp_codec_gst_init();
     y = pa_xnew0(pa_bluetooth_discovery, 1);
     PA_REFCNT_INIT(y);
     y->core = c;
@@ -1633,6 +2206,8 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c, int headset_backe
             "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
             ",arg0='" BLUEZ_DEVICE_INTERFACE "'",
             "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
+            ",arg0='" BLUEZ_MEDIA_ENDPOINT_INTERFACE "'",
+            "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
             ",arg0='" BLUEZ_MEDIA_TRANSPORT_INTERFACE "'",
             NULL) < 0) {
         pa_log_error("Failed to add D-Bus matches: %s", err.message);
@@ -1640,17 +2215,25 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c, int headset_backe
     }
     y->matches_added = true;
 
+    object_manager_init(y);
+
     count = pa_bluetooth_a2dp_codec_count();
     for (i = 0; i < count; i++) {
         a2dp_codec = pa_bluetooth_a2dp_codec_iter(i);
+        if (!a2dp_codec->can_be_supported())
+            continue;
 
-        endpoint = pa_sprintf_malloc("%s/%s", A2DP_SINK_ENDPOINT, a2dp_codec->name);
-        endpoint_init(y, endpoint);
-        pa_xfree(endpoint);
+        if (a2dp_codec->decode_buffer != NULL) {
+            endpoint = pa_sprintf_malloc("%s/%s", A2DP_SINK_ENDPOINT, a2dp_codec->name);
+            endpoint_init(y, endpoint);
+            pa_xfree(endpoint);
+        }
 
-        endpoint = pa_sprintf_malloc("%s/%s", A2DP_SOURCE_ENDPOINT, a2dp_codec->name);
-        endpoint_init(y, endpoint);
-        pa_xfree(endpoint);
+        if (a2dp_codec->encode_buffer != NULL) {
+            endpoint = pa_sprintf_malloc("%s/%s", A2DP_SOURCE_ENDPOINT, a2dp_codec->name);
+            endpoint_init(y, endpoint);
+            pa_xfree(endpoint);
+        }
     }
 
     get_managed_objects(y);
@@ -1717,23 +2300,34 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
                 "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',"
                 "member='PropertiesChanged',arg0='" BLUEZ_DEVICE_INTERFACE "'",
                 "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',"
+                "member='PropertiesChanged',arg0='" BLUEZ_MEDIA_ENDPOINT_INTERFACE "'",
+                "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',"
                 "member='PropertiesChanged',arg0='" BLUEZ_MEDIA_TRANSPORT_INTERFACE "'",
                 NULL);
 
         if (y->filter_added)
             dbus_connection_remove_filter(pa_dbus_connection_get(y->connection), filter_cb, y);
 
+        object_manager_done(y);
+
         count = pa_bluetooth_a2dp_codec_count();
         for (i = 0; i < count; i++) {
             a2dp_codec = pa_bluetooth_a2dp_codec_iter(i);
 
-            endpoint = pa_sprintf_malloc("%s/%s", A2DP_SINK_ENDPOINT, a2dp_codec->name);
-            endpoint_done(y, endpoint);
-            pa_xfree(endpoint);
+            if (!a2dp_codec->can_be_supported())
+                continue;
 
-            endpoint = pa_sprintf_malloc("%s/%s", A2DP_SOURCE_ENDPOINT, a2dp_codec->name);
-            endpoint_done(y, endpoint);
-            pa_xfree(endpoint);
+            if (a2dp_codec->decode_buffer != NULL) {
+                endpoint = pa_sprintf_malloc("%s/%s", A2DP_SINK_ENDPOINT, a2dp_codec->name);
+                endpoint_done(y, endpoint);
+                pa_xfree(endpoint);
+            }
+
+            if (a2dp_codec->encode_buffer != NULL) {
+                endpoint = pa_sprintf_malloc("%s/%s", A2DP_SOURCE_ENDPOINT, a2dp_codec->name);
+                endpoint_done(y, endpoint);
+                pa_xfree(endpoint);
+            }
         }
 
         pa_dbus_connection_unref(y->connection);


=====================================
src/modules/bluetooth/bluez5-util.h
=====================================
@@ -109,6 +109,7 @@ struct pa_bluetooth_device {
     bool tried_to_link_with_adapter;
     bool valid;
     bool autodetect_mtu;
+    bool codec_switching_in_progress;
 
     /* Device information */
     char *path;
@@ -117,6 +118,9 @@ struct pa_bluetooth_device {
     char *address;
     uint32_t class_of_device;
     pa_hashmap *uuids; /* char* -> char* (hashmap-as-a-set) */
+    /* pa_a2dp_codec_id* -> pa_hashmap ( char* (remote endpoint) -> struct a2dp_codec_capabilities* ) */
+    pa_hashmap *a2dp_sink_endpoints;
+    pa_hashmap *a2dp_source_endpoints;
 
     pa_bluetooth_transport *transports[PA_BLUETOOTH_PROFILE_COUNT];
 
@@ -129,6 +133,7 @@ struct pa_bluetooth_adapter {
     char *address;
 
     bool valid;
+    bool application_registered;
 };
 
 #ifdef HAVE_BLUEZ_5_OFONO_HEADSET
@@ -169,6 +174,7 @@ pa_bluetooth_device* pa_bluetooth_discovery_get_device_by_address(pa_bluetooth_d
 pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y, pa_bluetooth_hook_t hook);
 
 const char *pa_bluetooth_profile_to_string(pa_bluetooth_profile_t profile);
+bool pa_bluetooth_switch_codec(pa_bluetooth_device *device, pa_bluetooth_profile_t profile, pa_hashmap *capabilities_hashmap, const pa_a2dp_codec *a2dp_codec, void (*codec_switch_cb)(bool, pa_bluetooth_profile_t profile, void *), void *userdata);
 
 static inline bool pa_bluetooth_uuid_is_hsp_hs(const char *uuid) {
     return pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_HS) || pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_HS_ALT);


=====================================
src/modules/bluetooth/meson.build
=====================================
@@ -20,13 +20,20 @@ if get_option('bluez5-ofono-headset')
   libbluez5_util_sources += [ 'backend-ofono.c' ]
 endif
 
+if have_bluez5_gstreamer
+  libbluez5_util_headers += [ 'a2dp-codec-gst.h' ]
+  libbluez5_util_sources += [ 'a2dp-codec-gst.c' ]
+  libbluez5_util_sources += [ 'a2dp-codec-ldac-gst.c' ]
+  libbluez5_util_sources += [ 'a2dp-codec-aptx-gst.c' ]
+endif
+
 libbluez5_util = shared_library('bluez5-util',
   libbluez5_util_sources,
   libbluez5_util_headers,
   c_args : [pa_c_args, server_c_args],
   link_args : [nodelete_link_args],
   include_directories : [configinc, topinc],
-  dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep, dbus_dep, sbc_dep, libintl_dep],
+  dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep, dbus_dep, sbc_dep, libintl_dep, bluez5_gst_dep, bluez5_gstapp_dep],
   install : true,
   install_rpath : privlibdir,
   install_dir : modlibexecdir,


=====================================
src/modules/bluetooth/module-bluez5-device.c
=====================================
@@ -31,11 +31,13 @@
 #include <pulse/timeval.h>
 #include <pulse/utf8.h>
 #include <pulse/util.h>
+#include <pulse/message-params.h>
 
 #include <pulsecore/core-error.h>
 #include <pulsecore/core-rtclock.h>
 #include <pulsecore/core-util.h>
 #include <pulsecore/i18n.h>
+#include <pulsecore/message-handler.h>
 #include <pulsecore/module.h>
 #include <pulsecore/modargs.h>
 #include <pulsecore/poll.h>
@@ -998,6 +1000,8 @@ static int add_source(struct userdata *u) {
     data.name = pa_sprintf_malloc("bluez_source.%s.%s", u->device->address, pa_bluetooth_profile_to_string(u->profile));
     data.namereg_fail = false;
     pa_proplist_sets(data.proplist, "bluetooth.protocol", pa_bluetooth_profile_to_string(u->profile));
+    if (u->a2dp_codec)
+        pa_proplist_sets(data.proplist, PA_PROP_BLUETOOTH_CODEC, u->a2dp_codec->name);
     pa_source_new_data_set_sample_spec(&data, &u->decoder_sample_spec);
     if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT)
         pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
@@ -1182,6 +1186,8 @@ static int add_sink(struct userdata *u) {
     data.name = pa_sprintf_malloc("bluez_sink.%s.%s", u->device->address, pa_bluetooth_profile_to_string(u->profile));
     data.namereg_fail = false;
     pa_proplist_sets(data.proplist, "bluetooth.protocol", pa_bluetooth_profile_to_string(u->profile));
+    if (u->a2dp_codec)
+        pa_proplist_sets(data.proplist, PA_PROP_BLUETOOTH_CODEC, u->a2dp_codec->name);
     pa_sink_new_data_set_sample_spec(&data, &u->encoder_sample_spec);
     if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT)
         pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
@@ -1250,7 +1256,7 @@ static int transport_config(struct userdata *u) {
         u->a2dp_codec = u->transport->a2dp_codec;
         pa_assert(u->a2dp_codec);
 
-        info = u->a2dp_codec->init(is_a2dp_sink, false, u->transport->config, u->transport->config_size, is_a2dp_sink ? &u->encoder_sample_spec : &u->decoder_sample_spec);
+        info = u->a2dp_codec->init(is_a2dp_sink, false, u->transport->config, u->transport->config_size, is_a2dp_sink ? &u->encoder_sample_spec : &u->decoder_sample_spec, u->core);
         if (is_a2dp_sink)
             u->encoder_info = info;
         else
@@ -1312,7 +1318,10 @@ static int init_profile(struct userdata *u) {
     pa_assert(u);
     pa_assert(u->profile != PA_BLUETOOTH_PROFILE_OFF);
 
-    if (setup_transport(u) < 0)
+    r = setup_transport(u);
+    if (r == -EINPROGRESS)
+        return 0;
+    else if (r < 0)
         return -1;
 
     pa_assert(u->transport);
@@ -1638,6 +1647,10 @@ static int start_thread(struct userdata *u) {
             u->source->set_volume(u->source);
     }
 
+    if (u->sink || u->source)
+        if (u->a2dp_codec)
+            pa_proplist_sets(u->card->proplist, PA_PROP_BLUETOOTH_CODEC, u->a2dp_codec->name);
+
     return 0;
 }
 
@@ -1645,6 +1658,9 @@ static int start_thread(struct userdata *u) {
 static void stop_thread(struct userdata *u) {
     pa_assert(u);
 
+    if (u->sink || u->source)
+        pa_proplist_unset(u->card->proplist, PA_PROP_BLUETOOTH_CODEC);
+
     if (u->sink)
         pa_sink_unlink(u->sink);
 
@@ -2079,7 +2095,12 @@ static void handle_transport_state_change(struct userdata *u, struct pa_bluetoot
     pa_assert_se(cp = pa_hashmap_get(u->card->profiles, pa_bluetooth_profile_to_string(t->profile)));
 
     oldavail = cp->available;
-    pa_card_profile_set_available(cp, transport_state_to_availability(t->state));
+    /*
+     * If codec switching is in progress, transport state change should not
+     * make profile unavailable.
+     */
+    if (!t->device->codec_switching_in_progress)
+        pa_card_profile_set_available(cp, transport_state_to_availability(t->state));
 
     /* Update port availability */
     pa_assert_se(port = pa_hashmap_get(u->card->ports, u->output_port_name));
@@ -2149,7 +2170,7 @@ static pa_hook_result_t device_connection_changed_cb(pa_bluetooth_discovery *y,
     pa_assert(d);
     pa_assert(u);
 
-    if (d != u->device || pa_bluetooth_device_any_transport_connected(d))
+    if (d != u->device || pa_bluetooth_device_any_transport_connected(d) || d->codec_switching_in_progress)
         return PA_HOOK_OK;
 
     pa_log_debug("Unloading module for device %s", d->path);
@@ -2227,6 +2248,206 @@ static pa_hook_result_t transport_microphone_gain_changed_cb(pa_bluetooth_discov
     return PA_HOOK_OK;
 }
 
+static char* make_message_handler_path(const char *name) {
+    return pa_sprintf_malloc("/card/%s/bluez", name);
+}
+
+static void switch_codec_cb_handler(bool success, pa_bluetooth_profile_t profile, void *userdata)
+{
+    struct userdata *u = (struct userdata *) userdata;
+
+    if (!success)
+        goto off;
+
+    u->profile = profile;
+
+    if (init_profile(u) < 0) {
+        pa_log_info("Failed to initialise profile after codec switching");
+        goto off;
+    }
+
+    if (u->sink || u->source)
+        if (start_thread(u) < 0) {
+            pa_log_info("Failed to start thread after codec switching");
+            goto off;
+        }
+
+    pa_log_info("Codec successfully switched to %s with profile: %s",
+            u->a2dp_codec->name, pa_bluetooth_profile_to_string(u->profile));
+
+    return;
+
+off:
+    pa_assert_se(pa_card_set_profile(u->card, pa_hashmap_get(u->card->profiles, "off"), false) >= 0);
+}
+
+static char *list_codecs(struct userdata *u) {
+    const pa_a2dp_codec_capabilities *a2dp_capabilities;
+    const pa_a2dp_codec_id *key;
+    pa_hashmap *a2dp_endpoints;
+    pa_message_params *param;
+    unsigned int i;
+    bool is_a2dp_sink;
+    void *state;
+
+    is_a2dp_sink = u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK;
+
+    a2dp_endpoints = is_a2dp_sink ? u->device->a2dp_sink_endpoints : u->device->a2dp_source_endpoints;
+
+    param = pa_message_params_new();
+
+    pa_message_params_begin_list(param);
+
+    PA_HASHMAP_FOREACH_KV(key, a2dp_capabilities, a2dp_endpoints, state) {
+        for (i = 0; i < pa_bluetooth_a2dp_codec_count(); i++) {
+            const pa_a2dp_codec *a2dp_codec;
+
+            a2dp_codec = pa_bluetooth_a2dp_codec_iter(i);
+
+            if (memcmp(key, &a2dp_codec->id, sizeof(pa_a2dp_codec_id)) == 0) {
+                if (a2dp_codec->can_be_supported()) {
+                    pa_message_params_begin_list(param);
+
+                    pa_message_params_write_string(param, a2dp_codec->name);
+                    pa_message_params_write_string(param, a2dp_codec->description);
+
+                    pa_message_params_end_list(param);
+                }
+            }
+        }
+    }
+
+    pa_message_params_end_list(param);
+
+    return pa_message_params_to_string_free(param);
+}
+
+static int bluez5_device_message_handler(const char *object_path, const char *message, char *message_parameters, char **response, void *userdata) {
+    char *message_handler_path;
+    pa_hashmap *capabilities_hashmap;
+    pa_bluetooth_profile_t profile;
+    const pa_a2dp_codec *codec;
+    const char *codec_name;
+    struct userdata *u;
+    bool is_a2dp_sink;
+    void *state = NULL;
+    int err;
+
+    pa_assert(u = (struct userdata *)userdata);
+    pa_assert(message);
+    pa_assert(response);
+
+    message_handler_path = make_message_handler_path(u->card->name);
+
+    if (!object_path || !pa_streq(object_path, message_handler_path)) {
+        pa_xfree(message_handler_path);
+        return -PA_ERR_NOENTITY;
+    }
+
+    pa_xfree(message_handler_path);
+
+    if (u->device->codec_switching_in_progress) {
+        pa_log_info("Codec switching operation already in progress");
+        return -PA_ERR_INVALID;
+    }
+
+    if (!u->device->adapter->application_registered) {
+        pa_log_info("Old BlueZ version was detected, only SBC codec supported.");
+        return -PA_ERR_NOTIMPLEMENTED;
+    }
+
+    if (u->profile == PA_BLUETOOTH_PROFILE_OFF) {
+        pa_log_info("Bluetooth profile is off. Message cannot be handled.");
+        return -PA_ERR_INVALID;
+    } else if (u->profile != PA_BLUETOOTH_PROFILE_A2DP_SINK &&
+            u->profile != PA_BLUETOOTH_PROFILE_A2DP_SOURCE) {
+        pa_log_info("Listing or switching codecs only allowed for A2DP sink or source");
+        return -PA_ERR_INVALID;
+    }
+
+    if (pa_streq(message, "switch-codec")) {
+        err = pa_message_params_read_string(message_parameters, &codec_name, &state);
+        if (err < 0)
+            return err;
+
+        if (u->a2dp_codec && pa_streq(codec_name, u->a2dp_codec->name)) {
+            pa_log_info("Requested codec is currently selected codec");
+            return -PA_ERR_INVALID;
+        }
+
+        codec = pa_bluetooth_get_a2dp_codec(codec_name);
+        if (codec == NULL) {
+            pa_log_info("Invalid codec %s specified for switching", codec_name);
+            return -PA_ERR_INVALID;
+        }
+
+        if (!codec->can_be_supported()) {
+            pa_log_info("Codec not found on system");
+            return -PA_ERR_NOTSUPPORTED;
+        }
+
+        is_a2dp_sink = u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK;
+
+        /*
+         * We need to check if we have valid sink or source endpoints which
+         * were registered during the negotiation process. If we do, then we
+         * check if the specified codec is present among the codecs supported
+         * by the remote endpoint.
+         */
+        if (pa_hashmap_isempty(is_a2dp_sink ? u->device->a2dp_sink_endpoints : u->device->a2dp_source_endpoints)) {
+            pa_log_info("No device endpoints found. Codec switching not allowed.");
+            return -PA_ERR_INVALID;
+        }
+
+        capabilities_hashmap = pa_hashmap_get(is_a2dp_sink ? u->device->a2dp_sink_endpoints : u->device->a2dp_source_endpoints, &codec->id);
+        if (!capabilities_hashmap) {
+            pa_log_info("No remote endpoint found for %s codec. Codec not supported by remote endpoint.",
+                    codec->name);
+            return -PA_ERR_INVALID;
+        }
+
+        pa_log_info("Initiating codec switching process to %s", codec->name);
+
+        /*
+         * The current profile needs to be saved before we stop the thread and
+         * initiate the switch. u->profile will be changed in other places
+         * depending on the state of transport and port availability.
+         */
+        profile = u->profile;
+
+        stop_thread(u);
+
+        if (!pa_bluetooth_switch_codec(u->device, profile, capabilities_hashmap, codec, switch_codec_cb_handler, userdata)
+                && !u->device->codec_switching_in_progress)
+            goto profile_off;
+
+        return PA_OK;
+    } else if (pa_streq(message, "list-codecs")) {
+        *response = list_codecs(u);
+        return PA_OK;
+    } else if (pa_streq(message, "get-codec")) {
+        pa_message_params *param;
+        param = pa_message_params_new();
+
+        if (u->a2dp_codec)
+            pa_message_params_write_string(param, u->a2dp_codec->name);
+        else
+            pa_message_params_write_string(param, "none");
+
+        *response = pa_message_params_to_string_free(param);
+
+        return PA_OK;
+    }
+
+
+    return -PA_ERR_NOTIMPLEMENTED;
+
+profile_off:
+    pa_assert_se(pa_card_set_profile(u->card, pa_hashmap_get(u->card->profiles, "off"), false) >= 0);
+
+    return -PA_ERR_IO;
+}
+
 /* Run from main thread context */
 static int device_process_msg(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) {
     struct bluetooth_msg *m = BLUETOOTH_MSG(obj);
@@ -2264,6 +2485,7 @@ int pa__init(pa_module* m) {
     const char *path;
     pa_modargs *ma;
     bool autodetect_mtu;
+    char *message_handler_path;
 
     pa_assert(m);
 
@@ -2335,6 +2557,12 @@ int pa__init(pa_module* m) {
         if (start_thread(u) < 0)
             goto off;
 
+    message_handler_path = make_message_handler_path(u->card->name);
+    pa_message_handler_register(m->core, message_handler_path, "Bluez5 device message handler",
+            bluez5_device_message_handler, (void *) u);
+    pa_log_info("Bluez5 device message handler registered at path: %s", message_handler_path);
+    pa_xfree(message_handler_path);
+
     return 0;
 
 off:
@@ -2357,6 +2585,7 @@ fail:
 }
 
 void pa__done(pa_module *m) {
+    char *message_handler_path;
     struct userdata *u;
 
     pa_assert(m);
@@ -2364,6 +2593,10 @@ void pa__done(pa_module *m) {
     if (!(u = m->userdata))
         return;
 
+    message_handler_path = make_message_handler_path(u->card->name);
+    pa_message_handler_unregister(m->core, message_handler_path);
+    pa_xfree(message_handler_path);
+
     stop_thread(u);
 
     if (u->device_connection_changed_slot)


=====================================
src/modules/bluetooth/module-bluez5-discover.c
=====================================
@@ -62,7 +62,9 @@ static pa_hook_result_t device_connection_changed_cb(pa_bluetooth_discovery *y,
 
     module_loaded = pa_hashmap_get(u->loaded_device_paths, d->path) ? true : false;
 
-    if (module_loaded && !pa_bluetooth_device_any_transport_connected(d)) {
+    /* When changing A2DP codec there is no transport connected, ensure that no module is unloaded */
+    if (module_loaded && !pa_bluetooth_device_any_transport_connected(d) &&
+            !d->codec_switching_in_progress) {
         /* disconnection, the module unloads itself */
         pa_log_debug("Unregistering module for %s", d->path);
         pa_hashmap_remove(u->loaded_device_paths, d->path);


=====================================
src/pulse/proplist.h
=====================================
@@ -270,6 +270,9 @@ PA_C_DECL_BEGIN
 /** For context: whether to forcefully disable data transfer via POSIX or memfd shared memory. This property overrides any other client configuration which would otherwise enable SHM communication channels. \since 15.0 */
 #define PA_PROP_CONTEXT_FORCE_DISABLE_SHM      "context.force.disable.shm"
 
+/** For a bluez device: the currently selected codec name. \since 15.0 */
+#define PA_PROP_BLUETOOTH_CODEC                "bluetooth.codec"
+
 /** A property list object. Basically a dictionary with ASCII strings
  * as keys and arbitrary data as values. \since 0.9.11 */
 typedef struct pa_proplist pa_proplist;



View it on GitLab: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/compare/f7c84b3217a5f21824d12705e71373c363c9df0f...cdbb73f9ade5fed84f818aeacfc75295b86b6896

-- 
View it on GitLab: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/compare/f7c84b3217a5f21824d12705e71373c363c9df0f...cdbb73f9ade5fed84f818aeacfc75295b86b6896
You're receiving this email because of your account on gitlab.freedesktop.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.freedesktop.org/archives/pulseaudio-commits/attachments/20210119/e941aed0/attachment-0001.htm>


More information about the pulseaudio-commits mailing list