Separate A2DP processes from bluez5-util, module-bluez5-device. Add prop "bluetooth.a2dp_codec" to pulseaudio sink/source properties. --- src/Makefile.am | 15 +- src/modules/bluetooth/a2dp-codecs.h | 115 --- src/modules/bluetooth/a2dp/a2dp-api.h | 170 +++++ src/modules/bluetooth/a2dp/a2dp-codecs.h | 266 +++++++ src/modules/bluetooth/a2dp/a2dp_sbc.c | 659 ++++++++++++++++++ src/modules/bluetooth/a2dp/a2dp_util.c | 298 ++++++++ src/modules/bluetooth/{ => a2dp}/rtp.h | 0 src/modules/bluetooth/bluez5-util.c | 307 +++----- src/modules/bluetooth/bluez5-util.h | 6 + src/modules/bluetooth/module-bluez5-device.c | 460 ++++-------- .../bluetooth/module-bluez5-discover.c | 14 +- 11 files changed, 1672 insertions(+), 638 deletions(-) delete mode 100644 src/modules/bluetooth/a2dp-codecs.h create mode 100644 src/modules/bluetooth/a2dp/a2dp-api.h create mode 100644 src/modules/bluetooth/a2dp/a2dp-codecs.h create mode 100644 src/modules/bluetooth/a2dp/a2dp_sbc.c create mode 100644 src/modules/bluetooth/a2dp/a2dp_util.c rename src/modules/bluetooth/{ => a2dp}/rtp.h (100%) diff --git a/src/Makefile.am b/src/Makefile.am index 2dbb4563b..521b9b684 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -2123,8 +2123,11 @@ module_bluetooth_discover_la_CFLAGS = $(AM_CFLAGS) -DPA_MODULE_NAME=module_bluet libbluez5_util_la_SOURCES = \ modules/bluetooth/bluez5-util.c \ modules/bluetooth/bluez5-util.h \ - modules/bluetooth/a2dp-codecs.h \ - modules/bluetooth/rtp.h + modules/bluetooth/a2dp/a2dp_util.c \ + modules/bluetooth/a2dp/a2dp_sbc.c \ + modules/bluetooth/a2dp/a2dp-api.h \ + modules/bluetooth/a2dp/a2dp-codecs.h \ + modules/bluetooth/a2dp/rtp.h if HAVE_BLUEZ_5_OFONO_HEADSET libbluez5_util_la_SOURCES += \ modules/bluetooth/backend-ofono.c @@ -2135,8 +2138,8 @@ libbluez5_util_la_SOURCES += \ endif libbluez5_util_la_LDFLAGS = -avoid-version -libbluez5_util_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS) -libbluez5_util_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) +libbluez5_util_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS) $(SBC_LIBS) +libbluez5_util_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) $(SBC_CFLAGS) module_bluez5_discover_la_SOURCES = modules/bluetooth/module-bluez5-discover.c module_bluez5_discover_la_LDFLAGS = $(MODULE_LDFLAGS) @@ -2145,8 +2148,8 @@ module_bluez5_discover_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) -DPA_MODULE_NAME= module_bluez5_device_la_SOURCES = modules/bluetooth/module-bluez5-device.c module_bluez5_device_la_LDFLAGS = $(MODULE_LDFLAGS) -module_bluez5_device_la_LIBADD = $(MODULE_LIBADD) $(SBC_LIBS) libbluez5-util.la -module_bluez5_device_la_CFLAGS = $(AM_CFLAGS) $(SBC_CFLAGS) -DPA_MODULE_NAME=module_bluez5_device +module_bluez5_device_la_LIBADD = $(MODULE_LIBADD) libbluez5-util.la +module_bluez5_device_la_CFLAGS = $(AM_CFLAGS) -DPA_MODULE_NAME=module_bluez5_device # Apple Airtunes/RAOP module_raop_sink_la_SOURCES = modules/raop/module-raop-sink.c diff --git a/src/modules/bluetooth/a2dp-codecs.h b/src/modules/bluetooth/a2dp-codecs.h deleted file mode 100644 index 8afcfcb24..000000000 --- a/src/modules/bluetooth/a2dp-codecs.h +++ /dev/null @@ -1,115 +0,0 @@ -/* - * - * BlueZ - Bluetooth protocol stack for Linux - * - * Copyright (C) 2006-2010 Nokia Corporation - * Copyright (C) 2004-2010 Marcel Holtmann <marcel@xxxxxxxxxxxx> - * - * - * This library 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. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see <http://www.gnu.org/licenses/>. - * - */ - -#define A2DP_CODEC_SBC 0x00 -#define A2DP_CODEC_MPEG12 0x01 -#define A2DP_CODEC_MPEG24 0x02 -#define A2DP_CODEC_ATRAC 0x03 - -#define SBC_SAMPLING_FREQ_16000 (1 << 3) -#define SBC_SAMPLING_FREQ_32000 (1 << 2) -#define SBC_SAMPLING_FREQ_44100 (1 << 1) -#define SBC_SAMPLING_FREQ_48000 1 - -#define SBC_CHANNEL_MODE_MONO (1 << 3) -#define SBC_CHANNEL_MODE_DUAL_CHANNEL (1 << 2) -#define SBC_CHANNEL_MODE_STEREO (1 << 1) -#define SBC_CHANNEL_MODE_JOINT_STEREO 1 - -#define SBC_BLOCK_LENGTH_4 (1 << 3) -#define SBC_BLOCK_LENGTH_8 (1 << 2) -#define SBC_BLOCK_LENGTH_12 (1 << 1) -#define SBC_BLOCK_LENGTH_16 1 - -#define SBC_SUBBANDS_4 (1 << 1) -#define SBC_SUBBANDS_8 1 - -#define SBC_ALLOCATION_SNR (1 << 1) -#define SBC_ALLOCATION_LOUDNESS 1 - -#define MPEG_CHANNEL_MODE_MONO (1 << 3) -#define MPEG_CHANNEL_MODE_DUAL_CHANNEL (1 << 2) -#define MPEG_CHANNEL_MODE_STEREO (1 << 1) -#define MPEG_CHANNEL_MODE_JOINT_STEREO 1 - -#define MPEG_LAYER_MP1 (1 << 2) -#define MPEG_LAYER_MP2 (1 << 1) -#define MPEG_LAYER_MP3 1 - -#define MPEG_SAMPLING_FREQ_16000 (1 << 5) -#define MPEG_SAMPLING_FREQ_22050 (1 << 4) -#define MPEG_SAMPLING_FREQ_24000 (1 << 3) -#define MPEG_SAMPLING_FREQ_32000 (1 << 2) -#define MPEG_SAMPLING_FREQ_44100 (1 << 1) -#define MPEG_SAMPLING_FREQ_48000 1 - -#define MAX_BITPOOL 64 -#define MIN_BITPOOL 2 - -#if __BYTE_ORDER == __LITTLE_ENDIAN - -typedef struct { - uint8_t channel_mode:4; - uint8_t frequency:4; - uint8_t allocation_method:2; - uint8_t subbands:2; - uint8_t block_length:4; - uint8_t min_bitpool; - uint8_t max_bitpool; -} __attribute__ ((packed)) a2dp_sbc_t; - -typedef struct { - uint8_t channel_mode:4; - uint8_t crc:1; - uint8_t layer:3; - uint8_t frequency:6; - uint8_t mpf:1; - uint8_t rfa:1; - uint16_t bitrate; -} __attribute__ ((packed)) a2dp_mpeg_t; - -#elif __BYTE_ORDER == __BIG_ENDIAN - -typedef struct { - uint8_t frequency:4; - uint8_t channel_mode:4; - uint8_t block_length:4; - uint8_t subbands:2; - uint8_t allocation_method:2; - uint8_t min_bitpool; - uint8_t max_bitpool; -} __attribute__ ((packed)) a2dp_sbc_t; - -typedef struct { - uint8_t layer:3; - uint8_t crc:1; - uint8_t channel_mode:4; - uint8_t rfa:1; - uint8_t mpf:1; - uint8_t frequency:6; - uint16_t bitrate; -} __attribute__ ((packed)) a2dp_mpeg_t; - -#else -#error "Unknown byte order" -#endif diff --git a/src/modules/bluetooth/a2dp/a2dp-api.h b/src/modules/bluetooth/a2dp/a2dp-api.h new file mode 100644 index 000000000..b357555d0 --- /dev/null +++ b/src/modules/bluetooth/a2dp/a2dp-api.h @@ -0,0 +1,170 @@ +#ifndef fooa2dpcodecapifoo +#define fooa2dpcodecapifoo + +#ifdef HAVE_CONFIG_H + +#include <config.h> + +#endif + +#include <pulse/sample.h> +#include <pulse/proplist.h> +#include <pulsecore/hashmap.h> + +#include "a2dp-codecs.h" +#include "rtp.h" + +typedef struct pa_a2dp_codec pa_a2dp_codec_t; +typedef struct pa_a2dp_config pa_a2dp_config_t; + +extern const pa_a2dp_codec_t pa_a2dp_sbc; + + +/* Run from <pa_a2dp_sink_t>.encode */ + +typedef void (*pa_a2dp_source_read_cb_t)(const void **read_buf, size_t read_buf_size, void *data); + +typedef void (*pa_a2dp_source_read_buf_free_cb_t)(const void **read_buf, void *data); + + +// Larger index stands for higher priority by default +typedef enum pa_a2dp_codec_index { + PA_A2DP_SINK_MIN, + PA_A2DP_SINK_SBC, + PA_A2DP_SINK_MAX, + PA_A2DP_SOURCE_MIN = PA_A2DP_SINK_MAX, + PA_A2DP_SOURCE_SBC, + PA_A2DP_SOURCE_MAX, + PA_A2DP_CODEC_INDEX_UNAVAILABLE +} pa_a2dp_codec_index_t; + +typedef struct pa_a2dp_sink { + int priority; + + /* Load decoder if it's not loaded; Return true if it's loaded */ + bool (*decoder_load)(); + + /* Memory management is pa_a2dp_sink's work */ + bool (*init)(void **codec_data); + + /* Optional. Update user configurations + * Note: not transport 'configuration' or 'capabilities' */ + int (*update_user_config)(pa_proplist *user_config, void **codec_data); + + void (*config_transport)(pa_sample_spec default_sample_spec, const void *configuration, size_t configuration_size, + pa_sample_spec *sample_spec, void **codec_data); + + void (*get_block_size)(size_t read_link_mtu, size_t *read_block_size, void **codec_data); + + void (*setup_stream)(void **codec_data); + + size_t + (*decode)(const void *read_buf, size_t read_buf_size, void *write_buf, size_t write_buf_size, size_t *decoded, + uint32_t *timestamp, void **codec_data); + + void (*free)(void **codec_data); +} pa_a2dp_sink_t; + + +typedef struct pa_a2dp_source { + int priority; + + /* Load encoder if it's not loaded; Return true if it's loaded */ + bool (*encoder_load)(); + + /* Memory management is pa_a2dp_source's work */ + bool (*init)(pa_a2dp_source_read_cb_t read_cb, pa_a2dp_source_read_buf_free_cb_t free_cb, void **codec_data); + + /* Optional. Update user configurations + * Note: not transport 'configuration' or 'capabilities' */ + int (*update_user_config)(pa_proplist *user_config, void **codec_data); + + void (*config_transport)(pa_sample_spec default_sample_spec, const void *configuration, size_t configuration_size, + pa_sample_spec *sample_spec, void **codec_data); + + void (*get_block_size)(size_t write_link_mtu, size_t *write_block_size, void **codec_data); + + void (*setup_stream)(void **codec_data); + + /* The implement should pass read_cb_data to pa_a2dp_source_read_cb, pa_a2dp_source_read_buf_free_cb */ + size_t (*encode)(uint32_t timestamp, void *write_buf, size_t write_buf_size, size_t *encoded, + void *read_cb_data, void **codec_data); + + /* Optional */ + void (*set_tx_length)(size_t len, void **codec_data); + + /* Optional */ + void (*decrease_quality)(void **codec_data); + + void (*free)(void **codec_data); +} pa_a2dp_source_t; + + +struct pa_a2dp_codec { + const char *name; + uint8_t codec; + const a2dp_vendor_codec_t *vendor_codec; + pa_a2dp_sink_t *a2dp_sink; + pa_a2dp_source_t *a2dp_source; + + /* Memory management is pa_a2dp_codec's work */ + size_t (*get_capabilities)(void **capabilities); + + void (*free_capabilities)(void **capabilities); + + size_t (*select_configuration)(const pa_sample_spec default_sample_spec, const uint8_t *supported_capabilities, + const size_t capabilities_size, void **configuration); + + void (*free_configuration)(void **configuration); + + /* Return true if configuration valid */ + bool (*set_configuration)(const uint8_t *selected_configuration, const size_t configuration_size); + +}; + + +typedef struct a_a2dp_freq_cap { + uint32_t rate; + uint32_t cap; +} pa_a2dp_freq_cap_t; + + +/* Utils */ + +bool pa_a2dp_select_cap_frequency(uint32_t freq_cap, pa_sample_spec default_sample_spec, + const pa_a2dp_freq_cap_t *freq_cap_table, + size_t n, pa_a2dp_freq_cap_t *result); + +void pa_a2dp_init(pa_a2dp_config_t **a2dp_config); + +void pa_a2dp_set_max_priority(pa_a2dp_codec_index_t codec_index, pa_a2dp_config_t **a2dp_config); + +void pa_a2dp_set_disable(pa_a2dp_codec_index_t codec_index, pa_a2dp_config_t **a2dp_config); + +void pa_a2dp_free(pa_a2dp_config_t **a2dp_config); + + +void pa_a2dp_get_sink_indices(pa_hashmap **sink_indices, pa_a2dp_config_t **a2dp_config); + +void pa_a2dp_get_source_indices(pa_hashmap **source_indices, pa_a2dp_config_t **a2dp_config); + +void pa_a2dp_get_ordered_indices(pa_hashmap **ordered_indices, pa_a2dp_config_t **a2dp_config); + + +void pa_a2dp_codec_index_to_endpoint(pa_a2dp_codec_index_t codec_index, const char **endpoint); + +void pa_a2dp_endpoint_to_codec_index(const char *endpoint, pa_a2dp_codec_index_t *codec_index); + +void pa_a2dp_codec_index_to_a2dp_codec(pa_a2dp_codec_index_t codec_index, const pa_a2dp_codec_t **a2dp_codec); + +void +pa_a2dp_a2dp_codec_to_codec_index(const pa_a2dp_codec_t *a2dp_codec, bool is_sink, pa_a2dp_codec_index_t *codec_index); + +void pa_a2dp_get_a2dp_codec(uint8_t codec, const a2dp_vendor_codec_t *vendor_codec, const pa_a2dp_codec_t **a2dp_codec); + +bool pa_a2dp_codec_index_is_sink(pa_a2dp_codec_index_t codec_index); + +bool pa_a2dp_codec_index_is_source(pa_a2dp_codec_index_t codec_index); + + +#endif diff --git a/src/modules/bluetooth/a2dp/a2dp-codecs.h b/src/modules/bluetooth/a2dp/a2dp-codecs.h new file mode 100644 index 000000000..aefafb635 --- /dev/null +++ b/src/modules/bluetooth/a2dp/a2dp-codecs.h @@ -0,0 +1,266 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann <marcel@xxxxxxxxxxxx> + * + * + * This library 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. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#define A2DP_CODEC_SBC 0x00 +#define A2DP_CODEC_MPEG12 0x01 +#define A2DP_CODEC_MPEG24 0x02 +#define A2DP_CODEC_ATRAC 0x03 +#define A2DP_CODEC_VENDOR 0xFF + +#define SBC_SAMPLING_FREQ_16000 (1 << 3) +#define SBC_SAMPLING_FREQ_32000 (1 << 2) +#define SBC_SAMPLING_FREQ_44100 (1 << 1) +#define SBC_SAMPLING_FREQ_48000 1 + +#define SBC_CHANNEL_MODE_MONO (1 << 3) +#define SBC_CHANNEL_MODE_DUAL_CHANNEL (1 << 2) +#define SBC_CHANNEL_MODE_STEREO (1 << 1) +#define SBC_CHANNEL_MODE_JOINT_STEREO 1 + +#define SBC_BLOCK_LENGTH_4 (1 << 3) +#define SBC_BLOCK_LENGTH_8 (1 << 2) +#define SBC_BLOCK_LENGTH_12 (1 << 1) +#define SBC_BLOCK_LENGTH_16 1 + +#define SBC_SUBBANDS_4 (1 << 1) +#define SBC_SUBBANDS_8 1 + +#define SBC_ALLOCATION_SNR (1 << 1) +#define SBC_ALLOCATION_LOUDNESS 1 + +#define MAX_BITPOOL 64 +#define MIN_BITPOOL 2 + +#define MPEG_CHANNEL_MODE_MONO (1 << 3) +#define MPEG_CHANNEL_MODE_DUAL_CHANNEL (1 << 2) +#define MPEG_CHANNEL_MODE_STEREO (1 << 1) +#define MPEG_CHANNEL_MODE_JOINT_STEREO 1 + +#define MPEG_LAYER_MP1 (1 << 2) +#define MPEG_LAYER_MP2 (1 << 1) +#define MPEG_LAYER_MP3 1 + +#define MPEG_SAMPLING_FREQ_16000 (1 << 5) +#define MPEG_SAMPLING_FREQ_22050 (1 << 4) +#define MPEG_SAMPLING_FREQ_24000 (1 << 3) +#define MPEG_SAMPLING_FREQ_32000 (1 << 2) +#define MPEG_SAMPLING_FREQ_44100 (1 << 1) +#define MPEG_SAMPLING_FREQ_48000 1 + +#define MPEG_BIT_RATE_VBR 0x8000 +#define MPEG_BIT_RATE_320000 0x4000 +#define MPEG_BIT_RATE_256000 0x2000 +#define MPEG_BIT_RATE_224000 0x1000 +#define MPEG_BIT_RATE_192000 0x0800 +#define MPEG_BIT_RATE_160000 0x0400 +#define MPEG_BIT_RATE_128000 0x0200 +#define MPEG_BIT_RATE_112000 0x0100 +#define MPEG_BIT_RATE_96000 0x0080 +#define MPEG_BIT_RATE_80000 0x0040 +#define MPEG_BIT_RATE_64000 0x0020 +#define MPEG_BIT_RATE_56000 0x0010 +#define MPEG_BIT_RATE_48000 0x0008 +#define MPEG_BIT_RATE_40000 0x0004 +#define MPEG_BIT_RATE_32000 0x0002 +#define MPEG_BIT_RATE_FREE 0x0001 + +#define AAC_OBJECT_TYPE_MPEG2_AAC_LC 0x80 +#define AAC_OBJECT_TYPE_MPEG4_AAC_LC 0x40 +#define AAC_OBJECT_TYPE_MPEG4_AAC_LTP 0x20 +#define AAC_OBJECT_TYPE_MPEG4_AAC_SCA 0x10 + +#define AAC_SAMPLING_FREQ_8000 0x0800 +#define AAC_SAMPLING_FREQ_11025 0x0400 +#define AAC_SAMPLING_FREQ_12000 0x0200 +#define AAC_SAMPLING_FREQ_16000 0x0100 +#define AAC_SAMPLING_FREQ_22050 0x0080 +#define AAC_SAMPLING_FREQ_24000 0x0040 +#define AAC_SAMPLING_FREQ_32000 0x0020 +#define AAC_SAMPLING_FREQ_44100 0x0010 +#define AAC_SAMPLING_FREQ_48000 0x0008 +#define AAC_SAMPLING_FREQ_64000 0x0004 +#define AAC_SAMPLING_FREQ_88200 0x0002 +#define AAC_SAMPLING_FREQ_96000 0x0001 + +#define AAC_CHANNELS_1 0x02 +#define AAC_CHANNELS_2 0x01 + +#define AAC_GET_BITRATE(a) ((a).bitrate1 << 16 | \ + (a).bitrate2 << 8 | (a).bitrate3) +#define AAC_GET_FREQUENCY(a) ((a).frequency1 << 4 | (a).frequency2) + +#define AAC_SET_BITRATE(a, b) \ + do { \ + (a).bitrate1 = (b >> 16) & 0x7f; \ + (a).bitrate2 = (b >> 8) & 0xff; \ + (a).bitrate3 = b & 0xff; \ + } while (0) +#define AAC_SET_FREQUENCY(a, f) \ + do { \ + (a).frequency1 = (f >> 4) & 0xff; \ + (a).frequency2 = f & 0x0f; \ + } while (0) + +#define AAC_INIT_BITRATE(b) \ + .bitrate1 = (b >> 16) & 0x7f, \ + .bitrate2 = (b >> 8) & 0xff, \ + .bitrate3 = b & 0xff, +#define AAC_INIT_FREQUENCY(f) \ + .frequency1 = (f >> 4) & 0xff, \ + .frequency2 = f & 0x0f, + +#define APTX_VENDOR_ID 0x0000004f +#define APTX_CODEC_ID 0x0001 + +#define APTX_CHANNEL_MODE_MONO 0x01 +#define APTX_CHANNEL_MODE_STEREO 0x02 + +#define APTX_SAMPLING_FREQ_16000 0x08 +#define APTX_SAMPLING_FREQ_32000 0x04 +#define APTX_SAMPLING_FREQ_44100 0x02 +#define APTX_SAMPLING_FREQ_48000 0x01 + +#define LDAC_VENDOR_ID 0x0000012d +#define LDAC_CODEC_ID 0x00aa + +typedef struct { + uint32_t vendor_id; + uint16_t codec_id; +} __attribute__ ((packed)) a2dp_vendor_codec_t; + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +typedef struct { + uint8_t channel_mode:4; + uint8_t frequency:4; + uint8_t allocation_method:2; + uint8_t subbands:2; + uint8_t block_length:4; + uint8_t min_bitpool; + uint8_t max_bitpool; +} __attribute__ ((packed)) a2dp_sbc_t; + +typedef struct { + uint8_t channel_mode:4; + uint8_t crc:1; + uint8_t layer:3; + uint8_t frequency:6; + uint8_t mpf:1; + uint8_t rfa:1; + uint16_t bitrate; +} __attribute__ ((packed)) a2dp_mpeg_t; + +typedef struct { + uint8_t object_type; + uint8_t frequency1; + uint8_t rfa:2; + uint8_t channels:2; + uint8_t frequency2:4; + uint8_t bitrate1:7; + uint8_t vbr:1; + uint8_t bitrate2; + uint8_t bitrate3; +} __attribute__ ((packed)) a2dp_aac_t; + +typedef struct { + a2dp_vendor_codec_t info; + uint8_t channel_mode:4; + uint8_t frequency:4; +} __attribute__ ((packed)) a2dp_aptx_t; + +typedef struct { + a2dp_vendor_codec_t info; + uint8_t channel_mode:4; + uint8_t frequency:4; + uint8_t acl_sprint_reserved[4]; +} __attribute__ ((packed)) a2dp_aptxhd_t; + +//Codec Specific Information Elements for LDAC(the datasheet is in ldacBT.h) +typedef struct { + a2dp_vendor_codec_t info; + uint8_t frequency:6; + uint8_t rfa0:2; + uint8_t channel_mode:3; + uint8_t rfa1:5; +} __attribute__ ((packed)) a2dp_ldac_t; + +#elif __BYTE_ORDER == __BIG_ENDIAN + +typedef struct { + uint8_t frequency:4; + uint8_t channel_mode:4; + uint8_t block_length:4; + uint8_t subbands:2; + uint8_t allocation_method:2; + uint8_t min_bitpool; + uint8_t max_bitpool; +} __attribute__ ((packed)) a2dp_sbc_t; + +typedef struct { + uint8_t layer:3; + uint8_t crc:1; + uint8_t channel_mode:4; + uint8_t rfa:1; + uint8_t mpf:1; + uint8_t frequency:6; + uint16_t bitrate; +} __attribute__ ((packed)) a2dp_mpeg_t; + +typedef struct { + uint8_t object_type; + uint8_t frequency1; + uint8_t frequency2:4; + uint8_t channels:2; + uint8_t rfa:2; + uint8_t vbr:1; + uint8_t bitrate1:7; + uint8_t bitrate2; + uint8_t bitrate3; +} __attribute__ ((packed)) a2dp_aac_t; + +typedef struct { + a2dp_vendor_codec_t info; + uint8_t frequency:4; + uint8_t channel_mode:4; +} __attribute__ ((packed)) a2dp_aptx_t; + +typedef struct { + a2dp_vendor_codec_t info; + uint8_t frequency:4; + uint8_t channel_mode:4; + uint8_t acl_sprint_reserved[4]; +} __attribute__ ((packed)) a2dp_aptxhd_t; + +//Codec Specific Information Elements for LDAC(the datasheet is in ldacBT.h) +typedef struct { + a2dp_vendor_codec_t info; + uint8_t rfa0:2; + uint8_t frequency:6; + uint8_t rfa1:5; + uint8_t channel_mode:3; +} __attribute__ ((packed)) a2dp_ldac_t; + +#else +#error "Unknown byte order" +#endif diff --git a/src/modules/bluetooth/a2dp/a2dp_sbc.c b/src/modules/bluetooth/a2dp/a2dp_sbc.c new file mode 100644 index 000000000..4ad072bc9 --- /dev/null +++ b/src/modules/bluetooth/a2dp/a2dp_sbc.c @@ -0,0 +1,659 @@ + +#include <sbc/sbc.h> +#include <arpa/inet.h> +#include <string.h> + +#ifdef HAVE_CONFIG_H + +#include <config.h> + +#endif + +#include <pulse/xmalloc.h> +#include <pulsecore/once.h> + +#include "a2dp-api.h" + +#define streq(a, b) (!strcmp((a),(b))) + +#define BITPOOL_DEC_LIMIT 32 +#define BITPOOL_DEC_STEP 5 + +typedef struct sbc_info { + pa_a2dp_source_read_cb_t read_pcm; + pa_a2dp_source_read_buf_free_cb_t read_buf_free; + + bool is_a2dp_sink; + + int channel_mode; + sbc_t sbc; /* Codec data */ + bool sbc_initialized; /* Keep track if the encoder is initialized */ + size_t codesize, frame_length; /* SBC Codesize, frame_length. We simply cache those values here */ + uint16_t seq_num; /* Cumulative packet sequence */ + uint8_t min_bitpool; + uint8_t max_bitpool; + + size_t read_block_size; + size_t write_block_size; + +} sbc_info_t; + +static bool pa_sbc_decoder_load() { + /* SBC libs dynamically linked */ + return true; +} + +static bool pa_sbc_encoder_load() { + /* SBC libs dynamically linked */ + return true; +} + +static bool +pa_sbc_decoder_init(void **codec_data) { + sbc_info_t *info = pa_xmalloc0(sizeof(sbc_info_t)); + *codec_data = info; + info->is_a2dp_sink = true; + return true; +} + +static bool +pa_sbc_encoder_init(pa_a2dp_source_read_cb_t read_cb, pa_a2dp_source_read_buf_free_cb_t free_cb, void **codec_data) { + sbc_info_t *info = pa_xmalloc0(sizeof(sbc_info_t)); + *codec_data = info; + info->is_a2dp_sink = false; + info->read_pcm = read_cb; + info->read_buf_free = free_cb; + return true; +} + +static int pa_sbc_update_user_config(pa_proplist *user_config, void **codec_data) { + return 0; +} + +static size_t +pa_sbc_decode(const void *read_buf, size_t read_buf_size, void *write_buf, size_t write_buf_size, size_t *_decoded, + uint32_t *timestamp, void **codec_data) { + const struct rtp_header *header; + const struct rtp_payload *payload; + const void *p; + void *d; + size_t to_write, to_decode; + size_t total_written = 0; + sbc_info_t *sbc_info = *codec_data; + pa_assert(sbc_info); + + header = read_buf; + payload = (struct rtp_payload *) ((uint8_t *) read_buf + sizeof(*header)); + + *timestamp = ntohl(header->timestamp); + + p = (uint8_t *) read_buf + sizeof(*header) + sizeof(*payload); + to_decode = read_buf_size - sizeof(*header) - sizeof(*payload); + + d = write_buf; + to_write = write_buf_size; + + *_decoded = 0; + while (PA_LIKELY(to_decode > 0)) { + size_t written; + ssize_t decoded; + + decoded = sbc_decode(&sbc_info->sbc, + p, to_decode, + d, to_write, + &written); + + if (PA_UNLIKELY(decoded <= 0)) { + pa_log_error("SBC decoding error (%li)", (long) decoded); + *_decoded = 0; + return 0; + } + + total_written += written; + + /* Reset frame length, it can be changed due to bitpool change */ + sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc); + + pa_assert_fp((size_t) decoded <= to_decode); + pa_assert_fp((size_t) decoded == sbc_info->frame_length); + + pa_assert_fp((size_t) written == sbc_info->codesize); + + *_decoded += decoded; + p = (const uint8_t *) p + decoded; + to_decode -= decoded; + + d = (uint8_t *) d + written; + to_write -= written; + } + + return total_written; +} + +static size_t +pa_sbc_encode(uint32_t timestamp, void *write_buf, size_t write_buf_size, size_t *_encoded, void *read_cb_data, + void **codec_data) { + struct rtp_header *header; + struct rtp_payload *payload; + size_t nbytes; + void *d; + const void *p; + size_t to_write, to_encode; + unsigned frame_count; + sbc_info_t *sbc_info = *codec_data; + pa_assert(sbc_info); + + header = write_buf; + payload = (struct rtp_payload *) ((uint8_t *) write_buf + sizeof(*header)); + + frame_count = 0; + + /* Try to create a packet of the full MTU */ + + sbc_info->read_pcm(&p, (size_t) sbc_info->write_block_size, read_cb_data); + + to_encode = sbc_info->write_block_size; + + d = (uint8_t *) write_buf + sizeof(*header) + sizeof(*payload); + to_write = write_buf_size - sizeof(*header) - sizeof(*payload); + + *_encoded = 0; + while (PA_LIKELY(to_encode > 0 && to_write > 0)) { + ssize_t written; + ssize_t encoded; + + encoded = sbc_encode(&sbc_info->sbc, + p, to_encode, + d, to_write, + &written); + + if (PA_UNLIKELY(encoded <= 0)) { + pa_log_error("SBC encoding error (%li)", (long) encoded); + sbc_info->read_buf_free(&p, read_cb_data); + *_encoded = 0; + return 0; + } + + pa_assert_fp((size_t) encoded <= to_encode); + pa_assert_fp((size_t) encoded == sbc_info->codesize); + + pa_assert_fp((size_t) written <= to_write); + pa_assert_fp((size_t) written == sbc_info->frame_length); + + p = (const uint8_t *) p + encoded; + to_encode -= encoded; + *_encoded += encoded; + + d = (uint8_t *) d + written; + to_write -= written; + + frame_count++; + } + + sbc_info->read_buf_free(&p, read_cb_data); + + pa_assert(to_encode == 0); + + PA_ONCE_BEGIN + { + const char *impl = sbc_get_implementation_info(&sbc_info->sbc); + pa_log_debug("Using SBC encoder implementation: %s", impl ? impl : "NULL"); + } + PA_ONCE_END; + + /* write it to the fifo */ + memset(write_buf, 0, sizeof(*header) + sizeof(*payload)); + header->v = 2; + header->pt = 1; + header->sequence_number = htons(sbc_info->seq_num++); + header->timestamp = htonl(timestamp); + header->ssrc = htonl(1); + payload->frame_count = frame_count; + + nbytes = (uint8_t *) d - (uint8_t *) write_buf; + + return nbytes; +} + +static void +pa_sbc_config_transport(pa_sample_spec default_sample_spec, const void *configuration, size_t configuration_size, + pa_sample_spec *sample_spec, void **codec_data) { + sbc_info_t *sbc_info = *codec_data; + a2dp_sbc_t *config = (a2dp_sbc_t *) configuration; + + pa_assert(sbc_info); + pa_assert_se(configuration_size == sizeof(*config)); + + if (sbc_info->sbc_initialized) + sbc_reinit(&sbc_info->sbc, 0); + else + sbc_init(&sbc_info->sbc, 0); + sbc_info->sbc_initialized = true; + + sample_spec->format = PA_SAMPLE_S16LE; + + switch (config->frequency) { + case SBC_SAMPLING_FREQ_16000: + sbc_info->sbc.frequency = SBC_FREQ_16000; + sample_spec->rate = 16000U; + break; + case SBC_SAMPLING_FREQ_32000: + sbc_info->sbc.frequency = SBC_FREQ_32000; + sample_spec->rate = 32000U; + break; + case SBC_SAMPLING_FREQ_44100: + sbc_info->sbc.frequency = SBC_FREQ_44100; + sample_spec->rate = 44100U; + break; + case SBC_SAMPLING_FREQ_48000: + sbc_info->sbc.frequency = SBC_FREQ_48000; + sample_spec->rate = 48000U; + break; + default: + pa_assert_not_reached(); + } + + switch (config->channel_mode) { + case SBC_CHANNEL_MODE_MONO: + sbc_info->sbc.mode = SBC_MODE_MONO; + sample_spec->channels = 1; + break; + case SBC_CHANNEL_MODE_DUAL_CHANNEL: + sbc_info->sbc.mode = SBC_MODE_DUAL_CHANNEL; + sample_spec->channels = 2; + break; + case SBC_CHANNEL_MODE_STEREO: + sbc_info->sbc.mode = SBC_MODE_STEREO; + sample_spec->channels = 2; + break; + case SBC_CHANNEL_MODE_JOINT_STEREO: + sbc_info->sbc.mode = SBC_MODE_JOINT_STEREO; + sample_spec->channels = 2; + break; + default: + pa_assert_not_reached(); + } + + switch (config->allocation_method) { + case SBC_ALLOCATION_SNR: + sbc_info->sbc.allocation = SBC_AM_SNR; + break; + case SBC_ALLOCATION_LOUDNESS: + sbc_info->sbc.allocation = SBC_AM_LOUDNESS; + break; + default: + pa_assert_not_reached(); + } + + switch (config->subbands) { + case SBC_SUBBANDS_4: + sbc_info->sbc.subbands = SBC_SB_4; + break; + case SBC_SUBBANDS_8: + sbc_info->sbc.subbands = SBC_SB_8; + break; + default: + pa_assert_not_reached(); + } + + switch (config->block_length) { + case SBC_BLOCK_LENGTH_4: + sbc_info->sbc.blocks = SBC_BLK_4; + break; + case SBC_BLOCK_LENGTH_8: + sbc_info->sbc.blocks = SBC_BLK_8; + break; + case SBC_BLOCK_LENGTH_12: + sbc_info->sbc.blocks = SBC_BLK_12; + break; + case SBC_BLOCK_LENGTH_16: + sbc_info->sbc.blocks = SBC_BLK_16; + break; + default: + pa_assert_not_reached(); + } + + + sbc_info->min_bitpool = config->min_bitpool; + sbc_info->max_bitpool = config->max_bitpool; + + /* Set minimum bitpool for source to get the maximum possible block_size */ + sbc_info->sbc.bitpool = sbc_info->is_a2dp_sink ? sbc_info->min_bitpool : sbc_info->max_bitpool; + sbc_info->codesize = sbc_get_codesize(&sbc_info->sbc); + sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc); + + pa_log_info("SBC parameters: allocation=%u, subbands=%u, blocks=%u, bitpool=%u", + sbc_info->sbc.allocation, sbc_info->sbc.subbands ? 8 : 4, sbc_info->sbc.blocks, sbc_info->sbc.bitpool); +}; + +static void pa_sbc_get_read_block_size(size_t read_link_mtu, size_t *read_block_size, void **codec_data) { + sbc_info_t *sbc_info = *codec_data; + pa_assert(sbc_info); + *read_block_size = + (read_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload)) + / sbc_info->frame_length * sbc_info->codesize; + sbc_info->read_block_size = *read_block_size; +}; + +static void pa_sbc_get_write_block_size(size_t write_link_mtu, size_t *write_block_size, void **codec_data) { + sbc_info_t *sbc_info = *codec_data; + pa_assert(sbc_info); + *write_block_size = + (write_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload)) + / sbc_info->frame_length * sbc_info->codesize; + sbc_info->write_block_size = *write_block_size; +}; + +static void a2dp_set_bitpool(uint8_t bitpool, void **codec_data) { + sbc_info_t *sbc_info = *codec_data; + + if (sbc_info->sbc.bitpool == bitpool) + return; + + if (bitpool > sbc_info->max_bitpool) + bitpool = sbc_info->max_bitpool; + else if (bitpool < sbc_info->min_bitpool) + bitpool = sbc_info->min_bitpool; + + sbc_info->sbc.bitpool = bitpool; + + sbc_info->codesize = sbc_get_codesize(&sbc_info->sbc); + sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc); + + pa_log_debug("Bitpool has changed to %u", sbc_info->sbc.bitpool); +} + +static void a2dp_reduce_bitpool(void **codec_data) { + sbc_info_t *sbc_info = *codec_data; + uint8_t bitpool; + + /* Check if bitpool is already at its limit */ + if (sbc_info->sbc.bitpool <= BITPOOL_DEC_LIMIT) + return; + + bitpool = (uint8_t)(sbc_info->sbc.bitpool - BITPOOL_DEC_STEP); + + if (bitpool < BITPOOL_DEC_LIMIT) + bitpool = BITPOOL_DEC_LIMIT; + + a2dp_set_bitpool(bitpool, codec_data); +} + +static void pa_sbc_setup_stream(void **codec_data) { + sbc_info_t *sbc_info = *codec_data; + pa_assert(sbc_info); + if (!sbc_info->is_a2dp_sink) + a2dp_set_bitpool(sbc_info->max_bitpool, codec_data); +}; + +static void pa_sbc_free(void **codec_data) { + sbc_info_t *sbc_info = *codec_data; + if (!sbc_info) + return; + + + pa_xfree(sbc_info); + *codec_data = NULL; + +}; + +static size_t pa_sbc_get_capabilities(void **_capabilities) { + a2dp_sbc_t *capabilities = pa_xmalloc0(sizeof(a2dp_sbc_t)); + + capabilities->channel_mode = SBC_CHANNEL_MODE_MONO | SBC_CHANNEL_MODE_DUAL_CHANNEL | SBC_CHANNEL_MODE_STEREO | + SBC_CHANNEL_MODE_JOINT_STEREO; + capabilities->frequency = SBC_SAMPLING_FREQ_16000 | SBC_SAMPLING_FREQ_32000 | SBC_SAMPLING_FREQ_44100 | + SBC_SAMPLING_FREQ_48000; + capabilities->allocation_method = SBC_ALLOCATION_SNR | SBC_ALLOCATION_LOUDNESS; + capabilities->subbands = SBC_SUBBANDS_4 | SBC_SUBBANDS_8; + capabilities->block_length = SBC_BLOCK_LENGTH_4 | SBC_BLOCK_LENGTH_8 | SBC_BLOCK_LENGTH_12 | SBC_BLOCK_LENGTH_16; + capabilities->min_bitpool = MIN_BITPOOL; + capabilities->max_bitpool = MAX_BITPOOL; + + *_capabilities = capabilities; + + return sizeof(*capabilities); +}; + +static uint8_t a2dp_default_bitpool(uint8_t freq, uint8_t mode) { + /* These bitpool values were chosen based on the A2DP spec recommendation */ + switch (freq) { + case SBC_SAMPLING_FREQ_16000: + case SBC_SAMPLING_FREQ_32000: + return 53; + + case SBC_SAMPLING_FREQ_44100: + + switch (mode) { + case SBC_CHANNEL_MODE_MONO: + case SBC_CHANNEL_MODE_DUAL_CHANNEL: + return 31; + + case SBC_CHANNEL_MODE_STEREO: + case SBC_CHANNEL_MODE_JOINT_STEREO: + return 53; + default: + break; + } + + pa_log_warn("Invalid channel mode %u", mode); + return 53; + + case SBC_SAMPLING_FREQ_48000: + + switch (mode) { + case SBC_CHANNEL_MODE_MONO: + case SBC_CHANNEL_MODE_DUAL_CHANNEL: + return 29; + + case SBC_CHANNEL_MODE_STEREO: + case SBC_CHANNEL_MODE_JOINT_STEREO: + return 51; + default: + break; + } + + pa_log_warn("Invalid channel mode %u", mode); + return 51; + default: + break; + } + + pa_log_warn("Invalid sampling freq %u", freq); + return 53; +} + +static size_t +pa_sbc_select_configuration(const pa_sample_spec default_sample_spec, const uint8_t *supported_capabilities, + const size_t capabilities_size, void **configuration) { + a2dp_sbc_t *cap = (a2dp_sbc_t *) supported_capabilities; + a2dp_sbc_t *config = pa_xmalloc0(sizeof(a2dp_sbc_t)); + pa_a2dp_freq_cap_t sbc_freq_cap, sbc_freq_table[] = { + {16000U, SBC_SAMPLING_FREQ_16000}, + {32000U, SBC_SAMPLING_FREQ_32000}, + {44100U, SBC_SAMPLING_FREQ_44100}, + {48000U, SBC_SAMPLING_FREQ_48000} + }; + + if (capabilities_size != sizeof(a2dp_sbc_t)) + return 0; + + if (!pa_a2dp_select_cap_frequency(cap->frequency, default_sample_spec, sbc_freq_table, + PA_ELEMENTSOF(sbc_freq_table), &sbc_freq_cap)) + return 0; + + config->frequency = (uint8_t) sbc_freq_cap.cap; + + if (default_sample_spec.channels <= 1) { + if (cap->channel_mode & SBC_CHANNEL_MODE_MONO) + config->channel_mode = SBC_CHANNEL_MODE_MONO; + else if (cap->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO) + config->channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO; + else if (cap->channel_mode & SBC_CHANNEL_MODE_STEREO) + config->channel_mode = SBC_CHANNEL_MODE_STEREO; + else if (cap->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL) + config->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL; + else { + pa_log_error("No supported channel modes"); + return 0; + } + } + + if (default_sample_spec.channels >= 2) { + if (cap->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO) + config->channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO; + else if (cap->channel_mode & SBC_CHANNEL_MODE_STEREO) + config->channel_mode = SBC_CHANNEL_MODE_STEREO; + else if (cap->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL) + config->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL; + else if (cap->channel_mode & SBC_CHANNEL_MODE_MONO) + config->channel_mode = SBC_CHANNEL_MODE_MONO; + else { + pa_log_error("No supported channel modes"); + return 0; + } + } + if (cap->block_length & SBC_BLOCK_LENGTH_16) + config->block_length = SBC_BLOCK_LENGTH_16; + else if (cap->block_length & SBC_BLOCK_LENGTH_12) + config->block_length = SBC_BLOCK_LENGTH_12; + else if (cap->block_length & SBC_BLOCK_LENGTH_8) + config->block_length = SBC_BLOCK_LENGTH_8; + else if (cap->block_length & SBC_BLOCK_LENGTH_4) + config->block_length = SBC_BLOCK_LENGTH_4; + else { + pa_log_error("No supported block lengths"); + return 0; + } + + if (cap->subbands & SBC_SUBBANDS_8) + config->subbands = SBC_SUBBANDS_8; + else if (cap->subbands & SBC_SUBBANDS_4) + config->subbands = SBC_SUBBANDS_4; + else { + pa_log_error("No supported subbands"); + return 0; + } + + if (cap->allocation_method & SBC_ALLOCATION_LOUDNESS) + config->allocation_method = SBC_ALLOCATION_LOUDNESS; + else if (cap->allocation_method & SBC_ALLOCATION_SNR) + config->allocation_method = SBC_ALLOCATION_SNR; + + config->min_bitpool = (uint8_t) PA_MAX(MIN_BITPOOL, cap->min_bitpool); + config->max_bitpool = (uint8_t) PA_MIN(a2dp_default_bitpool(config->frequency, config->channel_mode), + cap->max_bitpool); + + if (config->min_bitpool > config->max_bitpool) + return 0; + + *configuration = config; + return sizeof(*config); +}; + +static void pa_sbc_free_capabilities(void **capabilities) { + if (!capabilities || !*capabilities) + return; + pa_xfree(*capabilities); + *capabilities = NULL; +} + +static bool pa_sbc_set_configuration(const uint8_t *selected_configuration, const size_t configuration_size) { + a2dp_sbc_t *c = (a2dp_sbc_t *) selected_configuration; + + if (configuration_size != sizeof(a2dp_sbc_t)) { + pa_log_error("SBC configuration array of invalid size"); + return false; + } + + switch (c->frequency) { + case SBC_SAMPLING_FREQ_16000: + case SBC_SAMPLING_FREQ_32000: + case SBC_SAMPLING_FREQ_44100: + case SBC_SAMPLING_FREQ_48000: + break; + default: + pa_log_error("Invalid sampling frequency in SBC configuration"); + return false; + } + + switch (c->channel_mode) { + case SBC_CHANNEL_MODE_MONO: + case SBC_CHANNEL_MODE_DUAL_CHANNEL: + case SBC_CHANNEL_MODE_STEREO: + case SBC_CHANNEL_MODE_JOINT_STEREO: + break; + default: + pa_log_error("Invalid channel mode in SBC Configuration"); + return false; + } + + switch (c->allocation_method) { + case SBC_ALLOCATION_SNR: + case SBC_ALLOCATION_LOUDNESS: + break; + default: + pa_log_error("Invalid allocation method in SBC configuration"); + return false; + } + + switch (c->subbands) { + case SBC_SUBBANDS_4: + case SBC_SUBBANDS_8: + break; + default: + pa_log_error("Invalid SBC subbands in SBC configuration"); + return false; + } + + switch (c->block_length) { + case SBC_BLOCK_LENGTH_4: + case SBC_BLOCK_LENGTH_8: + case SBC_BLOCK_LENGTH_12: + case SBC_BLOCK_LENGTH_16: + break; + default: + pa_log_error("Invalid block length in configuration"); + return false; + } + + return true; +}; + + +static pa_a2dp_source_t pa_sbc_source = { + .encoder_load = pa_sbc_encoder_load, + .init = pa_sbc_encoder_init, + .update_user_config = pa_sbc_update_user_config, + .encode = pa_sbc_encode, + .config_transport=pa_sbc_config_transport, + .get_block_size=pa_sbc_get_write_block_size, + .setup_stream = pa_sbc_setup_stream, + .set_tx_length = NULL, + .decrease_quality = a2dp_reduce_bitpool, + .free = pa_sbc_free +}; + +static pa_a2dp_sink_t pa_sbc_sink = { + .decoder_load = pa_sbc_decoder_load, + .init = pa_sbc_decoder_init, + .update_user_config = pa_sbc_update_user_config, + .config_transport = pa_sbc_config_transport, + .get_block_size = pa_sbc_get_read_block_size, + .setup_stream = pa_sbc_setup_stream, + .decode = pa_sbc_decode, + .free = pa_sbc_free +}; + +const pa_a2dp_codec_t pa_a2dp_sbc = { + .name = "SBC", + .codec = A2DP_CODEC_SBC, + .vendor_codec = NULL, + .a2dp_sink = &pa_sbc_sink, + .a2dp_source = &pa_sbc_source, + .get_capabilities = pa_sbc_get_capabilities, + .select_configuration = pa_sbc_select_configuration, + .free_capabilities = pa_sbc_free_capabilities, + .free_configuration = pa_sbc_free_capabilities, + .set_configuration = pa_sbc_set_configuration +}; \ No newline at end of file diff --git a/src/modules/bluetooth/a2dp/a2dp_util.c b/src/modules/bluetooth/a2dp/a2dp_util.c new file mode 100644 index 000000000..e73d8c8ff --- /dev/null +++ b/src/modules/bluetooth/a2dp/a2dp_util.c @@ -0,0 +1,298 @@ + +#include <string.h> +#include <pulse/xmalloc.h> + +#include "a2dp-api.h" + +#define streq(a, b) (!strcmp((a),(b))) + +#define A2DP_SOURCE_ENDPOINT "/MediaEndpoint/A2DPSource" +#define A2DP_SINK_ENDPOINT "/MediaEndpoint/A2DPSink" + +#define A2DP_SBC_SRC_ENDPOINT A2DP_SOURCE_ENDPOINT "/SBC" +#define A2DP_SBC_SNK_ENDPOINT A2DP_SINK_ENDPOINT "/SBC" + +#define A2DP_VENDOR_SRC_ENDPOINT A2DP_SOURCE_ENDPOINT "/VENDOR" +#define A2DP_VENDOR_SNK_ENDPOINT A2DP_SINK_ENDPOINT "/VENDOR" + + +#define PA_A2DP_PRIORITY_DISABLE 0 +#define PA_A2DP_PRIORITY_MIN 1 + + +struct pa_a2dp_config { + int max_priority; + pa_hashmap *a2dp_sinks; + pa_hashmap *a2dp_sources; + pa_hashmap *active_index_priorities; + pa_hashmap *ordered_indices; +}; + +static unsigned int_hash_func(const void *p) { + return (unsigned) *((const int *) p); +} + +static int int_compare_func(const void *a, const void *b) { + const int x = *((const int *) a); + const int y = *((const int *) b); + return x < y ? -1 : (x > y ? 1 : 0); +}; + + +void pa_a2dp_init(pa_a2dp_config_t **a2dp_config) { + pa_a2dp_config_t *config; + pa_a2dp_codec_index_t codec_index = PA_A2DP_SINK_MIN; + const pa_a2dp_codec_t *a2dp_codec; + + config = pa_xmalloc(sizeof(pa_a2dp_config_t)); + *a2dp_config = config; + + config->a2dp_sinks = pa_hashmap_new_full(int_hash_func, int_compare_func, pa_xfree, pa_xfree); + config->a2dp_sources = pa_hashmap_new_full(int_hash_func, int_compare_func, pa_xfree, pa_xfree); + config->active_index_priorities = pa_hashmap_new_full(int_hash_func, int_compare_func, + pa_xfree, pa_xfree); + config->ordered_indices = NULL; + + config->max_priority = PA_A2DP_PRIORITY_MIN - 1; + while (++codec_index < PA_A2DP_SINK_MAX) { + pa_a2dp_codec_index_to_a2dp_codec(codec_index, &a2dp_codec); + if (!a2dp_codec || !a2dp_codec->a2dp_sink || !a2dp_codec->a2dp_sink->decoder_load()) + continue; + ++config->max_priority; + pa_hashmap_put(config->a2dp_sinks, pa_xmemdup(&config->max_priority, sizeof(int)), + pa_xmemdup(&codec_index, sizeof(pa_a2dp_codec_index_t))); + pa_hashmap_put(config->active_index_priorities, pa_xmemdup(&codec_index, sizeof(pa_a2dp_codec_index_t)), + pa_xmemdup(&config->max_priority, sizeof(int))); + a2dp_codec->a2dp_sink->priority = config->max_priority; + } + codec_index = PA_A2DP_SOURCE_MIN; + while (++codec_index < PA_A2DP_SOURCE_MAX) { + pa_a2dp_codec_index_to_a2dp_codec(codec_index, &a2dp_codec); + if (!a2dp_codec || !a2dp_codec->a2dp_source || !a2dp_codec->a2dp_source->encoder_load()) + continue; + ++config->max_priority; + pa_hashmap_put(config->a2dp_sources, pa_xmemdup(&config->max_priority, sizeof(int)), + pa_xmemdup(&codec_index, sizeof(pa_a2dp_codec_index_t))); + pa_hashmap_put(config->active_index_priorities, pa_xmemdup(&codec_index, sizeof(pa_a2dp_codec_index_t)), + pa_xmemdup(&config->max_priority, sizeof(int))); + a2dp_codec->a2dp_source->priority = config->max_priority; + } +}; + +void pa_a2dp_set_max_priority(pa_a2dp_codec_index_t codec_index, pa_a2dp_config_t **a2dp_config) { + const pa_a2dp_codec_t *a2dp_codec; + pa_a2dp_config_t *config = *a2dp_config; + + pa_a2dp_codec_index_to_a2dp_codec(codec_index, &a2dp_codec); + + if (!a2dp_codec || !pa_hashmap_remove(config->active_index_priorities, &codec_index)) { + printf("no entry;"); + pa_log_debug("No such codec: %d", codec_index); + return; + } + + ++config->max_priority; + pa_hashmap_put(config->active_index_priorities, pa_xmemdup(&codec_index, sizeof(pa_a2dp_codec_index_t)), + pa_xmemdup(&config->max_priority, sizeof(int))); + + if (pa_a2dp_codec_index_is_sink(codec_index)) + a2dp_codec->a2dp_sink->priority = config->max_priority; + else + a2dp_codec->a2dp_source->priority = config->max_priority; +}; + +void pa_a2dp_set_disable(pa_a2dp_codec_index_t codec_index, pa_a2dp_config_t **a2dp_config) { + const pa_a2dp_codec_t *a2dp_codec; + pa_a2dp_config_t *config = *a2dp_config; + pa_a2dp_codec_index_to_a2dp_codec(codec_index, &a2dp_codec); + + if (!a2dp_codec || !pa_hashmap_remove(config->active_index_priorities, &codec_index)) { + pa_log_debug("No such codec: %d", codec_index); + return; + } + + if (pa_a2dp_codec_index_is_sink(codec_index)) + a2dp_codec->a2dp_sink->priority = PA_A2DP_PRIORITY_DISABLE; + else + a2dp_codec->a2dp_source->priority = PA_A2DP_PRIORITY_DISABLE; +}; + +void pa_a2dp_free(pa_a2dp_config_t **a2dp_config) { + pa_a2dp_config_t *config = *a2dp_config; + + if (!config) + return; + if (config->ordered_indices) + pa_hashmap_free(config->ordered_indices); + + if (config->active_index_priorities) + pa_hashmap_free(config->active_index_priorities); + + if (config->a2dp_sinks) + pa_hashmap_free(config->a2dp_sinks); + + if (config->a2dp_sources) + pa_hashmap_free(config->a2dp_sources); + + pa_xfree(config); + *a2dp_config = NULL; +} + + +void pa_a2dp_get_sink_indices(pa_hashmap **sink_indices, pa_a2dp_config_t **a2dp_config) { + pa_a2dp_config_t *config = *a2dp_config; + *sink_indices = config->a2dp_sinks; +}; + +void pa_a2dp_get_source_indices(pa_hashmap **source_indices, pa_a2dp_config_t **a2dp_config) { + pa_a2dp_config_t *config = *a2dp_config; + *source_indices = config->a2dp_sources; +}; + +void pa_a2dp_get_ordered_indices(pa_hashmap **ordered_indices, pa_a2dp_config_t **a2dp_config) { + void *state; + pa_a2dp_codec_index_t *index, *indices; + int *priority, i; + pa_a2dp_config_t *config = *a2dp_config; + + indices = pa_xmalloc(sizeof(pa_a2dp_codec_index_t) * (config->max_priority + 1)); + + for (i = 0; i <= config->max_priority; i++) + indices[i] = PA_A2DP_CODEC_INDEX_UNAVAILABLE; + + PA_HASHMAP_FOREACH_KV(index, priority, config->active_index_priorities, state) + { + if (*priority <= 0) + continue; + indices[*priority] = *index; + } + + if (config->ordered_indices) + pa_hashmap_free(config->ordered_indices); + config->ordered_indices = pa_hashmap_new_full(int_hash_func, int_compare_func, pa_xfree, pa_xfree); + + for (i = config->max_priority; i >= PA_A2DP_PRIORITY_MIN; i--) { + if (indices[i] == PA_A2DP_CODEC_INDEX_UNAVAILABLE) + continue; + priority = pa_xmemdup(&i, sizeof(int)); + index = pa_xmemdup(indices + i, sizeof(pa_a2dp_codec_index_t)); + pa_hashmap_put(config->ordered_indices, priority, index); + } + + *ordered_indices = config->ordered_indices; +}; + + +void pa_a2dp_codec_index_to_endpoint(pa_a2dp_codec_index_t codec_index, const char **endpoint) { + switch (codec_index) { + case PA_A2DP_SINK_SBC: + *endpoint = A2DP_SBC_SNK_ENDPOINT; + break; + case PA_A2DP_SOURCE_SBC: + *endpoint = A2DP_SBC_SRC_ENDPOINT; + break; + default: + *endpoint = NULL; + } +}; + +void pa_a2dp_endpoint_to_codec_index(const char *endpoint, pa_a2dp_codec_index_t *codec_index) { + if (streq(endpoint, A2DP_SBC_SNK_ENDPOINT)) + *codec_index = PA_A2DP_SINK_SBC; + else if (streq(endpoint, A2DP_SBC_SRC_ENDPOINT)) + *codec_index = PA_A2DP_SOURCE_SBC; + else + *codec_index = PA_A2DP_CODEC_INDEX_UNAVAILABLE; +}; + +void pa_a2dp_codec_index_to_a2dp_codec(pa_a2dp_codec_index_t codec_index, const pa_a2dp_codec_t **a2dp_codec) { + switch (codec_index) { + case PA_A2DP_SINK_SBC: + case PA_A2DP_SOURCE_SBC: + *a2dp_codec = &pa_a2dp_sbc; + break; + default: + *a2dp_codec = NULL; + } +}; + +void pa_a2dp_a2dp_codec_to_codec_index(const pa_a2dp_codec_t *a2dp_codec, bool is_a2dp_sink, + pa_a2dp_codec_index_t *codec_index) { + if (!a2dp_codec) { + *codec_index = PA_A2DP_CODEC_INDEX_UNAVAILABLE; + return; + } + switch (a2dp_codec->codec) { + case A2DP_CODEC_SBC: + *codec_index = is_a2dp_sink ? PA_A2DP_SINK_SBC : PA_A2DP_SOURCE_SBC; + return; + case A2DP_CODEC_VENDOR: + if (!a2dp_codec->vendor_codec) { + *codec_index = PA_A2DP_CODEC_INDEX_UNAVAILABLE; + return; + } + *codec_index = PA_A2DP_CODEC_INDEX_UNAVAILABLE; + break; + default: + *codec_index = PA_A2DP_CODEC_INDEX_UNAVAILABLE; + } +}; + +void +pa_a2dp_get_a2dp_codec(uint8_t codec, const a2dp_vendor_codec_t *vendor_codec, const pa_a2dp_codec_t **a2dp_codec) { + switch (codec) { + case A2DP_CODEC_SBC: + *a2dp_codec = &pa_a2dp_sbc; + return; + case A2DP_CODEC_VENDOR: + if (!vendor_codec) { + *a2dp_codec = NULL; + pa_assert_not_reached(); + } + *a2dp_codec = NULL; + break; + default: + *a2dp_codec = NULL; + } +}; + +bool pa_a2dp_codec_index_is_sink(pa_a2dp_codec_index_t codec_index) { + if (codec_index > PA_A2DP_SINK_MIN && codec_index < PA_A2DP_SINK_MAX) + return true; + return false; +}; + +bool pa_a2dp_codec_index_is_source(pa_a2dp_codec_index_t codec_index) { + if (codec_index > PA_A2DP_SOURCE_MIN && codec_index < PA_A2DP_SOURCE_MAX) + return true; + return false; +}; + +bool +pa_a2dp_select_cap_frequency(uint32_t freq_cap, pa_sample_spec default_sample_spec, + const pa_a2dp_freq_cap_t *freq_cap_table, + size_t n, pa_a2dp_freq_cap_t *result) { + int i; + /* Find the lowest freq that is at least as high as the requested sampling rate */ + for (i = 0; (unsigned) i < n; i++) + if (freq_cap_table[i].rate >= default_sample_spec.rate && (freq_cap & freq_cap_table[i].cap)) { + *result = freq_cap_table[i]; + break; + } + + if ((unsigned) i == n) { + for (--i; i >= 0; i--) { + if (freq_cap & freq_cap_table[i].cap) { + *result = freq_cap_table[i]; + break; + } + } + + if (i < 0) { + pa_log_error("Not suitable sample rate"); + return false; + } + } + pa_assert((unsigned) i < n); + return true; +}; diff --git a/src/modules/bluetooth/rtp.h b/src/modules/bluetooth/a2dp/rtp.h similarity index 100% rename from src/modules/bluetooth/rtp.h rename to src/modules/bluetooth/a2dp/rtp.h diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c index 2d8337317..202652712 100644 --- a/src/modules/bluetooth/bluez5-util.c +++ b/src/modules/bluetooth/bluez5-util.c @@ -33,7 +33,8 @@ #include <pulsecore/refcnt.h> #include <pulsecore/shared.h> -#include "a2dp-codecs.h" +#include "a2dp/a2dp-api.h" + #include "bluez5-util.h" @@ -48,8 +49,6 @@ #define BLUEZ_ERROR_NOT_SUPPORTED "org.bluez.Error.NotSupported" -#define A2DP_SOURCE_ENDPOINT "/MediaEndpoint/A2DPSource" -#define A2DP_SINK_ENDPOINT "/MediaEndpoint/A2DPSink" #define ENDPOINT_INTROSPECT_XML \ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ @@ -89,6 +88,8 @@ struct pa_bluetooth_discovery { pa_hashmap *devices; pa_hashmap *transports; + pa_a2dp_config_t *a2dp_config; + int headset_backend; pa_bluetooth_backend *ofono_backend, *native_backend; PA_LLIST_HEAD(pa_dbus_pending, pending); @@ -888,7 +889,9 @@ finish: static void register_endpoint(pa_bluetooth_discovery *y, const char *path, const char *endpoint, const char *uuid) { DBusMessage *m; DBusMessageIter i, d; - uint8_t codec = 0; + uint8_t codec; + pa_a2dp_codec_index_t index; + const pa_a2dp_codec_t *a2dp_codec; pa_log_debug("Registering %s on adapter %s", endpoint, path); @@ -899,22 +902,23 @@ static void register_endpoint(pa_bluetooth_discovery *y, const char *path, const 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); pa_dbus_append_basic_variant_dict_entry(&d, "UUID", DBUS_TYPE_STRING, &uuid); + + pa_a2dp_endpoint_to_codec_index(endpoint, &index); + pa_a2dp_codec_index_to_a2dp_codec(index, &a2dp_codec); + + if(!a2dp_codec) + return; + + codec = a2dp_codec->codec; + pa_dbus_append_basic_variant_dict_entry(&d, "Codec", DBUS_TYPE_BYTE, &codec); if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SOURCE) || pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SINK)) { - a2dp_sbc_t capabilities; - - capabilities.channel_mode = SBC_CHANNEL_MODE_MONO | SBC_CHANNEL_MODE_DUAL_CHANNEL | SBC_CHANNEL_MODE_STEREO | - SBC_CHANNEL_MODE_JOINT_STEREO; - capabilities.frequency = SBC_SAMPLING_FREQ_16000 | SBC_SAMPLING_FREQ_32000 | SBC_SAMPLING_FREQ_44100 | - SBC_SAMPLING_FREQ_48000; - capabilities.allocation_method = SBC_ALLOCATION_SNR | SBC_ALLOCATION_LOUDNESS; - capabilities.subbands = SBC_SUBBANDS_4 | SBC_SUBBANDS_8; - capabilities.block_length = SBC_BLOCK_LENGTH_4 | SBC_BLOCK_LENGTH_8 | SBC_BLOCK_LENGTH_12 | SBC_BLOCK_LENGTH_16; - capabilities.min_bitpool = MIN_BITPOOL; - capabilities.max_bitpool = MAX_BITPOOL; - - pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, &capabilities, sizeof(capabilities)); + void * capabilities; + size_t capabilities_size = a2dp_codec->get_capabilities(&capabilities); + pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, capabilities, + (unsigned int) capabilities_size); + a2dp_codec->free_capabilities(&capabilities); } dbus_message_iter_close_container(&i, &d); @@ -927,6 +931,9 @@ static void parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessa const char *path; void *state; pa_bluetooth_device *d; + pa_a2dp_codec_index_t *index; + pa_hashmap *indices; + const char *endpoint; pa_assert(dbus_message_iter_get_arg_type(dict_i) == DBUS_TYPE_OBJECT_PATH); dbus_message_iter_get_basic(dict_i, &path); @@ -964,8 +971,13 @@ static void parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessa if (!a->valid) return; - register_endpoint(y, path, A2DP_SOURCE_ENDPOINT, PA_BLUETOOTH_UUID_A2DP_SOURCE); - register_endpoint(y, path, A2DP_SINK_ENDPOINT, PA_BLUETOOTH_UUID_A2DP_SINK); + pa_a2dp_get_ordered_indices(&indices, &y->a2dp_config); + PA_HASHMAP_FOREACH(index, indices, state) { + pa_a2dp_codec_index_to_endpoint(*index, &endpoint); + register_endpoint(y, path, endpoint, + pa_a2dp_codec_index_is_sink(*index) ? PA_BLUETOOTH_UUID_A2DP_SINK + : PA_BLUETOOTH_UUID_A2DP_SOURCE); + } } else if (pa_streq(interface, BLUEZ_DEVICE_INTERFACE)) { @@ -1257,47 +1269,6 @@ fail: return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } -static uint8_t a2dp_default_bitpool(uint8_t freq, uint8_t mode) { - /* These bitpool values were chosen based on the A2DP spec recommendation */ - switch (freq) { - case SBC_SAMPLING_FREQ_16000: - case SBC_SAMPLING_FREQ_32000: - return 53; - - case SBC_SAMPLING_FREQ_44100: - - switch (mode) { - case SBC_CHANNEL_MODE_MONO: - case SBC_CHANNEL_MODE_DUAL_CHANNEL: - return 31; - - case SBC_CHANNEL_MODE_STEREO: - case SBC_CHANNEL_MODE_JOINT_STEREO: - return 53; - } - - pa_log_warn("Invalid channel mode %u", mode); - return 53; - - case SBC_SAMPLING_FREQ_48000: - - switch (mode) { - case SBC_CHANNEL_MODE_MONO: - case SBC_CHANNEL_MODE_DUAL_CHANNEL: - return 29; - - case SBC_CHANNEL_MODE_STEREO: - case SBC_CHANNEL_MODE_JOINT_STEREO: - return 51; - } - - pa_log_warn("Invalid channel mode %u", mode); - return 51; - } - - pa_log_warn("Invalid sampling freq %u", freq); - return 53; -} const char *pa_bluetooth_profile_to_string(pa_bluetooth_profile_t profile) { switch(profile) { @@ -1326,6 +1297,18 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage pa_bluetooth_profile_t p = PA_BLUETOOTH_PROFILE_OFF; DBusMessageIter args, props; DBusMessage *r; + pa_a2dp_codec_index_t index; + const pa_a2dp_codec_t * a2dp_codec; + pa_a2dp_sink_t *a2dp_sink = NULL; + pa_a2dp_source_t *a2dp_source = NULL; + + endpoint_path = dbus_message_get_path(m); + + pa_a2dp_endpoint_to_codec_index(endpoint_path, &index); + pa_a2dp_codec_index_to_a2dp_codec(index, &a2dp_codec); + + if(!a2dp_codec) + goto fail2; if (!dbus_message_iter_init(m, &args) || !pa_streq(dbus_message_get_signature(m), "oa{sv}")) { pa_log_error("Invalid signature for method SetConfiguration()"); @@ -1367,13 +1350,14 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage dbus_message_iter_get_basic(&value, &uuid); - endpoint_path = dbus_message_get_path(m); - if (pa_streq(endpoint_path, A2DP_SOURCE_ENDPOINT)) { + if (pa_a2dp_codec_index_is_source(index)) { if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SOURCE)) p = PA_BLUETOOTH_PROFILE_A2DP_SINK; - } else if (pa_streq(endpoint_path, A2DP_SINK_ENDPOINT)) { + a2dp_source = a2dp_codec->a2dp_source; + } else if (pa_a2dp_codec_index_is_sink(index)) { if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SINK)) p = PA_BLUETOOTH_PROFILE_A2DP_SOURCE; + a2dp_sink = a2dp_codec->a2dp_sink; } if (p == PA_BLUETOOTH_PROFILE_OFF) { @@ -1389,7 +1373,6 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage dbus_message_iter_get_basic(&value, &dev_path); } else if (pa_streq(key, "Configuration")) { DBusMessageIter array; - a2dp_sbc_t *c; if (var != DBUS_TYPE_ARRAY) { pa_log_error("Property %s of wrong type %c", key, (char)var); @@ -1404,40 +1387,9 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage } dbus_message_iter_get_fixed_array(&array, &config, &size); - if (size != sizeof(a2dp_sbc_t)) { - pa_log_error("Configuration array of invalid size"); - goto fail; - } - c = (a2dp_sbc_t *) config; - - if (c->frequency != SBC_SAMPLING_FREQ_16000 && c->frequency != SBC_SAMPLING_FREQ_32000 && - c->frequency != SBC_SAMPLING_FREQ_44100 && c->frequency != SBC_SAMPLING_FREQ_48000) { - pa_log_error("Invalid sampling frequency in configuration"); + if(!a2dp_codec->set_configuration(config, (const size_t) size)) goto fail; - } - - if (c->channel_mode != SBC_CHANNEL_MODE_MONO && c->channel_mode != SBC_CHANNEL_MODE_DUAL_CHANNEL && - c->channel_mode != SBC_CHANNEL_MODE_STEREO && c->channel_mode != SBC_CHANNEL_MODE_JOINT_STEREO) { - pa_log_error("Invalid channel mode in configuration"); - goto fail; - } - - if (c->allocation_method != SBC_ALLOCATION_SNR && c->allocation_method != SBC_ALLOCATION_LOUDNESS) { - pa_log_error("Invalid allocation method in configuration"); - goto fail; - } - - if (c->subbands != SBC_SUBBANDS_4 && c->subbands != SBC_SUBBANDS_8) { - pa_log_error("Invalid SBC subbands in configuration"); - goto fail; - } - - if (c->block_length != SBC_BLOCK_LENGTH_4 && c->block_length != SBC_BLOCK_LENGTH_8 && - c->block_length != SBC_BLOCK_LENGTH_12 && c->block_length != SBC_BLOCK_LENGTH_16) { - pa_log_error("Invalid block length in configuration"); - goto fail; - } } dbus_message_iter_next(&props); @@ -1459,6 +1411,11 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage goto fail2; } + if(!a2dp_sink && !a2dp_source){ + pa_log_error("No a2dp_sink or a2dp_source available for endpoint %s", endpoint_path); + goto fail2; + } + sender = dbus_message_get_sender(m); pa_assert_se(r = dbus_message_new_method_return(m)); @@ -1468,6 +1425,10 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage t = pa_bluetooth_transport_new(d, sender, path, p, config, size); t->acquire = bluez5_transport_acquire_cb; t->release = bluez5_transport_release_cb; + t->codec = a2dp_codec->codec; + t->a2dp_codec = a2dp_codec; + t->a2dp_sink = a2dp_sink; + t->a2dp_source = a2dp_source; pa_bluetooth_transport_put(t); pa_log_debug("Transport %s available for profile %s", t->path, pa_bluetooth_profile_to_string(t->profile)); @@ -1484,22 +1445,15 @@ fail2: static DBusMessage *endpoint_select_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) { pa_bluetooth_discovery *y = userdata; - a2dp_sbc_t *cap, config; - uint8_t *pconf = (uint8_t *) &config; - int i, size; + void *cap, *pconf; + int size,config_size; DBusMessage *r; DBusError err; + pa_a2dp_codec_index_t index; + const pa_a2dp_codec_t *a2dp_codec; + const char * endpoint = dbus_message_get_path(m); - static const struct { - uint32_t rate; - uint8_t cap; - } freq_table[] = { - { 16000U, SBC_SAMPLING_FREQ_16000 }, - { 32000U, SBC_SAMPLING_FREQ_32000 }, - { 44100U, SBC_SAMPLING_FREQ_44100 }, - { 48000U, SBC_SAMPLING_FREQ_48000 } - }; - + pa_log_debug("selecing configuration"); dbus_error_init(&err); if (!dbus_message_get_args(m, &err, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &cap, &size, DBUS_TYPE_INVALID)) { @@ -1508,102 +1462,22 @@ static DBusMessage *endpoint_select_configuration(DBusConnection *conn, DBusMess goto fail; } - if (size != sizeof(config)) { - pa_log_error("Capabilities array has invalid size"); - goto fail; - } - - pa_zero(config); - - /* 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 >= y->core->default_sample_spec.rate && (cap->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 (cap->frequency & freq_table[i].cap) { - config.frequency = freq_table[i].cap; - break; - } - } - - if (i < 0) { - pa_log_error("Not suitable sample rate"); - goto fail; - } - } - - pa_assert((unsigned) i < PA_ELEMENTSOF(freq_table)); - - if (y->core->default_sample_spec.channels <= 1) { - if (cap->channel_mode & SBC_CHANNEL_MODE_MONO) - config.channel_mode = SBC_CHANNEL_MODE_MONO; - else if (cap->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO) - config.channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO; - else if (cap->channel_mode & SBC_CHANNEL_MODE_STEREO) - config.channel_mode = SBC_CHANNEL_MODE_STEREO; - else if (cap->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL) - config.channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL; - else { - pa_log_error("No supported channel modes"); - goto fail; - } - } - - if (y->core->default_sample_spec.channels >= 2) { - if (cap->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO) - config.channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO; - else if (cap->channel_mode & SBC_CHANNEL_MODE_STEREO) - config.channel_mode = SBC_CHANNEL_MODE_STEREO; - else if (cap->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL) - config.channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL; - else if (cap->channel_mode & SBC_CHANNEL_MODE_MONO) - config.channel_mode = SBC_CHANNEL_MODE_MONO; - else { - pa_log_error("No supported channel modes"); - goto fail; - } - } + pa_a2dp_endpoint_to_codec_index(endpoint, &index); + pa_a2dp_codec_index_to_a2dp_codec(index, &a2dp_codec); - if (cap->block_length & SBC_BLOCK_LENGTH_16) - config.block_length = SBC_BLOCK_LENGTH_16; - else if (cap->block_length & SBC_BLOCK_LENGTH_12) - config.block_length = SBC_BLOCK_LENGTH_12; - else if (cap->block_length & SBC_BLOCK_LENGTH_8) - config.block_length = SBC_BLOCK_LENGTH_8; - else if (cap->block_length & SBC_BLOCK_LENGTH_4) - config.block_length = SBC_BLOCK_LENGTH_4; - else { - pa_log_error("No supported block lengths"); + if(!a2dp_codec) goto fail; - } - if (cap->subbands & SBC_SUBBANDS_8) - config.subbands = SBC_SUBBANDS_8; - else if (cap->subbands & SBC_SUBBANDS_4) - config.subbands = SBC_SUBBANDS_4; - else { - pa_log_error("No supported subbands"); + config_size = (int) a2dp_codec->select_configuration(y->core->default_sample_spec, cap, (const size_t) size, &pconf); + if (size != config_size) { + pa_log_error("Capabilities array has invalid size %d, %d",size, config_size); goto fail; } - if (cap->allocation_method & SBC_ALLOCATION_LOUDNESS) - config.allocation_method = SBC_ALLOCATION_LOUDNESS; - else if (cap->allocation_method & SBC_ALLOCATION_SNR) - config.allocation_method = SBC_ALLOCATION_SNR; - - config.min_bitpool = (uint8_t) PA_MAX(MIN_BITPOOL, cap->min_bitpool); - config.max_bitpool = (uint8_t) PA_MIN(a2dp_default_bitpool(config.frequency, config.channel_mode), cap->max_bitpool); - - if (config.min_bitpool > config.max_bitpool) - goto fail; - pa_assert_se(r = dbus_message_new_method_return(m)); pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &pconf, size, DBUS_TYPE_INVALID)); + a2dp_codec->free_configuration(&pconf); return r; fail: @@ -1668,6 +1542,7 @@ static DBusHandlerResult endpoint_handler(DBusConnection *c, DBusMessage *m, voi struct pa_bluetooth_discovery *y = userdata; DBusMessage *r = NULL; const char *path, *interface, *member; + pa_a2dp_codec_index_t index; pa_assert(y); @@ -1677,7 +1552,8 @@ static DBusHandlerResult endpoint_handler(DBusConnection *c, DBusMessage *m, voi pa_log_debug("dbus: path=%s, interface=%s, member=%s", path, interface, member); - if (!pa_streq(path, A2DP_SOURCE_ENDPOINT) && !pa_streq(path, A2DP_SINK_ENDPOINT)) + pa_a2dp_endpoint_to_codec_index(path, &index); + if (index == PA_A2DP_CODEC_INDEX_UNAVAILABLE) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) { @@ -1706,6 +1582,10 @@ static DBusHandlerResult endpoint_handler(DBusConnection *c, DBusMessage *m, voi } static void endpoint_init(pa_bluetooth_discovery *y, pa_bluetooth_profile_t profile) { + void *state; + pa_a2dp_codec_index_t *index; + pa_hashmap *indices; + const char *endpoint; static const DBusObjectPathVTable vtable_endpoint = { .message_function = endpoint_handler, }; @@ -1714,33 +1594,49 @@ static void endpoint_init(pa_bluetooth_discovery *y, pa_bluetooth_profile_t prof switch(profile) { case PA_BLUETOOTH_PROFILE_A2DP_SINK: - pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_ENDPOINT, - &vtable_endpoint, y)); + pa_a2dp_get_source_indices(&indices, &y->a2dp_config); break; case PA_BLUETOOTH_PROFILE_A2DP_SOURCE: - pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), A2DP_SINK_ENDPOINT, - &vtable_endpoint, y)); + pa_a2dp_get_sink_indices(&indices, &y->a2dp_config); break; default: pa_assert_not_reached(); break; } + + PA_HASHMAP_FOREACH(index, indices, state){ + pa_a2dp_codec_index_to_endpoint(*index, &endpoint); + if(!endpoint) + continue; + pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), endpoint, + &vtable_endpoint, y)); + } } static void endpoint_done(pa_bluetooth_discovery *y, pa_bluetooth_profile_t profile) { + void *state; + pa_a2dp_codec_index_t *index; + pa_hashmap *indices; + const char *endpoint; + pa_assert(y); switch(profile) { case PA_BLUETOOTH_PROFILE_A2DP_SINK: - dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_ENDPOINT); + pa_a2dp_get_source_indices(&indices, &y->a2dp_config); break; case PA_BLUETOOTH_PROFILE_A2DP_SOURCE: - dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SINK_ENDPOINT); + pa_a2dp_get_sink_indices(&indices, &y->a2dp_config); break; default: pa_assert_not_reached(); break; } + + PA_HASHMAP_FOREACH(index,indices,state){ + pa_a2dp_codec_index_to_endpoint(*index, &endpoint); + dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), endpoint); + } } pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c, int headset_backend) { @@ -1760,6 +1656,8 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c, int headset_backe y->transports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); PA_LLIST_HEAD_INIT(pa_dbus_pending, y->pending); + pa_a2dp_init(&y->a2dp_config); + for (i = 0; i < PA_BLUETOOTH_HOOK_MAX; i++) pa_hook_init(&y->hooks[i], y); @@ -1871,6 +1769,9 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) { endpoint_done(y, PA_BLUETOOTH_PROFILE_A2DP_SINK); endpoint_done(y, PA_BLUETOOTH_PROFILE_A2DP_SOURCE); + if(y->a2dp_config) + pa_a2dp_free(&y->a2dp_config); + pa_dbus_connection_unref(y->connection); } diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h index ad30708f0..c3be423e8 100644 --- a/src/modules/bluetooth/bluez5-util.h +++ b/src/modules/bluetooth/bluez5-util.h @@ -22,6 +22,8 @@ #include <pulsecore/core.h> +#include "a2dp/a2dp-api.h" + #define PA_BLUETOOTH_UUID_A2DP_SOURCE "0000110a-0000-1000-8000-00805f9b34fb" #define PA_BLUETOOTH_UUID_A2DP_SINK "0000110b-0000-1000-8000-00805f9b34fb" @@ -78,6 +80,10 @@ struct pa_bluetooth_transport { char *path; pa_bluetooth_profile_t profile; + const pa_a2dp_codec_t *a2dp_codec; + pa_a2dp_sink_t *a2dp_sink; + pa_a2dp_source_t *a2dp_source; + uint8_t codec; uint8_t *config; size_t config_size; diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c index d983efdec..0fcde4941 100644 --- a/src/modules/bluetooth/module-bluez5-device.c +++ b/src/modules/bluetooth/module-bluez5-device.c @@ -46,9 +46,8 @@ #include <pulsecore/thread-mq.h> #include <pulsecore/time-smoother.h> -#include "a2dp-codecs.h" +#include "a2dp/a2dp-api.h" #include "bluez5-util.h" -#include "rtp.h" PA_MODULE_AUTHOR("Jo茫o Paulo Rechi Vita"); PA_MODULE_DESCRIPTION("BlueZ 5 Bluetooth audio sink and source"); @@ -62,13 +61,12 @@ PA_MODULE_USAGE("path=<device object path>" #define FIXED_LATENCY_RECORD_A2DP (25 * PA_USEC_PER_MSEC) #define FIXED_LATENCY_RECORD_SCO (25 * PA_USEC_PER_MSEC) -#define BITPOOL_DEC_LIMIT 32 -#define BITPOOL_DEC_STEP 5 #define HSP_MAX_GAIN 15 static const char* const valid_modargs[] = { "path", "autodetect_mtu", + "a2dp_config", NULL }; @@ -94,17 +92,13 @@ typedef struct bluetooth_msg { PA_DEFINE_PRIVATE_CLASS(bluetooth_msg, pa_msgobject); #define BLUETOOTH_MSG(o) (bluetooth_msg_cast(o)) -typedef struct sbc_info { - sbc_t sbc; /* Codec data */ - bool sbc_initialized; /* Keep track if the encoder is initialized */ - size_t codesize, frame_length; /* SBC Codesize, frame_length. We simply cache those values here */ - uint16_t seq_num; /* Cumulative packet sequence */ - uint8_t min_bitpool; - uint8_t max_bitpool; - - void* buffer; /* Codec transfer buffer */ - size_t buffer_size; /* Size of the buffer */ -} sbc_info_t; +typedef struct pa_a2dp_info { + pa_proplist *a2dp_config; + void *a2dp_sink_data; + void *a2dp_source_data; + void *buffer; + size_t buffer_size; +} pa_a2dp_info_t; struct userdata { pa_module *module; @@ -146,7 +140,7 @@ struct userdata { pa_smoother *read_smoother; pa_memchunk write_memchunk; pa_sample_spec sample_spec; - struct sbc_info sbc_info; + pa_a2dp_info_t a2dp_info; }; typedef enum pa_bluetooth_form_factor { @@ -418,105 +412,55 @@ static void a2dp_prepare_buffer(struct userdata *u) { pa_assert(u); - if (u->sbc_info.buffer_size >= min_buffer_size) + if (u->a2dp_info.buffer_size >= min_buffer_size) return; - u->sbc_info.buffer_size = 2 * min_buffer_size; - pa_xfree(u->sbc_info.buffer); - u->sbc_info.buffer = pa_xmalloc(u->sbc_info.buffer_size); + u->a2dp_info.buffer_size = 2 * min_buffer_size; + pa_xfree(u->a2dp_info.buffer); + u->a2dp_info.buffer = pa_xmalloc(u->a2dp_info.buffer_size); +} + +static void a2dp_encoder_buffer_read_cb(const void **read_buf, size_t read_buf_size, void *userdata) { + struct userdata *u = (struct userdata *) userdata; + if (!u->write_memchunk.memblock) + pa_sink_render_full(u->sink, read_buf_size, &u->write_memchunk); + pa_assert(u->write_memchunk.length == read_buf_size); + *read_buf = (const uint8_t *) pa_memblock_acquire_chunk(&u->write_memchunk); +} + +static void a2dp_encoder_buffer_free_cb(const void **read_buf, void *userdata) { + struct userdata *u = (struct userdata *) userdata; + if (!read_buf) + return;; + pa_memblock_release(u->write_memchunk.memblock); + pa_memblock_unref(u->write_memchunk.memblock); + pa_memchunk_reset(&u->write_memchunk); + *read_buf = NULL; } /* Run from IO thread */ static int a2dp_process_render(struct userdata *u) { - struct sbc_info *sbc_info; - struct rtp_header *header; - struct rtp_payload *payload; - size_t nbytes; - void *d; - const void *p; - size_t to_write, to_encode; - unsigned frame_count; + size_t nbytes, encoded; int ret = 0; pa_assert(u); pa_assert(u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK); pa_assert(u->sink); - - /* First, render some data */ - if (!u->write_memchunk.memblock) - pa_sink_render_full(u->sink, u->write_block_size, &u->write_memchunk); - - pa_assert(u->write_memchunk.length == u->write_block_size); + pa_assert(u->transport); + pa_assert(u->transport->a2dp_source); a2dp_prepare_buffer(u); - sbc_info = &u->sbc_info; - header = sbc_info->buffer; - payload = (struct rtp_payload*) ((uint8_t*) sbc_info->buffer + sizeof(*header)); - - frame_count = 0; - - /* Try to create a packet of the full MTU */ - - p = (const uint8_t *) pa_memblock_acquire_chunk(&u->write_memchunk); - to_encode = u->write_memchunk.length; - - d = (uint8_t*) sbc_info->buffer + sizeof(*header) + sizeof(*payload); - to_write = sbc_info->buffer_size - sizeof(*header) - sizeof(*payload); - - while (PA_LIKELY(to_encode > 0 && to_write > 0)) { - ssize_t written; - ssize_t encoded; - - encoded = sbc_encode(&sbc_info->sbc, - p, to_encode, - d, to_write, - &written); - - if (PA_UNLIKELY(encoded <= 0)) { - pa_log_error("SBC encoding error (%li)", (long) encoded); - pa_memblock_release(u->write_memchunk.memblock); - return -1; - } - - pa_assert_fp((size_t) encoded <= to_encode); - pa_assert_fp((size_t) encoded == sbc_info->codesize); - - pa_assert_fp((size_t) written <= to_write); - pa_assert_fp((size_t) written == sbc_info->frame_length); - - p = (const uint8_t*) p + encoded; - to_encode -= encoded; - - d = (uint8_t*) d + written; - to_write -= written; - - frame_count++; - } - - pa_memblock_release(u->write_memchunk.memblock); - - pa_assert(to_encode == 0); - - PA_ONCE_BEGIN { - pa_log_debug("Using SBC encoder implementation: %s", pa_strnull(sbc_get_implementation_info(&sbc_info->sbc))); - } PA_ONCE_END; - - /* write it to the fifo */ - memset(sbc_info->buffer, 0, sizeof(*header) + sizeof(*payload)); - header->v = 2; - header->pt = 1; - header->sequence_number = htons(sbc_info->seq_num++); - header->timestamp = htonl(u->write_index / pa_frame_size(&u->sample_spec)); - header->ssrc = htonl(1); - payload->frame_count = frame_count; - - nbytes = (uint8_t*) d - (uint8_t*) sbc_info->buffer; + nbytes = u->transport->a2dp_source->encode((uint32_t) (u->write_index / pa_frame_size(&u->sample_spec)), + u->a2dp_info.buffer, u->a2dp_info.buffer_size, + &encoded, u, &u->a2dp_info.a2dp_source_data); + if(nbytes == 0) + return -1; for (;;) { ssize_t l; - l = pa_write(u->stream_fd, sbc_info->buffer, nbytes, &u->stream_write_type); + l = pa_write(u->stream_fd, u->a2dp_info.buffer, nbytes, &u->stream_write_type); pa_assert(l != 0); @@ -547,9 +491,7 @@ static int a2dp_process_render(struct userdata *u) { break; } - u->write_index += (uint64_t) u->write_memchunk.length; - pa_memblock_unref(u->write_memchunk.memblock); - pa_memchunk_reset(&u->write_memchunk); + u->write_index += encoded; ret = 1; @@ -568,6 +510,8 @@ static int a2dp_process_push(struct userdata *u) { pa_assert(u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE); pa_assert(u->source); pa_assert(u->read_smoother); + pa_assert(u->transport); + pa_assert(u->transport->a2dp_sink); memchunk.memblock = pa_memblock_new(u->core->mempool, u->read_block_size); memchunk.index = memchunk.length = 0; @@ -575,22 +519,18 @@ static int a2dp_process_push(struct userdata *u) { for (;;) { bool found_tstamp = false; pa_usec_t tstamp; - struct sbc_info *sbc_info; - struct rtp_header *header; - struct rtp_payload *payload; - const void *p; + pa_a2dp_info_t *a2dp_info; void *d; ssize_t l; - size_t to_write, to_decode; + size_t decoded; size_t total_written = 0; + uint32_t timestamp; a2dp_prepare_buffer(u); - sbc_info = &u->sbc_info; - header = sbc_info->buffer; - payload = (struct rtp_payload*) ((uint8_t*) sbc_info->buffer + sizeof(*header)); + a2dp_info = &u->a2dp_info; - l = pa_read(u->stream_fd, sbc_info->buffer, sbc_info->buffer_size, &u->stream_write_type); + l = pa_read(u->stream_fd, a2dp_info->buffer, a2dp_info->buffer_size, &u->stream_write_type); if (l <= 0) { @@ -607,7 +547,7 @@ static int a2dp_process_push(struct userdata *u) { break; } - pa_assert((size_t) l <= sbc_info->buffer_size); + pa_assert((size_t) l <= a2dp_info->buffer_size); /* TODO: get timestamp from rtp */ if (!found_tstamp) { @@ -615,50 +555,22 @@ static int a2dp_process_push(struct userdata *u) { tstamp = pa_rtclock_now(); } - p = (uint8_t*) sbc_info->buffer + sizeof(*header) + sizeof(*payload); - to_decode = l - sizeof(*header) - sizeof(*payload); - d = pa_memblock_acquire(memchunk.memblock); - to_write = memchunk.length = pa_memblock_get_length(memchunk.memblock); - - while (PA_LIKELY(to_decode > 0)) { - size_t written; - ssize_t decoded; - - decoded = sbc_decode(&sbc_info->sbc, - p, to_decode, - d, to_write, - &written); - - if (PA_UNLIKELY(decoded <= 0)) { - pa_log_error("SBC decoding error (%li)", (long) decoded); - pa_memblock_release(memchunk.memblock); - pa_memblock_unref(memchunk.memblock); - return 0; - } + memchunk.length = pa_memblock_get_length(memchunk.memblock); - total_written += written; - - /* Reset frame length, it can be changed due to bitpool change */ - sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc); - - pa_assert_fp((size_t) decoded <= to_decode); - pa_assert_fp((size_t) decoded == sbc_info->frame_length); - - pa_assert_fp((size_t) written == sbc_info->codesize); - - p = (const uint8_t*) p + decoded; - to_decode -= decoded; - - d = (uint8_t*) d + written; - to_write -= written; + total_written = u->transport->a2dp_sink->decode(a2dp_info->buffer, (size_t) l, d, memchunk.length, &decoded, + ×tamp, &a2dp_info->a2dp_sink_data); + if(total_written == 0){ + pa_memblock_release(memchunk.memblock); + pa_memblock_unref(memchunk.memblock); + return 0; } u->read_index += (uint64_t) total_written; pa_smoother_put(u->read_smoother, tstamp, pa_bytes_to_usec(u->read_index, &u->sample_spec)); pa_smoother_resume(u->read_smoother, tstamp, true); - memchunk.length -= to_write; + memchunk.length = total_written; pa_memblock_release(memchunk.memblock); @@ -705,40 +617,10 @@ static void update_buffer_size(struct userdata *u) { } } -/* Run from I/O thread */ -static void a2dp_set_bitpool(struct userdata *u, uint8_t bitpool) { - struct sbc_info *sbc_info; - - pa_assert(u); - - sbc_info = &u->sbc_info; - - if (sbc_info->sbc.bitpool == bitpool) - return; - - if (bitpool > sbc_info->max_bitpool) - bitpool = sbc_info->max_bitpool; - else if (bitpool < sbc_info->min_bitpool) - bitpool = sbc_info->min_bitpool; - - sbc_info->sbc.bitpool = bitpool; - - sbc_info->codesize = sbc_get_codesize(&sbc_info->sbc); - sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc); - - pa_log_debug("Bitpool has changed to %u", sbc_info->sbc.bitpool); - - u->read_block_size = - (u->read_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload)) - / sbc_info->frame_length * sbc_info->codesize; - - u->write_block_size = - (u->write_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload)) - / sbc_info->frame_length * sbc_info->codesize; - +static void a2dp_set_sink(struct userdata *u){ pa_sink_set_max_request_within_thread(u->sink, u->write_block_size); pa_sink_set_fixed_latency_within_thread(u->sink, - FIXED_LATENCY_PLAYBACK_A2DP + pa_bytes_to_usec(u->write_block_size, &u->sample_spec)); + FIXED_LATENCY_PLAYBACK_A2DP + pa_bytes_to_usec(u->write_block_size, &u->sample_spec)); /* If there is still data in the memchunk, we have to discard it * because the write_block_size may have changed. */ @@ -750,27 +632,6 @@ static void a2dp_set_bitpool(struct userdata *u, uint8_t bitpool) { update_buffer_size(u); } -/* Run from I/O thread */ -static void a2dp_reduce_bitpool(struct userdata *u) { - struct sbc_info *sbc_info; - uint8_t bitpool; - - pa_assert(u); - - sbc_info = &u->sbc_info; - - /* Check if bitpool is already at its limit */ - if (sbc_info->sbc.bitpool <= BITPOOL_DEC_LIMIT) - return; - - bitpool = sbc_info->sbc.bitpool - BITPOOL_DEC_STEP; - - if (bitpool < BITPOOL_DEC_LIMIT) - bitpool = BITPOOL_DEC_LIMIT; - - a2dp_set_bitpool(u, bitpool); -} - static void teardown_stream(struct userdata *u) { if (u->rtpoll_item) { pa_rtpoll_item_free(u->rtpoll_item); @@ -860,13 +721,15 @@ static void transport_config_mtu(struct userdata *u) { u->write_block_size = pa_frame_align(u->write_block_size, &u->sink->sample_spec); } } else { - u->read_block_size = - (u->read_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload)) - / u->sbc_info.frame_length * u->sbc_info.codesize; - - u->write_block_size = - (u->write_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload)) - / u->sbc_info.frame_length * u->sbc_info.codesize; + u->read_block_size = u->read_link_mtu; + u->write_block_size = u->write_link_mtu; + if (u->transport->a2dp_sink) + u->transport->a2dp_sink->get_block_size(u->read_link_mtu, &u->read_block_size, &u->a2dp_info.a2dp_sink_data); + else if (u->transport->a2dp_source) + u->transport->a2dp_source->get_block_size(u->write_link_mtu, &u->write_block_size, + &u->a2dp_info.a2dp_source_data); + else + pa_assert_not_reached(); } if (u->sink) { @@ -906,9 +769,19 @@ static void setup_stream(struct userdata *u) { pa_log_debug("Stream properly set up, we're ready to roll!"); - if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK) { - a2dp_set_bitpool(u, u->sbc_info.max_bitpool); - update_buffer_size(u); + if (u->transport->a2dp_sink) { + u->transport->a2dp_sink->setup_stream(&u->a2dp_info.a2dp_sink_data); + u->transport->a2dp_sink->get_block_size(u->read_link_mtu, &u->read_block_size, + &u->a2dp_info.a2dp_sink_data); + pa_proplist_sets(u->source->proplist, "bluetooth.a2dp_codec", u->transport->a2dp_codec->name); + + } else if (u->transport->a2dp_source) { + u->transport->a2dp_source->setup_stream(&u->a2dp_info.a2dp_source_data); + u->transport->a2dp_source->get_block_size(u->write_link_mtu, &u->write_block_size, + &u->a2dp_info.a2dp_source_data); + a2dp_set_sink(u); + pa_proplist_sets(u->sink->proplist, "bluetooth.a2dp_codec", u->transport->a2dp_codec->name); + } u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); @@ -1095,6 +968,8 @@ static int add_source(struct userdata *u) { pa_assert_not_reached(); break; } + else if(u->transport->a2dp_codec && u->transport->a2dp_sink) + pa_proplist_sets(data.proplist, "bluetooth.a2dp_codec", u->transport->a2dp_codec->name); u->source = pa_source_new(u->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY); pa_source_new_data_done(&data); @@ -1270,6 +1145,9 @@ static int add_sink(struct userdata *u) { pa_assert_not_reached(); break; } + else if(u->transport->a2dp_codec && u->transport->a2dp_source) + pa_proplist_sets(data.proplist, "bluetooth.a2dp_codec", u->transport->a2dp_codec->name); + u->sink = pa_sink_new(u->core, &data, PA_SINK_HARDWARE|PA_SINK_LATENCY); pa_sink_new_data_done(&data); @@ -1296,111 +1174,39 @@ static void transport_config(struct userdata *u) { u->sample_spec.channels = 1; u->sample_spec.rate = 8000; } else { - sbc_info_t *sbc_info = &u->sbc_info; - a2dp_sbc_t *config; - + pa_proplist *user_config = NULL; pa_assert(u->transport); - u->sample_spec.format = PA_SAMPLE_S16LE; - config = (a2dp_sbc_t *) u->transport->config; - - if (sbc_info->sbc_initialized) - sbc_reinit(&sbc_info->sbc, 0); - else - sbc_init(&sbc_info->sbc, 0); - sbc_info->sbc_initialized = true; - - switch (config->frequency) { - case SBC_SAMPLING_FREQ_16000: - sbc_info->sbc.frequency = SBC_FREQ_16000; - u->sample_spec.rate = 16000U; - break; - case SBC_SAMPLING_FREQ_32000: - sbc_info->sbc.frequency = SBC_FREQ_32000; - u->sample_spec.rate = 32000U; - break; - case SBC_SAMPLING_FREQ_44100: - sbc_info->sbc.frequency = SBC_FREQ_44100; - u->sample_spec.rate = 44100U; - break; - case SBC_SAMPLING_FREQ_48000: - sbc_info->sbc.frequency = SBC_FREQ_48000; - u->sample_spec.rate = 48000U; - break; - default: - pa_assert_not_reached(); - } - - switch (config->channel_mode) { - case SBC_CHANNEL_MODE_MONO: - sbc_info->sbc.mode = SBC_MODE_MONO; - u->sample_spec.channels = 1; - break; - case SBC_CHANNEL_MODE_DUAL_CHANNEL: - sbc_info->sbc.mode = SBC_MODE_DUAL_CHANNEL; - u->sample_spec.channels = 2; - break; - case SBC_CHANNEL_MODE_STEREO: - sbc_info->sbc.mode = SBC_MODE_STEREO; - u->sample_spec.channels = 2; - break; - case SBC_CHANNEL_MODE_JOINT_STEREO: - sbc_info->sbc.mode = SBC_MODE_JOINT_STEREO; - u->sample_spec.channels = 2; - break; - default: - pa_assert_not_reached(); - } + if (u->a2dp_info.a2dp_config) + user_config = pa_proplist_copy(u->a2dp_info.a2dp_config); - switch (config->allocation_method) { - case SBC_ALLOCATION_SNR: - sbc_info->sbc.allocation = SBC_AM_SNR; - break; - case SBC_ALLOCATION_LOUDNESS: - sbc_info->sbc.allocation = SBC_AM_LOUDNESS; - break; - default: - pa_assert_not_reached(); - } + if (u->transport->a2dp_sink) { + pa_assert_se(u->transport->a2dp_sink->init(&u->a2dp_info.a2dp_sink_data)); - switch (config->subbands) { - case SBC_SUBBANDS_4: - sbc_info->sbc.subbands = SBC_SB_4; - break; - case SBC_SUBBANDS_8: - sbc_info->sbc.subbands = SBC_SB_8; - break; - default: - pa_assert_not_reached(); - } + if (u->transport->a2dp_sink->update_user_config && user_config) + u->transport->a2dp_sink->update_user_config(user_config, + &u->a2dp_info.a2dp_sink_data); - switch (config->block_length) { - case SBC_BLOCK_LENGTH_4: - sbc_info->sbc.blocks = SBC_BLK_4; - break; - case SBC_BLOCK_LENGTH_8: - sbc_info->sbc.blocks = SBC_BLK_8; - break; - case SBC_BLOCK_LENGTH_12: - sbc_info->sbc.blocks = SBC_BLK_12; - break; - case SBC_BLOCK_LENGTH_16: - sbc_info->sbc.blocks = SBC_BLK_16; - break; - default: - pa_assert_not_reached(); - } + u->transport->a2dp_sink->config_transport(u->core->default_sample_spec, u->transport->config, + u->transport->config_size, + &u->sample_spec, &u->a2dp_info.a2dp_sink_data); + } else if (u->transport->a2dp_source) { + pa_assert_se( + u->transport->a2dp_source->init(a2dp_encoder_buffer_read_cb, a2dp_encoder_buffer_free_cb, + &u->a2dp_info.a2dp_source_data)); - sbc_info->min_bitpool = config->min_bitpool; - sbc_info->max_bitpool = config->max_bitpool; + if (u->transport->a2dp_source->update_user_config && user_config) + u->transport->a2dp_source->update_user_config(user_config, + &u->a2dp_info.a2dp_source_data); - /* Set minimum bitpool for source to get the maximum possible block_size */ - sbc_info->sbc.bitpool = u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK ? sbc_info->max_bitpool : sbc_info->min_bitpool; - sbc_info->codesize = sbc_get_codesize(&sbc_info->sbc); - sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc); + u->transport->a2dp_source->config_transport(u->core->default_sample_spec, u->transport->config, + u->transport->config_size, + &u->sample_spec, &u->a2dp_info.a2dp_source_data); + } else + pa_assert_not_reached(); - pa_log_info("SBC parameters: allocation=%u, subbands=%u, blocks=%u, bitpool=%u", - sbc_info->sbc.allocation, sbc_info->sbc.subbands ? 8 : 4, sbc_info->sbc.blocks, sbc_info->sbc.bitpool); + if(user_config) + pa_proplist_free(user_config); } } @@ -1620,6 +1426,11 @@ static void thread_func(void *userdata) { if (audio_sent <= time_passed) { size_t bytes_to_send = pa_usec_to_bytes(time_passed - audio_sent, &u->sample_spec); + if (u->transport->a2dp_source && u->transport->a2dp_source->set_tx_length){ + u->transport->a2dp_source->set_tx_length(bytes_to_send, &u->a2dp_info.a2dp_source_data); + u->transport->a2dp_source->get_block_size(u->write_link_mtu, &u->write_block_size, &u->a2dp_info.a2dp_source_data); + } + /* There are more than two blocks that need to be written. It seems that * the socket has not been accepting data fast enough (could be due to * hiccups in the wireless transmission). We need to discard everything @@ -1651,8 +1462,13 @@ static void thread_func(void *userdata) { skip_bytes -= bytes_to_render; } - if (u->write_index > 0 && u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK) - a2dp_reduce_bitpool(u); + if (u->write_index > 0 && u->transport->a2dp_source && + u->transport->a2dp_source->decrease_quality) { + + u->transport->a2dp_source->decrease_quality(&u->a2dp_info.a2dp_source_data); + u->transport->a2dp_source->get_block_size(u->write_link_mtu, &u->write_block_size, &u->a2dp_info.a2dp_source_data); + } + } blocks_to_write = 1; @@ -1807,6 +1623,14 @@ static void stop_thread(struct userdata *u) { } if (u->transport) { + if(u->transport->a2dp_sink && u->a2dp_info.a2dp_sink_data){ + u->transport->a2dp_sink->free(&u->a2dp_info.a2dp_sink_data); + u->a2dp_info.a2dp_sink_data = NULL; + } + if(u->transport->a2dp_source && u->a2dp_info.a2dp_sink_data){ + u->transport->a2dp_source->free(&u->a2dp_info.a2dp_source_data); + u->a2dp_info.a2dp_source_data = NULL; + } transport_release(u); u->transport = NULL; } @@ -2415,6 +2239,12 @@ int pa__init(pa_module* m) { u->device->autodetect_mtu = autodetect_mtu; + u->a2dp_info.a2dp_config = pa_proplist_new(); + if (pa_modargs_get_proplist(ma, "a2dp_config", u->a2dp_info.a2dp_config, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid proplist key=value pairs for a2dp_config parameter"); + goto fail_free_modargs; + } + pa_modargs_free(ma); u->device_connection_changed_slot = @@ -2492,11 +2322,17 @@ void pa__done(pa_module *m) { if (u->transport_microphone_gain_changed_slot) pa_hook_slot_free(u->transport_microphone_gain_changed_slot); - if (u->sbc_info.buffer) - pa_xfree(u->sbc_info.buffer); + if (u->a2dp_info.buffer) + pa_xfree(u->a2dp_info.buffer); + + if (u->a2dp_info.a2dp_sink_data) + pa_xfree(u->a2dp_info.a2dp_sink_data); + + if (u->a2dp_info.a2dp_source_data) + pa_xfree(u->a2dp_info.a2dp_source_data); - if (u->sbc_info.sbc_initialized) - sbc_finish(&u->sbc_info.sbc); + if (u->a2dp_info.a2dp_config) + pa_proplist_free(u->a2dp_info.a2dp_config); if (u->msg) pa_xfree(u->msg); diff --git a/src/modules/bluetooth/module-bluez5-discover.c b/src/modules/bluetooth/module-bluez5-discover.c index b6c8eb050..b5a813968 100644 --- a/src/modules/bluetooth/module-bluez5-discover.c +++ b/src/modules/bluetooth/module-bluez5-discover.c @@ -42,6 +42,7 @@ PA_MODULE_USAGE( static const char* const valid_modargs[] = { "headset", "autodetect_mtu", + "a2dp_config", NULL }; @@ -52,6 +53,7 @@ struct userdata { pa_hook_slot *device_connection_changed_slot; pa_bluetooth_discovery *discovery; bool autodetect_mtu; + const char *a2dp_config; }; static pa_hook_result_t device_connection_changed_cb(pa_bluetooth_discovery *y, const pa_bluetooth_device *d, struct userdata *u) { @@ -72,7 +74,8 @@ static pa_hook_result_t device_connection_changed_cb(pa_bluetooth_discovery *y, if (!module_loaded && pa_bluetooth_device_any_transport_connected(d)) { /* a new device has been connected */ pa_module *m; - char *args = pa_sprintf_malloc("path=%s autodetect_mtu=%i", d->path, (int)u->autodetect_mtu); + char *args = pa_sprintf_malloc("path=%s autodetect_mtu=%i a2dp_config=\"%s\"", + d->path, (int) u->autodetect_mtu, u->a2dp_config); pa_log_debug("Loading module-bluez5-device %s", args); pa_module_load(&m, u->module->core, "module-bluez5-device", args); @@ -100,7 +103,7 @@ const char *default_headset_backend = "ofono"; int pa__init(pa_module *m) { struct userdata *u; pa_modargs *ma; - const char *headset_str; + const char *headset_str, *a2dp_config; int headset_backend; bool autodetect_mtu; @@ -129,10 +132,14 @@ int pa__init(pa_module *m) { goto fail; } + pa_assert_se(a2dp_config = pa_modargs_get_value(ma, "a2dp_config", "")); + + m->userdata = u = pa_xnew0(struct userdata, 1); u->module = m; u->core = m->core; u->autodetect_mtu = autodetect_mtu; + u->a2dp_config = pa_xmemdup(a2dp_config, strlen(a2dp_config) + 1); u->loaded_device_paths = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); if (!(u->discovery = pa_bluetooth_discovery_get(u->core, headset_backend))) @@ -169,5 +176,8 @@ void pa__done(pa_module *m) { if (u->loaded_device_paths) pa_hashmap_free(u->loaded_device_paths); + if (u->a2dp_config) + pa_xfree((void *) u->a2dp_config); + pa_xfree(u); } -- 2.19.2
Attachment:
signature.asc
Description: OpenPGP digital signature
_______________________________________________ pulseaudio-discuss mailing list pulseaudio-discuss@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss