bluetooth: Modular API for A2DP codecs

This patch introduce new modular API for bluetooth A2DP codecs. Its
benefits are:

* bluez5-util and module-bluez5-device does not contain any codec specific
  code, they are codec independent.

* For adding new A2DP codec it is needed just to adjust one table in
  a2dp-codec-util.c file. All codec specific functions are in separate
  codec file.

* Support for backchannel (microphone voice). Some A2DP codecs (like
  FastStream or aptX Low Latency) are bi-directional and can be used for
  both music playback and audio call.

* Support for more configurations per codec. This allows to implement low
  quality mode of some codec together with high quality.

Current SBC codec implementation was moved from bluez5-util and
module-bluez5-device to its own file and converted to this new A2DP API.
This commit is contained in:
Pali Rohár 2019-04-06 11:15:58 +02:00 committed by Tanu Kaskinen
parent e8c4638f79
commit 106aa91477
8 changed files with 1127 additions and 622 deletions

View file

@ -2131,6 +2131,9 @@ 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-codec-api.h \
modules/bluetooth/a2dp-codec-util.c \
modules/bluetooth/a2dp-codec-util.h \
modules/bluetooth/a2dp-codecs.h \
modules/bluetooth/rtp.h
if HAVE_BLUEZ_5_OFONO_HEADSET
@ -2145,6 +2148,11 @@ 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_CPPFLAGS = $(AM_CPPFLAGS)
libbluez5_util_la_SOURCES += modules/bluetooth/a2dp-codec-sbc.c
libbluez5_util_la_LIBADD += $(SBC_LIBS)
libbluez5_util_la_CFLAGS += $(SBC_CFLAGS)
module_bluez5_discover_la_SOURCES = modules/bluetooth/module-bluez5-discover.c
module_bluez5_discover_la_LDFLAGS = $(MODULE_LDFLAGS)
@ -2153,8 +2161,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

View file

@ -0,0 +1,95 @@
#ifndef fooa2dpcodechfoo
#define fooa2dpcodechfoo
/***
This file is part of PulseAudio.
Copyright 2018-2019 Pali Rohár <pali.rohar@gmail.com>
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
PulseAudio is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
***/
#include <pulsecore/core.h>
#define MAX_A2DP_CAPS_SIZE 254
typedef struct pa_a2dp_codec_capabilities {
uint8_t size;
uint8_t buffer[]; /* max size is 254 bytes */
} pa_a2dp_codec_capabilities;
typedef struct pa_a2dp_codec_id {
uint8_t codec_id;
uint32_t vendor_id;
uint16_t vendor_codec_id;
} pa_a2dp_codec_id;
typedef struct pa_a2dp_codec {
/* Unique name of the codec, lowercase and without whitespaces, used for
* constructing identifier, D-Bus paths, ... */
const char *name;
/* Human readable codec description */
const char *description;
/* A2DP codec id */
pa_a2dp_codec_id id;
/* True if codec is bi-directional and supports backchannel */
bool support_backchannel;
/* Returns true if codec accepts capabilities, for_encoding is true when
* capabilities are used for encoding */
bool (*can_accept_capabilities)(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding);
/* Choose remote endpoint based on capabilities from hash map
* (const char *endpoint -> const pa_a2dp_codec_capabilities *capability)
* and returns corresponding endpoint key (or NULL when there is no valid),
* for_encoder is true when capabilities hash map is used for encoding */
const char *(*choose_remote_endpoint)(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding);
/* Fill codec capabilities, returns size of filled buffer */
uint8_t (*fill_capabilities)(uint8_t capabilities_buffer[MAX_A2DP_CAPS_SIZE]);
/* Validate codec configuration, returns true on success */
bool (*is_configuration_valid)(const uint8_t *config_buffer, uint8_t config_size);
/* Fill preferred codec configuration, returns size of filled buffer or 0 on failure */
uint8_t (*fill_preferred_configuration)(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[MAX_A2DP_CAPS_SIZE]);
/* Initialize codec, returns codec info data and set sample_spec,
* for_encoding is true when codec_info is used for encoding,
* for_backchannel is true when codec_info is used for backchannel */
void *(*init)(bool for_encoding, bool for_backchannel, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *sample_spec);
/* Deinitialize and release codec info data in codec_info */
void (*deinit)(void *codec_info);
/* Reset internal state of codec info data in codec_info */
void (*reset)(void *codec_info);
/* Get read block size for codec */
size_t (*get_read_block_size)(void *codec_info, size_t read_link_mtu);
/* Get write block size for codec */
size_t (*get_write_block_size)(void *codec_info, size_t write_link_mtu);
/* Reduce encoder bitrate for codec, returns new write block size or zero
* if not changed, called when socket is not accepting encoded data fast
* enough */
size_t (*reduce_encoder_bitrate)(void *codec_info, size_t write_link_mtu);
/* Encode input_buffer of input_size to output_buffer of output_size,
* returns size of filled ouput_buffer and set processed to size of
* processed input_buffer */
size_t (*encode_buffer)(void *codec_info, uint32_t timestamp, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size, size_t *processed);
/* Decode input_buffer of input_size to output_buffer of output_size,
* returns size of filled ouput_buffer and set processed to size of
* processed input_buffer */
size_t (*decode_buffer)(void *codec_info, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size, size_t *processed);
} pa_a2dp_codec;
#endif

View file

@ -0,0 +1,638 @@
/***
This file is part of PulseAudio.
Copyright 2018-2019 Pali Rohár <pali.rohar@gmail.com>
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
PulseAudio is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <pulsecore/core-util.h>
#include <pulsecore/log.h>
#include <pulsecore/macro.h>
#include <pulsecore/once.h>
#include <pulse/sample.h>
#include <pulse/xmalloc.h>
#include <arpa/inet.h>
#include <sbc/sbc.h>
#include "a2dp-codecs.h"
#include "a2dp-codec-api.h"
#include "rtp.h"
#define SBC_BITPOOL_DEC_LIMIT 32
#define SBC_BITPOOL_DEC_STEP 5
struct sbc_info {
sbc_t sbc; /* Codec data */
size_t codesize, frame_length; /* SBC Codesize, frame_length. We simply cache those values here */
uint16_t seq_num; /* Cumulative packet sequence */
uint8_t frequency;
uint8_t blocks;
uint8_t subbands;
uint8_t mode;
uint8_t allocation;
uint8_t initial_bitpool;
uint8_t min_bitpool;
uint8_t max_bitpool;
};
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;
if (capabilities_size != sizeof(*capabilities))
return false;
if (!(capabilities->frequency & (SBC_SAMPLING_FREQ_16000 | SBC_SAMPLING_FREQ_32000 | SBC_SAMPLING_FREQ_44100 | SBC_SAMPLING_FREQ_48000)))
return false;
if (!(capabilities->channel_mode & (SBC_CHANNEL_MODE_MONO | SBC_CHANNEL_MODE_DUAL_CHANNEL | SBC_CHANNEL_MODE_STEREO | SBC_CHANNEL_MODE_JOINT_STEREO)))
return false;
if (!(capabilities->allocation_method & (SBC_ALLOCATION_SNR | SBC_ALLOCATION_LOUDNESS)))
return false;
if (!(capabilities->subbands & (SBC_SUBBANDS_4 | SBC_SUBBANDS_8)))
return false;
if (!(capabilities->block_length & (SBC_BLOCK_LENGTH_4 | SBC_BLOCK_LENGTH_8 | SBC_BLOCK_LENGTH_12 | SBC_BLOCK_LENGTH_16)))
return false;
return true;
}
static const char *choose_remote_endpoint(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding) {
const pa_a2dp_codec_capabilities *a2dp_capabilities;
const char *key;
void *state;
/* There is no preference, just choose random valid entry */
PA_HASHMAP_FOREACH_KV(key, a2dp_capabilities, capabilities_hashmap, state) {
if (can_accept_capabilities(a2dp_capabilities->buffer, a2dp_capabilities->size, for_encoding))
return key;
}
return NULL;
}
static uint8_t fill_capabilities(uint8_t capabilities_buffer[MAX_A2DP_CAPS_SIZE]) {
a2dp_sbc_t *capabilities = (a2dp_sbc_t *) capabilities_buffer;
pa_zero(*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 = SBC_MIN_BITPOOL;
capabilities->max_bitpool = 64;
return sizeof(*capabilities);
}
static bool is_configuration_valid(const uint8_t *config_buffer, uint8_t config_size) {
const a2dp_sbc_t *config = (const a2dp_sbc_t *) config_buffer;
if (config_size != sizeof(*config)) {
pa_log_error("Invalid size of config buffer");
return false;
}
if (config->frequency != SBC_SAMPLING_FREQ_16000 && config->frequency != SBC_SAMPLING_FREQ_32000 &&
config->frequency != SBC_SAMPLING_FREQ_44100 && config->frequency != SBC_SAMPLING_FREQ_48000) {
pa_log_error("Invalid sampling frequency in configuration");
return false;
}
if (config->channel_mode != SBC_CHANNEL_MODE_MONO && config->channel_mode != SBC_CHANNEL_MODE_DUAL_CHANNEL &&
config->channel_mode != SBC_CHANNEL_MODE_STEREO && config->channel_mode != SBC_CHANNEL_MODE_JOINT_STEREO) {
pa_log_error("Invalid channel mode in configuration");
return false;
}
if (config->allocation_method != SBC_ALLOCATION_SNR && config->allocation_method != SBC_ALLOCATION_LOUDNESS) {
pa_log_error("Invalid allocation method in configuration");
return false;
}
if (config->subbands != SBC_SUBBANDS_4 && config->subbands != SBC_SUBBANDS_8) {
pa_log_error("Invalid SBC subbands in configuration");
return false;
}
if (config->block_length != SBC_BLOCK_LENGTH_4 && config->block_length != SBC_BLOCK_LENGTH_8 &&
config->block_length != SBC_BLOCK_LENGTH_12 && config->block_length != SBC_BLOCK_LENGTH_16) {
pa_log_error("Invalid block length in configuration");
return false;
}
return true;
}
static uint8_t 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;
}
static uint8_t fill_preferred_configuration(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[MAX_A2DP_CAPS_SIZE]) {
a2dp_sbc_t *config = (a2dp_sbc_t *) config_buffer;
const a2dp_sbc_t *capabilities = (const a2dp_sbc_t *) capabilities_buffer;
int i;
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 }
};
if (capabilities_size != sizeof(*capabilities)) {
pa_log_error("Invalid size of capabilities buffer");
return 0;
}
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 >= default_sample_spec->rate && (capabilities->frequency & freq_table[i].cap)) {
config->frequency = freq_table[i].cap;
break;
}
if ((unsigned) i == PA_ELEMENTSOF(freq_table)) {
for (--i; i >= 0; i--) {
if (capabilities->frequency & freq_table[i].cap) {
config->frequency = freq_table[i].cap;
break;
}
}
if (i < 0) {
pa_log_error("Not suitable sample rate");
return 0;
}
}
pa_assert((unsigned) i < PA_ELEMENTSOF(freq_table));
if (default_sample_spec->channels <= 1) {
if (capabilities->channel_mode & SBC_CHANNEL_MODE_MONO)
config->channel_mode = SBC_CHANNEL_MODE_MONO;
else if (capabilities->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
config->channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
else if (capabilities->channel_mode & SBC_CHANNEL_MODE_STEREO)
config->channel_mode = SBC_CHANNEL_MODE_STEREO;
else if (capabilities->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;
}
} else {
if (capabilities->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
config->channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
else if (capabilities->channel_mode & SBC_CHANNEL_MODE_STEREO)
config->channel_mode = SBC_CHANNEL_MODE_STEREO;
else if (capabilities->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
config->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
else if (capabilities->channel_mode & SBC_CHANNEL_MODE_MONO)
config->channel_mode = SBC_CHANNEL_MODE_MONO;
else {
pa_log_error("No supported channel modes");
return 0;
}
}
if (capabilities->block_length & SBC_BLOCK_LENGTH_16)
config->block_length = SBC_BLOCK_LENGTH_16;
else if (capabilities->block_length & SBC_BLOCK_LENGTH_12)
config->block_length = SBC_BLOCK_LENGTH_12;
else if (capabilities->block_length & SBC_BLOCK_LENGTH_8)
config->block_length = SBC_BLOCK_LENGTH_8;
else if (capabilities->block_length & SBC_BLOCK_LENGTH_4)
config->block_length = SBC_BLOCK_LENGTH_4;
else {
pa_log_error("No supported block lengths");
return 0;
}
if (capabilities->subbands & SBC_SUBBANDS_8)
config->subbands = SBC_SUBBANDS_8;
else if (capabilities->subbands & SBC_SUBBANDS_4)
config->subbands = SBC_SUBBANDS_4;
else {
pa_log_error("No supported subbands");
return 0;
}
if (capabilities->allocation_method & SBC_ALLOCATION_LOUDNESS)
config->allocation_method = SBC_ALLOCATION_LOUDNESS;
else if (capabilities->allocation_method & SBC_ALLOCATION_SNR)
config->allocation_method = SBC_ALLOCATION_SNR;
config->min_bitpool = (uint8_t) PA_MAX(SBC_MIN_BITPOOL, capabilities->min_bitpool);
config->max_bitpool = (uint8_t) PA_MIN(default_bitpool(config->frequency, config->channel_mode), capabilities->max_bitpool);
if (config->min_bitpool > config->max_bitpool)
return 0;
return sizeof(*config);
}
static void set_params(struct sbc_info *sbc_info) {
sbc_info->sbc.frequency = sbc_info->frequency;
sbc_info->sbc.blocks = sbc_info->blocks;
sbc_info->sbc.subbands = sbc_info->subbands;
sbc_info->sbc.mode = sbc_info->mode;
sbc_info->sbc.allocation = sbc_info->allocation;
sbc_info->sbc.bitpool = sbc_info->initial_bitpool;
sbc_info->codesize = sbc_get_codesize(&sbc_info->sbc);
sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc);
}
static void *init(bool for_encoding, bool for_backchannel, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *sample_spec) {
struct sbc_info *sbc_info;
const a2dp_sbc_t *config = (const a2dp_sbc_t *) config_buffer;
int ret;
pa_assert(config_size == sizeof(*config));
pa_assert(!for_backchannel);
sbc_info = pa_xnew0(struct sbc_info, 1);
ret = sbc_init(&sbc_info->sbc, 0);
if (ret != 0) {
pa_xfree(sbc_info);
pa_log_error("SBC initialization failed: %d", ret);
return NULL;
}
sample_spec->format = PA_SAMPLE_S16LE;
switch (config->frequency) {
case SBC_SAMPLING_FREQ_16000:
sbc_info->frequency = SBC_FREQ_16000;
sample_spec->rate = 16000U;
break;
case SBC_SAMPLING_FREQ_32000:
sbc_info->frequency = SBC_FREQ_32000;
sample_spec->rate = 32000U;
break;
case SBC_SAMPLING_FREQ_44100:
sbc_info->frequency = SBC_FREQ_44100;
sample_spec->rate = 44100U;
break;
case SBC_SAMPLING_FREQ_48000:
sbc_info->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->mode = SBC_MODE_MONO;
sample_spec->channels = 1;
break;
case SBC_CHANNEL_MODE_DUAL_CHANNEL:
sbc_info->mode = SBC_MODE_DUAL_CHANNEL;
sample_spec->channels = 2;
break;
case SBC_CHANNEL_MODE_STEREO:
sbc_info->mode = SBC_MODE_STEREO;
sample_spec->channels = 2;
break;
case SBC_CHANNEL_MODE_JOINT_STEREO:
sbc_info->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->allocation = SBC_AM_SNR;
break;
case SBC_ALLOCATION_LOUDNESS:
sbc_info->allocation = SBC_AM_LOUDNESS;
break;
default:
pa_assert_not_reached();
}
switch (config->subbands) {
case SBC_SUBBANDS_4:
sbc_info->subbands = SBC_SB_4;
break;
case SBC_SUBBANDS_8:
sbc_info->subbands = SBC_SB_8;
break;
default:
pa_assert_not_reached();
}
switch (config->block_length) {
case SBC_BLOCK_LENGTH_4:
sbc_info->blocks = SBC_BLK_4;
break;
case SBC_BLOCK_LENGTH_8:
sbc_info->blocks = SBC_BLK_8;
break;
case SBC_BLOCK_LENGTH_12:
sbc_info->blocks = SBC_BLK_12;
break;
case SBC_BLOCK_LENGTH_16:
sbc_info->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->initial_bitpool = for_encoding ? sbc_info->max_bitpool : sbc_info->min_bitpool;
set_params(sbc_info);
pa_log_info("SBC parameters: allocation=%s, subbands=%u, blocks=%u, mode=%s bitpool=%u codesize=%u frame_length=%u",
sbc_info->sbc.allocation ? "SNR" : "Loudness", sbc_info->sbc.subbands ? 8 : 4,
(sbc_info->sbc.blocks+1)*4, sbc_info->sbc.mode == SBC_MODE_MONO ? "Mono" :
sbc_info->sbc.mode == SBC_MODE_DUAL_CHANNEL ? "DualChannel" :
sbc_info->sbc.mode == SBC_MODE_STEREO ? "Stereo" : "JointStereo",
sbc_info->sbc.bitpool, (unsigned)sbc_info->codesize, (unsigned)sbc_info->frame_length);
return sbc_info;
}
static void deinit(void *codec_info) {
struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
sbc_finish(&sbc_info->sbc);
pa_xfree(sbc_info);
}
static void set_bitpool(struct sbc_info *sbc_info, uint8_t bitpool) {
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 reset(void *codec_info) {
struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
int ret;
ret = sbc_reinit(&sbc_info->sbc, 0);
if (ret != 0) {
pa_log_error("SBC reinitialization failed: %d", ret);
return;
}
/* sbc_reinit() sets also default parameters, so reset them back */
set_params(sbc_info);
sbc_info->seq_num = 0;
}
static size_t get_block_size(void *codec_info, size_t link_mtu) {
struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
return (link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
/ sbc_info->frame_length * sbc_info->codesize;
}
static size_t reduce_encoder_bitrate(void *codec_info, size_t write_link_mtu) {
struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
uint8_t bitpool;
/* Check if bitpool is already at its limit */
if (sbc_info->sbc.bitpool <= SBC_BITPOOL_DEC_LIMIT)
return 0;
bitpool = sbc_info->sbc.bitpool - SBC_BITPOOL_DEC_STEP;
if (bitpool < SBC_BITPOOL_DEC_LIMIT)
bitpool = SBC_BITPOOL_DEC_LIMIT;
if (sbc_info->sbc.bitpool == bitpool)
return 0;
set_bitpool(sbc_info, bitpool);
return get_block_size(codec_info, write_link_mtu);
}
static size_t encode_buffer(void *codec_info, uint32_t timestamp, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size, size_t *processed) {
struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
struct rtp_header *header;
struct rtp_payload *payload;
uint8_t *d;
const uint8_t *p;
size_t to_write, to_encode;
unsigned frame_count;
header = (struct rtp_header*) output_buffer;
payload = (struct rtp_payload*) (output_buffer + sizeof(*header));
frame_count = 0;
p = input_buffer;
to_encode = input_size;
d = output_buffer + sizeof(*header) + sizeof(*payload);
to_write = output_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);
*processed = p - input_buffer;
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 += encoded;
to_encode -= encoded;
d += written;
to_write -= written;
frame_count++;
}
PA_ONCE_BEGIN {
pa_log_debug("Using SBC codec implementation: %s", pa_strnull(sbc_get_implementation_info(&sbc_info->sbc)));
} PA_ONCE_END;
/* write it to the fifo */
memset(output_buffer, 0, sizeof(*header) + sizeof(*payload));
header->v = 2;
/* A2DP spec: "A payload type in the RTP dynamic range shall be chosen".
* RFC3551 defines the dynamic range to span from 96 to 127, and 96 appears
* to be the most common choice in A2DP implementations. */
header->pt = 96;
header->sequence_number = htons(sbc_info->seq_num++);
header->timestamp = htonl(timestamp);
header->ssrc = htonl(1);
payload->frame_count = frame_count;
*processed = p - input_buffer;
return d - output_buffer;
}
static size_t decode_buffer(void *codec_info, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size, size_t *processed) {
struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
struct rtp_header *header;
struct rtp_payload *payload;
const uint8_t *p;
uint8_t *d;
size_t to_write, to_decode;
header = (struct rtp_header *) input_buffer;
payload = (struct rtp_payload*) (input_buffer + sizeof(*header));
p = input_buffer + sizeof(*header) + sizeof(*payload);
to_decode = input_size - sizeof(*header) - sizeof(*payload);
d = output_buffer;
to_write = output_size;
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);
*processed = p - input_buffer;
return 0;
}
/* 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 += decoded;
to_decode -= decoded;
d += written;
to_write -= written;
}
*processed = p - input_buffer;
return d - output_buffer;
}
const pa_a2dp_codec pa_a2dp_codec_sbc = {
.name = "sbc",
.description = "SBC",
.id = { A2DP_CODEC_SBC, 0, 0 },
.support_backchannel = false,
.can_accept_capabilities = can_accept_capabilities,
.choose_remote_endpoint = choose_remote_endpoint,
.fill_capabilities = fill_capabilities,
.is_configuration_valid = is_configuration_valid,
.fill_preferred_configuration = fill_preferred_configuration,
.init = init,
.deinit = deinit,
.reset = reset,
.get_read_block_size = get_block_size,
.get_write_block_size = get_block_size,
.reduce_encoder_bitrate = reduce_encoder_bitrate,
.encode_buffer = encode_buffer,
.decode_buffer = decode_buffer,
};

View file

@ -0,0 +1,56 @@
/***
This file is part of PulseAudio.
Copyright 2019 Pali Rohár <pali.rohar@gmail.com>
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
PulseAudio is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <pulsecore/core.h>
#include <pulsecore/core-util.h>
#include "a2dp-codec-util.h"
extern const pa_a2dp_codec pa_a2dp_codec_sbc;
/* This is list of supported codecs. Their order is important.
* Codec with higher index has higher priority. */
const pa_a2dp_codec *pa_a2dp_codecs[] = {
&pa_a2dp_codec_sbc,
};
unsigned int pa_bluetooth_a2dp_codec_count(void) {
return PA_ELEMENTSOF(pa_a2dp_codecs);
}
const pa_a2dp_codec *pa_bluetooth_a2dp_codec_iter(unsigned int i) {
pa_assert(i < pa_bluetooth_a2dp_codec_count());
return pa_a2dp_codecs[i];
}
const pa_a2dp_codec *pa_bluetooth_get_a2dp_codec(const char *name) {
unsigned int i;
unsigned int count = pa_bluetooth_a2dp_codec_count();
for (i = 0; i < count; i++) {
if (pa_streq(pa_a2dp_codecs[i]->name, name))
return pa_a2dp_codecs[i];
}
return NULL;
}

View file

@ -0,0 +1,34 @@
#ifndef fooa2dpcodecutilhfoo
#define fooa2dpcodecutilhfoo
/***
This file is part of PulseAudio.
Copyright 2019 Pali Rohár <pali.rohar@gmail.com>
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
PulseAudio is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
***/
#include "a2dp-codec-api.h"
/* Get number of supported A2DP codecs */
unsigned int pa_bluetooth_a2dp_codec_count(void);
/* Get i-th codec. Codec with higher number has higher priority */
const pa_a2dp_codec *pa_bluetooth_a2dp_codec_iter(unsigned int i);
/* Get codec by name */
const pa_a2dp_codec *pa_bluetooth_get_a2dp_codec(const char *name);
#endif

View file

@ -2,6 +2,7 @@
This file is part of PulseAudio.
Copyright 2008-2013 João Paulo Rechi Vita
Copyrigth 2018-2019 Pali Rohár <pali.rohar@gmail.com>
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
@ -33,6 +34,7 @@
#include <pulsecore/refcnt.h>
#include <pulsecore/shared.h>
#include "a2dp-codec-util.h"
#include "a2dp-codecs.h"
#include "bluez5-util.h"
@ -885,13 +887,19 @@ finish:
pa_xfree(endpoint);
}
static void register_endpoint(pa_bluetooth_discovery *y, const char *path, const char *endpoint, const char *uuid) {
static void register_endpoint(pa_bluetooth_discovery *y, const pa_a2dp_codec *a2dp_codec, const char *path, const char *endpoint, const char *uuid) {
DBusMessage *m;
DBusMessageIter i, d;
uint8_t codec = 0;
uint8_t capabilities[MAX_A2DP_CAPS_SIZE];
size_t capabilities_size;
uint8_t codec_id;
pa_log_debug("Registering %s on adapter %s", endpoint, path);
codec_id = a2dp_codec->id.codec_id;
capabilities_size = a2dp_codec->fill_capabilities(capabilities);
pa_assert(capabilities_size != 0);
pa_assert_se(m = dbus_message_new_method_call(BLUEZ_SERVICE, path, BLUEZ_MEDIA_INTERFACE, "RegisterEndpoint"));
dbus_message_iter_init_append(m, &i);
@ -899,23 +907,8 @@ 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_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 = SBC_MIN_BITPOOL;
capabilities.max_bitpool = 64;
pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, &capabilities, sizeof(capabilities));
}
pa_dbus_append_basic_variant_dict_entry(&d, "Codec", DBUS_TYPE_BYTE, &codec_id);
pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, &capabilities, capabilities_size);
dbus_message_iter_close_container(&i, &d);
@ -950,6 +943,7 @@ static void parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessa
if (pa_streq(interface, BLUEZ_ADAPTER_INTERFACE)) {
pa_bluetooth_adapter *a;
unsigned a2dp_codec_i;
if ((a = pa_hashmap_get(y->adapters, path))) {
pa_log_error("Found duplicated D-Bus path for adapter %s", path);
@ -964,8 +958,20 @@ 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);
/* Order is important. bluez prefers endpoints registered earlier.
* And codec with higher number has higher priority. So iterate in reverse order. */
for (a2dp_codec_i = pa_bluetooth_a2dp_codec_count(); a2dp_codec_i > 0; a2dp_codec_i--) {
const pa_a2dp_codec *a2dp_codec = pa_bluetooth_a2dp_codec_iter(a2dp_codec_i-1);
char *endpoint;
endpoint = pa_sprintf_malloc("%s/%s", A2DP_SINK_ENDPOINT, a2dp_codec->name);
register_endpoint(y, a2dp_codec, path, endpoint, PA_BLUETOOTH_UUID_A2DP_SINK);
pa_xfree(endpoint);
endpoint = pa_sprintf_malloc("%s/%s", A2DP_SOURCE_ENDPOINT, a2dp_codec->name);
register_endpoint(y, a2dp_codec, path, endpoint, PA_BLUETOOTH_UUID_A2DP_SOURCE);
pa_xfree(endpoint);
}
} else if (pa_streq(interface, BLUEZ_DEVICE_INTERFACE)) {
@ -1257,48 +1263,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) {
case PA_BLUETOOTH_PROFILE_A2DP_SINK:
@ -1316,10 +1280,24 @@ const char *pa_bluetooth_profile_to_string(pa_bluetooth_profile_t profile) {
return NULL;
}
static const pa_a2dp_codec *a2dp_endpoint_to_a2dp_codec(const char *endpoint) {
const char *codec_name;
if (pa_startswith(endpoint, A2DP_SINK_ENDPOINT "/"))
codec_name = endpoint + strlen(A2DP_SINK_ENDPOINT "/");
else if (pa_startswith(endpoint, A2DP_SOURCE_ENDPOINT "/"))
codec_name = endpoint + strlen(A2DP_SOURCE_ENDPOINT "/");
else
return NULL;
return pa_bluetooth_get_a2dp_codec(codec_name);
}
static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) {
pa_bluetooth_discovery *y = userdata;
pa_bluetooth_device *d;
pa_bluetooth_transport *t;
const pa_a2dp_codec *a2dp_codec = NULL;
const char *sender, *path, *endpoint_path, *dev_path = NULL, *uuid = NULL;
const uint8_t *config = NULL;
int size = 0;
@ -1345,6 +1323,8 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY)
goto fail;
endpoint_path = dbus_message_get_path(m);
/* Read transport properties */
while (dbus_message_iter_get_arg_type(&props) == DBUS_TYPE_DICT_ENTRY) {
const char *key;
@ -1367,16 +1347,13 @@ 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_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SOURCE))
p = PA_BLUETOOTH_PROFILE_A2DP_SINK;
} else if (pa_streq(endpoint_path, A2DP_SINK_ENDPOINT)) {
if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SINK))
p = PA_BLUETOOTH_PROFILE_A2DP_SOURCE;
}
if (pa_startswith(endpoint_path, A2DP_SINK_ENDPOINT "/"))
p = PA_BLUETOOTH_PROFILE_A2DP_SOURCE;
else if (pa_startswith(endpoint_path, A2DP_SOURCE_ENDPOINT "/"))
p = PA_BLUETOOTH_PROFILE_A2DP_SINK;
if (p == PA_BLUETOOTH_PROFILE_OFF) {
if ((pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SOURCE) && p != PA_BLUETOOTH_PROFILE_A2DP_SINK) ||
(pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SINK) && p != PA_BLUETOOTH_PROFILE_A2DP_SOURCE)) {
pa_log_error("UUID %s of transport %s incompatible with endpoint %s", uuid, path, endpoint_path);
goto fail;
}
@ -1389,7 +1366,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,45 +1380,20 @@ 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;
a2dp_codec = a2dp_endpoint_to_a2dp_codec(endpoint_path);
pa_assert(a2dp_codec);
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->is_configuration_valid(config, 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);
}
if (!a2dp_codec)
goto fail2;
if ((d = pa_hashmap_get(y->devices, dev_path))) {
if (!d->valid) {
pa_log_error("Information about device %s is invalid", dev_path);
@ -1466,6 +1417,7 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
dbus_message_unref(r);
t = pa_bluetooth_transport_new(d, sender, path, p, config, size);
t->a2dp_codec = a2dp_codec;
t->acquire = bluez5_transport_acquire_cb;
t->release = bluez5_transport_release_cb;
pa_bluetooth_transport_put(t);
@ -1484,21 +1436,17 @@ 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;
const char *endpoint_path;
uint8_t *cap;
int size;
const pa_a2dp_codec *a2dp_codec;
uint8_t config[MAX_A2DP_CAPS_SIZE];
uint8_t *config_ptr = config;
size_t config_size;
DBusMessage *r;
DBusError err;
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 }
};
endpoint_path = dbus_message_get_path(m);
dbus_error_init(&err);
@ -1508,101 +1456,15 @@ static DBusMessage *endpoint_select_configuration(DBusConnection *conn, DBusMess
goto fail;
}
if (size != sizeof(config)) {
pa_log_error("Capabilities array has invalid size");
goto fail;
}
a2dp_codec = a2dp_endpoint_to_a2dp_codec(endpoint_path);
pa_assert(a2dp_codec);
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;
}
}
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");
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");
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(SBC_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)
config_size = a2dp_codec->fill_preferred_configuration(&y->core->default_sample_spec, cap, size, config);
if (config_size == 0)
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));
pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &config_ptr, config_size, DBUS_TYPE_INVALID));
return r;
@ -1677,7 +1539,7 @@ 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))
if (!a2dp_endpoint_to_a2dp_codec(path))
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
@ -1705,49 +1567,32 @@ static DBusHandlerResult endpoint_handler(DBusConnection *c, DBusMessage *m, voi
return DBUS_HANDLER_RESULT_HANDLED;
}
static void endpoint_init(pa_bluetooth_discovery *y, pa_bluetooth_profile_t profile) {
static void endpoint_init(pa_bluetooth_discovery *y, const char *endpoint) {
static const DBusObjectPathVTable vtable_endpoint = {
.message_function = endpoint_handler,
};
pa_assert(y);
pa_assert(endpoint);
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));
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));
break;
default:
pa_assert_not_reached();
break;
}
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) {
static void endpoint_done(pa_bluetooth_discovery *y, const char *endpoint) {
pa_assert(y);
pa_assert(endpoint);
switch(profile) {
case PA_BLUETOOTH_PROFILE_A2DP_SINK:
dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_ENDPOINT);
break;
case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SINK_ENDPOINT);
break;
default:
pa_assert_not_reached();
break;
}
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) {
pa_bluetooth_discovery *y;
DBusError err;
DBusConnection *conn;
unsigned i;
unsigned i, count;
const pa_a2dp_codec *a2dp_codec;
char *endpoint;
y = pa_xnew0(pa_bluetooth_discovery, 1);
PA_REFCNT_INIT(y);
@ -1799,8 +1644,18 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c, int headset_backe
}
y->matches_added = true;
endpoint_init(y, PA_BLUETOOTH_PROFILE_A2DP_SINK);
endpoint_init(y, PA_BLUETOOTH_PROFILE_A2DP_SOURCE);
count = pa_bluetooth_a2dp_codec_count();
for (i = 0; i < count; i++) {
a2dp_codec = pa_bluetooth_a2dp_codec_iter(i);
endpoint = pa_sprintf_malloc("%s/%s", A2DP_SINK_ENDPOINT, a2dp_codec->name);
endpoint_init(y, endpoint);
pa_xfree(endpoint);
endpoint = pa_sprintf_malloc("%s/%s", A2DP_SOURCE_ENDPOINT, a2dp_codec->name);
endpoint_init(y, endpoint);
pa_xfree(endpoint);
}
get_managed_objects(y);
@ -1823,6 +1678,10 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_ref(pa_bluetooth_discovery *y) {
}
void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
unsigned i, count;
const pa_a2dp_codec *a2dp_codec;
char *endpoint;
pa_assert(y);
pa_assert(PA_REFCNT_VALUE(y) > 0);
@ -1868,8 +1727,18 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
if (y->filter_added)
dbus_connection_remove_filter(pa_dbus_connection_get(y->connection), filter_cb, y);
endpoint_done(y, PA_BLUETOOTH_PROFILE_A2DP_SINK);
endpoint_done(y, PA_BLUETOOTH_PROFILE_A2DP_SOURCE);
count = pa_bluetooth_a2dp_codec_count();
for (i = 0; i < count; i++) {
a2dp_codec = pa_bluetooth_a2dp_codec_iter(i);
endpoint = pa_sprintf_malloc("%s/%s", A2DP_SINK_ENDPOINT, a2dp_codec->name);
endpoint_done(y, endpoint);
pa_xfree(endpoint);
endpoint = pa_sprintf_malloc("%s/%s", A2DP_SOURCE_ENDPOINT, a2dp_codec->name);
endpoint_done(y, endpoint);
pa_xfree(endpoint);
}
pa_dbus_connection_unref(y->connection);
}

View file

@ -5,6 +5,7 @@
This file is part of PulseAudio.
Copyright 2008-2013 João Paulo Rechi Vita
Copyrigth 2018-2019 Pali Rohár <pali.rohar@gmail.com>
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
@ -22,6 +23,8 @@
#include <pulsecore/core.h>
#include "a2dp-codec-util.h"
#define PA_BLUETOOTH_UUID_A2DP_SOURCE "0000110a-0000-1000-8000-00805f9b34fb"
#define PA_BLUETOOTH_UUID_A2DP_SINK "0000110b-0000-1000-8000-00805f9b34fb"
@ -82,6 +85,8 @@ struct pa_bluetooth_transport {
uint8_t *config;
size_t config_size;
const pa_a2dp_codec *a2dp_codec;
uint16_t microphone_gain;
uint16_t speaker_gain;

View file

@ -3,6 +3,7 @@
Copyright 2008-2013 João Paulo Rechi Vita
Copyright 2011-2013 BMW Car IT GmbH.
Copyright 2018-2019 Pali Rohár <pali.rohar@gmail.com>
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
@ -25,7 +26,6 @@
#include <errno.h>
#include <arpa/inet.h>
#include <sbc/sbc.h>
#include <pulse/rtclock.h>
#include <pulse/timeval.h>
@ -47,8 +47,8 @@
#include <pulsecore/time-smoother.h>
#include "a2dp-codecs.h"
#include "a2dp-codec-util.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,8 +62,6 @@ 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[] = {
@ -94,18 +92,6 @@ 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;
struct userdata {
pa_module *module;
pa_core *core;
@ -145,8 +131,18 @@ struct userdata {
pa_usec_t started_at;
pa_smoother *read_smoother;
pa_memchunk write_memchunk;
pa_sample_spec sample_spec;
struct sbc_info sbc_info;
const pa_a2dp_codec *a2dp_codec;
void *encoder_info;
pa_sample_spec encoder_sample_spec;
void *encoder_buffer; /* Codec transfer buffer */
size_t encoder_buffer_size; /* Size of the buffer */
void *decoder_info;
pa_sample_spec decoder_sample_spec;
void *decoder_buffer; /* Codec transfer buffer */
size_t decoder_buffer_size; /* Size of the buffer */
};
typedef enum pa_bluetooth_form_factor {
@ -380,7 +376,7 @@ static int sco_process_push(struct userdata *u) {
* issues in our Bluetooth adapter. In these cases, in order to avoid
* an assertion failure due to unaligned data, just discard the whole
* packet */
if (!pa_frame_aligned(l, &u->sample_spec)) {
if (!pa_frame_aligned(l, &u->decoder_sample_spec)) {
pa_log_warn("SCO packet received of unaligned size: %zu", l);
pa_memblock_unref(memchunk.memblock);
return -1;
@ -403,7 +399,7 @@ static int sco_process_push(struct userdata *u) {
tstamp = pa_rtclock_now();
}
pa_smoother_put(u->read_smoother, tstamp, pa_bytes_to_usec(u->read_index, &u->sample_spec));
pa_smoother_put(u->read_smoother, tstamp, pa_bytes_to_usec(u->read_index, &u->decoder_sample_spec));
pa_smoother_resume(u->read_smoother, tstamp, true);
pa_source_post(u->source, &memchunk);
@ -413,115 +409,37 @@ static int sco_process_push(struct userdata *u) {
}
/* Run from IO thread */
static void a2dp_prepare_buffer(struct userdata *u) {
size_t min_buffer_size = PA_MAX(u->read_link_mtu, u->write_link_mtu);
static void a2dp_prepare_encoder_buffer(struct userdata *u) {
pa_assert(u);
if (u->sbc_info.buffer_size >= min_buffer_size)
if (u->encoder_buffer_size >= u->write_link_mtu)
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->encoder_buffer_size = 2 * u->write_link_mtu;
pa_xfree(u->encoder_buffer);
u->encoder_buffer = pa_xmalloc(u->encoder_buffer_size);
}
/* 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;
int ret = 0;
static void a2dp_prepare_decoder_buffer(struct userdata *u) {
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);
if (u->decoder_buffer_size >= u->read_link_mtu)
return;
pa_assert(u->write_memchunk.length == u->write_block_size);
u->decoder_buffer_size = 2 * u->read_link_mtu;
pa_xfree(u->decoder_buffer);
u->decoder_buffer = pa_xmalloc(u->decoder_buffer_size);
}
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;
/* A2DP spec: "A payload type in the RTP dynamic range shall be chosen".
* RFC3551 defines the dynamic range to span from 96 to 127, and 96 appears
* to be the most common choice in A2DP implementations. */
header->pt = 96;
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;
/* Run from IO thread */
static int a2dp_write_buffer(struct userdata *u, size_t nbytes) {
int ret = 0;
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->encoder_buffer, nbytes, &u->stream_write_type);
pa_assert(l != 0);
@ -564,6 +482,40 @@ static int a2dp_process_render(struct userdata *u) {
return ret;
}
/* Run from IO thread */
static int a2dp_process_render(struct userdata *u) {
const uint8_t *ptr;
size_t processed;
size_t length;
pa_assert(u);
pa_assert(u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK);
pa_assert(u->sink);
pa_assert(u->a2dp_codec);
/* 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);
a2dp_prepare_encoder_buffer(u);
/* Try to create a packet of the full MTU */
ptr = (const uint8_t *) pa_memblock_acquire_chunk(&u->write_memchunk);
length = u->a2dp_codec->encode_buffer(u->encoder_info, u->write_index / pa_frame_size(&u->encoder_sample_spec), ptr, u->write_memchunk.length, u->encoder_buffer, u->encoder_buffer_size, &processed);
pa_memblock_release(u->write_memchunk.memblock);
if (length == 0)
return -1;
pa_assert(processed == u->write_memchunk.length);
return a2dp_write_buffer(u, length);
}
/* Run from IO thread */
static int a2dp_process_push(struct userdata *u) {
int ret = 0;
@ -573,6 +525,7 @@ 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->a2dp_codec);
memchunk.memblock = pa_memblock_new(u->core->mempool, u->read_block_size);
memchunk.index = memchunk.length = 0;
@ -580,22 +533,13 @@ 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;
void *d;
uint8_t *ptr;
ssize_t l;
size_t to_write, to_decode;
size_t total_written = 0;
size_t processed;
a2dp_prepare_buffer(u);
a2dp_prepare_decoder_buffer(u);
sbc_info = &u->sbc_info;
header = sbc_info->buffer;
payload = (struct rtp_payload*) ((uint8_t*) sbc_info->buffer + sizeof(*header));
l = pa_read(u->stream_fd, sbc_info->buffer, sbc_info->buffer_size, &u->stream_write_type);
l = pa_read(u->stream_fd, u->decoder_buffer, u->decoder_buffer_size, &u->stream_write_type);
if (l <= 0) {
@ -612,7 +556,7 @@ static int a2dp_process_push(struct userdata *u) {
break;
}
pa_assert((size_t) l <= sbc_info->buffer_size);
pa_assert((size_t) l <= u->decoder_buffer_size);
/* TODO: get timestamp from rtp */
if (!found_tstamp) {
@ -620,51 +564,20 @@ 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);
ptr = pa_memblock_acquire(memchunk.memblock);
memchunk.length = pa_memblock_get_length(memchunk.memblock);
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;
}
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;
memchunk.length = u->a2dp_codec->decode_buffer(u->decoder_info, u->decoder_buffer, l, ptr, memchunk.length, &processed);
if (memchunk.length == 0) {
pa_memblock_release(memchunk.memblock);
ret = 0;
break;
}
u->read_index += (uint64_t) total_written;
pa_smoother_put(u->read_smoother, tstamp, pa_bytes_to_usec(u->read_index, &u->sample_spec));
u->read_index += (uint64_t) memchunk.length;
pa_smoother_put(u->read_smoother, tstamp, pa_bytes_to_usec(u->read_index, &u->decoder_sample_spec));
pa_smoother_resume(u->read_smoother, tstamp, true);
memchunk.length -= to_write;
pa_memblock_release(memchunk.memblock);
pa_source_post(u->source, &memchunk);
@ -678,7 +591,7 @@ static int a2dp_process_push(struct userdata *u) {
return ret;
}
static void update_buffer_size(struct userdata *u) {
static void update_sink_buffer_size(struct userdata *u) {
int old_bufsize;
socklen_t len = sizeof(int);
int ret;
@ -710,72 +623,6 @@ 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;
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));
/* If there is still data in the memchunk, we have to discard it
* because the write_block_size may have changed. */
if (u->write_memchunk.memblock) {
pa_memblock_unref(u->write_memchunk.memblock);
pa_memchunk_reset(&u->write_memchunk);
}
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);
@ -849,6 +696,24 @@ static void transport_release(struct userdata *u) {
pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u->msg), BLUETOOTH_MESSAGE_STREAM_FD_HUP, NULL, 0, NULL, NULL);
}
/* Run from I/O thread */
static void handle_sink_block_size_change(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,
(u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK ?
FIXED_LATENCY_PLAYBACK_A2DP : FIXED_LATENCY_PLAYBACK_SCO) +
pa_bytes_to_usec(u->write_block_size, &u->encoder_sample_spec));
/* If there is still data in the memchunk, we have to discard it
* because the write_block_size may have changed. */
if (u->write_memchunk.memblock) {
pa_memblock_unref(u->write_memchunk.memblock);
pa_memchunk_reset(&u->write_memchunk);
}
update_sink_buffer_size(u);
}
/* Run from I/O thread */
static void transport_config_mtu(struct userdata *u) {
if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) {
@ -865,28 +730,22 @@ 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;
pa_assert(u->a2dp_codec);
if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK) {
u->write_block_size = u->a2dp_codec->get_write_block_size(u->encoder_info, u->write_link_mtu);
} else {
u->read_block_size = u->a2dp_codec->get_read_block_size(u->decoder_info, u->read_link_mtu);
}
}
if (u->sink) {
pa_sink_set_max_request_within_thread(u->sink, u->write_block_size);
pa_sink_set_fixed_latency_within_thread(u->sink,
(u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK ?
FIXED_LATENCY_PLAYBACK_A2DP : FIXED_LATENCY_PLAYBACK_SCO) +
pa_bytes_to_usec(u->write_block_size, &u->sample_spec));
}
if (u->sink)
handle_sink_block_size_change(u);
if (u->source)
pa_source_set_fixed_latency_within_thread(u->source,
(u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE ?
FIXED_LATENCY_RECORD_A2DP : FIXED_LATENCY_RECORD_SCO) +
pa_bytes_to_usec(u->read_block_size, &u->sample_spec));
pa_bytes_to_usec(u->read_block_size, &u->decoder_sample_spec));
}
/* Run from I/O thread */
@ -900,6 +759,14 @@ static void setup_stream(struct userdata *u) {
pa_log_info("Transport %s resuming", u->transport->path);
if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK) {
pa_assert(u->a2dp_codec);
u->a2dp_codec->reset(u->encoder_info);
} else if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE) {
pa_assert(u->a2dp_codec);
u->a2dp_codec->reset(u->decoder_info);
}
transport_config_mtu(u);
pa_make_fd_nonblock(u->stream_fd);
@ -911,11 +778,6 @@ 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);
}
u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1);
pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
pollfd->fd = u->stream_fd;
@ -957,7 +819,7 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off
if (u->read_smoother) {
wi = pa_smoother_get(u->read_smoother, pa_rtclock_now());
ri = pa_bytes_to_usec(u->read_index, &u->sample_spec);
ri = pa_bytes_to_usec(u->read_index, &u->decoder_sample_spec);
*((int64_t*) data) = u->source->thread_info.fixed_latency + wi - ri;
} else
@ -1050,11 +912,11 @@ static void source_set_volume_cb(pa_source *s) {
if (volume < PA_VOLUME_NORM)
volume++;
pa_cvolume_set(&s->real_volume, u->sample_spec.channels, volume);
pa_cvolume_set(&s->real_volume, u->decoder_sample_spec.channels, volume);
/* Set soft volume when in headset role */
if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY)
pa_cvolume_set(&s->soft_volume, u->sample_spec.channels, volume);
pa_cvolume_set(&s->soft_volume, u->decoder_sample_spec.channels, volume);
/* If we are in the AG role, we send a command to the head set to change
* the microphone gain. In the HS role, source and sink are swapped, so
@ -1075,7 +937,7 @@ static int add_source(struct userdata *u) {
data.name = pa_sprintf_malloc("bluez_source.%s.%s", u->device->address, pa_bluetooth_profile_to_string(u->profile));
data.namereg_fail = false;
pa_proplist_sets(data.proplist, "bluetooth.protocol", pa_bluetooth_profile_to_string(u->profile));
pa_source_new_data_set_sample_spec(&data, &u->sample_spec);
pa_source_new_data_set_sample_spec(&data, &u->decoder_sample_spec);
if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT)
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
@ -1133,10 +995,10 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
if (u->read_smoother) {
ri = pa_smoother_get(u->read_smoother, pa_rtclock_now());
wi = pa_bytes_to_usec(u->write_index + u->write_block_size, &u->sample_spec);
wi = pa_bytes_to_usec(u->write_index + u->write_block_size, &u->encoder_sample_spec);
} else if (u->started_at) {
ri = pa_rtclock_now() - u->started_at;
wi = pa_bytes_to_usec(u->write_index, &u->sample_spec);
wi = pa_bytes_to_usec(u->write_index, &u->encoder_sample_spec);
}
*((int64_t*) data) = u->sink->thread_info.fixed_latency + wi - ri;
@ -1224,11 +1086,11 @@ static void sink_set_volume_cb(pa_sink *s) {
if (volume < PA_VOLUME_NORM)
volume++;
pa_cvolume_set(&s->real_volume, u->sample_spec.channels, volume);
pa_cvolume_set(&s->real_volume, u->encoder_sample_spec.channels, volume);
/* Set soft volume when in headset role */
if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY)
pa_cvolume_set(&s->soft_volume, u->sample_spec.channels, volume);
pa_cvolume_set(&s->soft_volume, u->encoder_sample_spec.channels, volume);
/* If we are in the AG role, we send a command to the head set to change
* the speaker gain. In the HS role, source and sink are swapped, so
@ -1249,7 +1111,7 @@ static int add_sink(struct userdata *u) {
data.name = pa_sprintf_malloc("bluez_sink.%s.%s", u->device->address, pa_bluetooth_profile_to_string(u->profile));
data.namereg_fail = false;
pa_proplist_sets(data.proplist, "bluetooth.protocol", pa_bluetooth_profile_to_string(u->profile));
pa_sink_new_data_set_sample_spec(&data, &u->sample_spec);
pa_sink_new_data_set_sample_spec(&data, &u->encoder_sample_spec);
if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT)
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
@ -1295,117 +1157,38 @@ static int add_sink(struct userdata *u) {
}
/* Run from main thread */
static void transport_config(struct userdata *u) {
static int transport_config(struct userdata *u) {
if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) {
u->sample_spec.format = PA_SAMPLE_S16LE;
u->sample_spec.channels = 1;
u->sample_spec.rate = 8000;
u->encoder_sample_spec.format = PA_SAMPLE_S16LE;
u->encoder_sample_spec.channels = 1;
u->encoder_sample_spec.rate = 8000;
u->decoder_sample_spec.format = PA_SAMPLE_S16LE;
u->decoder_sample_spec.channels = 1;
u->decoder_sample_spec.rate = 8000;
return 0;
} else {
sbc_info_t *sbc_info = &u->sbc_info;
a2dp_sbc_t *config;
bool is_a2dp_sink = u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK;
void *info;
pa_assert(u->transport);
u->sample_spec.format = PA_SAMPLE_S16LE;
config = (a2dp_sbc_t *) u->transport->config;
pa_assert(!u->a2dp_codec);
pa_assert(!u->encoder_info);
pa_assert(!u->decoder_info);
if (sbc_info->sbc_initialized)
sbc_reinit(&sbc_info->sbc, 0);
u->a2dp_codec = u->transport->a2dp_codec;
pa_assert(u->a2dp_codec);
info = u->a2dp_codec->init(is_a2dp_sink, false, u->transport->config, u->transport->config_size, is_a2dp_sink ? &u->encoder_sample_spec : &u->decoder_sample_spec);
if (is_a2dp_sink)
u->encoder_info = info;
else
sbc_init(&sbc_info->sbc, 0);
sbc_info->sbc_initialized = true;
u->decoder_info = info;
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();
}
if (!info)
return -1;
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();
}
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 = 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);
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);
return 0;
}
}
@ -1436,9 +1219,7 @@ static int setup_transport(struct userdata *u) {
return -1; /* We need to fail here until the interactions with module-suspend-on-idle and alike get improved */
}
transport_config(u);
return 0;
return transport_config(u);
}
/* Run from main thread */
@ -1618,12 +1399,12 @@ static void thread_func(void *userdata) {
if (u->started_at) {
time_passed = pa_rtclock_now() - u->started_at;
audio_sent = pa_bytes_to_usec(u->write_index, &u->sample_spec);
audio_sent = pa_bytes_to_usec(u->write_index, &u->encoder_sample_spec);
}
/* A new block needs to be sent. */
if (audio_sent <= time_passed) {
size_t bytes_to_send = pa_usec_to_bytes(time_passed - audio_sent, &u->sample_spec);
size_t bytes_to_send = pa_usec_to_bytes(time_passed - audio_sent, &u->encoder_sample_spec);
/* 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
@ -1636,7 +1417,7 @@ static void thread_func(void *userdata) {
pa_usec_t skip_usec;
skip_bytes = bytes_to_send - 2 * u->write_block_size;
skip_usec = pa_bytes_to_usec(skip_bytes, &u->sample_spec);
skip_usec = pa_bytes_to_usec(skip_bytes, &u->encoder_sample_spec);
pa_log_debug("Skipping %llu us (= %llu bytes) in audio stream",
(unsigned long long) skip_usec,
@ -1656,8 +1437,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->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK) {
size_t new_write_block_size = u->a2dp_codec->reduce_encoder_bitrate(u->encoder_info, u->write_link_mtu);
if (new_write_block_size) {
u->write_block_size = new_write_block_size;
handle_sink_block_size_change(u);
}
}
}
blocks_to_write = 1;
@ -1686,7 +1472,7 @@ static void thread_func(void *userdata) {
if (writable) {
/* There was no write pending on this iteration of the loop.
* Let's estimate when we need to wake up next */
next_write_at = pa_bytes_to_usec(u->write_index, &u->sample_spec);
next_write_at = pa_bytes_to_usec(u->write_index, &u->encoder_sample_spec);
sleep_for = time_passed < next_write_at ? next_write_at - time_passed : 0;
/* pa_log("Sleeping for %lu; time passed %lu, next write at %lu", (unsigned long) sleep_for, (unsigned long) time_passed, (unsigned long)next_write_at); */
} else
@ -1830,6 +1616,20 @@ static void stop_thread(struct userdata *u) {
pa_smoother_free(u->read_smoother);
u->read_smoother = NULL;
}
if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK || u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE) {
if (u->encoder_info) {
u->a2dp_codec->deinit(u->encoder_info);
u->encoder_info = NULL;
}
if (u->decoder_info) {
u->a2dp_codec->deinit(u->decoder_info);
u->decoder_info = NULL;
}
u->a2dp_codec = NULL;
}
}
/* Run from main thread */
@ -2309,7 +2109,7 @@ static pa_hook_result_t transport_speaker_gain_changed_cb(pa_bluetooth_discovery
if (volume < PA_VOLUME_NORM)
volume++;
pa_cvolume_set(&v, u->sample_spec.channels, volume);
pa_cvolume_set(&v, u->encoder_sample_spec.channels, volume);
if (t->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT)
pa_sink_volume_changed(u->sink, &v);
else
@ -2336,7 +2136,7 @@ static pa_hook_result_t transport_microphone_gain_changed_cb(pa_bluetooth_discov
if (volume < PA_VOLUME_NORM)
volume++;
pa_cvolume_set(&v, u->sample_spec.channels, volume);
pa_cvolume_set(&v, u->decoder_sample_spec.channels, volume);
if (t->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT)
pa_source_volume_changed(u->source, &v);
@ -2497,11 +2297,11 @@ 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->encoder_buffer)
pa_xfree(u->encoder_buffer);
if (u->sbc_info.sbc_initialized)
sbc_finish(&u->sbc_info.sbc);
if (u->decoder_buffer)
pa_xfree(u->decoder_buffer);
if (u->msg)
pa_xfree(u->msg);