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

@ -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);