mirror of
https://gitlab.freedesktop.org/pulseaudio/pulseaudio.git
synced 2025-11-04 13:29:59 -05:00
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:
parent
e8c4638f79
commit
106aa91477
8 changed files with 1127 additions and 622 deletions
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue