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>
This commit is contained in:
Sanchayan Maity 2021-01-01 19:32:37 +05:30
parent 6044169763
commit d61493640e
8 changed files with 69 additions and 7 deletions

View file

@ -48,6 +48,9 @@ typedef struct pa_a2dp_codec {
/* True if codec is bi-directional and supports backchannel */ /* True if codec is bi-directional and supports backchannel */
bool support_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 /* Returns true if codec accepts capabilities, for_encoding is true when
* capabilities are used for encoding */ * capabilities are used for encoding */
bool (*can_accept_capabilities)(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding); bool (*can_accept_capabilities)(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding);

View file

@ -33,6 +33,28 @@
#include "a2dp-codec-gst.h" #include "a2dp-codec-gst.h"
#include "rtp.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) { 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) if (A2DP_GET_VENDOR_ID(capabilities->info) != vendor_id || A2DP_GET_CODEC_ID(capabilities->info) != codec_id)
return false; return false;
@ -359,6 +381,7 @@ const pa_a2dp_codec pa_a2dp_codec_aptx = {
.description = "aptX", .description = "aptX",
.id = { A2DP_CODEC_VENDOR, APTX_VENDOR_ID, APTX_CODEC_ID }, .id = { A2DP_CODEC_VENDOR, APTX_VENDOR_ID, APTX_CODEC_ID },
.support_backchannel = false, .support_backchannel = false,
.can_be_supported = can_be_supported,
.can_accept_capabilities = can_accept_capabilities, .can_accept_capabilities = can_accept_capabilities,
.choose_remote_endpoint = choose_remote_endpoint, .choose_remote_endpoint = choose_remote_endpoint,
.fill_capabilities = fill_capabilities, .fill_capabilities = fill_capabilities,
@ -379,6 +402,7 @@ const pa_a2dp_codec pa_a2dp_codec_aptx_hd = {
.description = "aptX HD", .description = "aptX HD",
.id = { A2DP_CODEC_VENDOR, APTX_HD_VENDOR_ID, APTX_HD_CODEC_ID }, .id = { A2DP_CODEC_VENDOR, APTX_HD_VENDOR_ID, APTX_HD_CODEC_ID },
.support_backchannel = false, .support_backchannel = false,
.can_be_supported = can_be_supported,
.can_accept_capabilities = can_accept_capabilities_hd, .can_accept_capabilities = can_accept_capabilities_hd,
.choose_remote_endpoint = choose_remote_endpoint_hd, .choose_remote_endpoint = choose_remote_endpoint_hd,
.fill_capabilities = fill_capabilities_hd, .fill_capabilities = fill_capabilities_hd,

View file

@ -575,15 +575,8 @@ fail:
void *gst_codec_init(enum a2dp_codec_type codec_type, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *ss) { void *gst_codec_init(enum a2dp_codec_type codec_type, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *ss) {
struct gst_info *info = NULL; struct gst_info *info = NULL;
GError *error = NULL;
bool ret; bool ret;
if (!gst_init_check(NULL, NULL, &error)) {
pa_log_error("Could not initialise GStreamer: %s", error->message);
g_error_free(error);
goto fail;
}
info = pa_xnew0(struct gst_info, 1); info = pa_xnew0(struct gst_info, 1);
pa_assert(info); pa_assert(info);

View file

@ -33,6 +33,20 @@
#include "a2dp-codec-gst.h" #include "a2dp-codec-gst.h"
#include "rtp.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) { 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) if (A2DP_GET_VENDOR_ID(capabilities->info) != vendor_id || A2DP_GET_CODEC_ID(capabilities->info) != codec_id)
return false; return false;
@ -276,6 +290,7 @@ const pa_a2dp_codec pa_a2dp_codec_ldac_eqmid_hq = {
.description = "LDAC (High Quality)", .description = "LDAC (High Quality)",
.id = { A2DP_CODEC_VENDOR, LDAC_VENDOR_ID, LDAC_CODEC_ID }, .id = { A2DP_CODEC_VENDOR, LDAC_VENDOR_ID, LDAC_CODEC_ID },
.support_backchannel = false, .support_backchannel = false,
.can_be_supported = can_be_supported,
.can_accept_capabilities = can_accept_capabilities, .can_accept_capabilities = can_accept_capabilities,
.choose_remote_endpoint = choose_remote_endpoint, .choose_remote_endpoint = choose_remote_endpoint,
.fill_capabilities = fill_capabilities, .fill_capabilities = fill_capabilities,
@ -295,6 +310,7 @@ const pa_a2dp_codec pa_a2dp_codec_ldac_eqmid_sq = {
.description = "LDAC (Standard Quality)", .description = "LDAC (Standard Quality)",
.id = { A2DP_CODEC_VENDOR, LDAC_VENDOR_ID, LDAC_CODEC_ID }, .id = { A2DP_CODEC_VENDOR, LDAC_VENDOR_ID, LDAC_CODEC_ID },
.support_backchannel = false, .support_backchannel = false,
.can_be_supported = can_be_supported,
.can_accept_capabilities = can_accept_capabilities, .can_accept_capabilities = can_accept_capabilities,
.choose_remote_endpoint = choose_remote_endpoint, .choose_remote_endpoint = choose_remote_endpoint,
.fill_capabilities = fill_capabilities, .fill_capabilities = fill_capabilities,
@ -314,6 +330,7 @@ const pa_a2dp_codec pa_a2dp_codec_ldac_eqmid_mq = {
.description = "LDAC (Mobile Quality)", .description = "LDAC (Mobile Quality)",
.id = { A2DP_CODEC_VENDOR, LDAC_VENDOR_ID, LDAC_CODEC_ID }, .id = { A2DP_CODEC_VENDOR, LDAC_VENDOR_ID, LDAC_CODEC_ID },
.support_backchannel = false, .support_backchannel = false,
.can_be_supported = can_be_supported,
.can_accept_capabilities = can_accept_capabilities, .can_accept_capabilities = can_accept_capabilities,
.choose_remote_endpoint = choose_remote_endpoint, .choose_remote_endpoint = choose_remote_endpoint,
.fill_capabilities = fill_capabilities, .fill_capabilities = fill_capabilities,

View file

@ -53,6 +53,10 @@ struct sbc_info {
uint8_t max_bitpool; 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) { 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; const a2dp_sbc_t *capabilities = (const a2dp_sbc_t *) capabilities_buffer;
@ -666,6 +670,7 @@ const pa_a2dp_codec pa_a2dp_codec_sbc = {
.description = "SBC", .description = "SBC",
.id = { A2DP_CODEC_SBC, 0, 0 }, .id = { A2DP_CODEC_SBC, 0, 0 },
.support_backchannel = false, .support_backchannel = false,
.can_be_supported = can_be_supported,
.can_accept_capabilities = can_accept_capabilities, .can_accept_capabilities = can_accept_capabilities,
.choose_remote_endpoint = choose_remote_endpoint, .choose_remote_endpoint = choose_remote_endpoint,
.fill_capabilities = fill_capabilities, .fill_capabilities = fill_capabilities,

View file

@ -23,6 +23,9 @@
#include <pulsecore/core.h> #include <pulsecore/core.h>
#include <pulsecore/core-util.h> #include <pulsecore/core-util.h>
#if defined(HAVE_GSTAPTX) || defined(HAVE_GSTLDAC)
#include <gst/gst.h>
#endif
#include "a2dp-codec-util.h" #include "a2dp-codec-util.h"
@ -72,3 +75,16 @@ const pa_a2dp_codec *pa_bluetooth_get_a2dp_codec(const char *name) {
return NULL; 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
}

View file

@ -31,4 +31,7 @@ const pa_a2dp_codec *pa_bluetooth_a2dp_codec_iter(unsigned int i);
/* Get codec by name */ /* Get codec by name */
const pa_a2dp_codec *pa_bluetooth_get_a2dp_codec(const char *name); const pa_a2dp_codec *pa_bluetooth_get_a2dp_codec(const char *name);
/* Initialise GStreamer */
void pa_bluetooth_a2dp_codec_gst_init(void);
#endif #endif

View file

@ -2150,6 +2150,7 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c, int headset_backe
const pa_a2dp_codec *a2dp_codec; const pa_a2dp_codec *a2dp_codec;
char *endpoint; char *endpoint;
pa_bluetooth_a2dp_codec_gst_init();
y = pa_xnew0(pa_bluetooth_discovery, 1); y = pa_xnew0(pa_bluetooth_discovery, 1);
PA_REFCNT_INIT(y); PA_REFCNT_INIT(y);
y->core = c; y->core = c;