Commit 3902cee4 authored by Igor Kovalenko's avatar Igor Kovalenko Committed by PulseAudio Marge Bot
Browse files

bluetooth: unify encoder code paths

Part-of: <!507>
parent 8a87af38
......@@ -83,6 +83,9 @@ typedef struct pa_a2dp_codec {
/* Get write block size for codec, it is maximal size of buffer
* which can produce at most write_link_mtu bytes of encoded data */
size_t (*get_write_block_size)(void *codec_info, size_t write_link_mtu);
/* Get encoded block size for codec to hold one encoded frame.
* Note HFP mSBC codec encoded block may not fit into one MTU and is sent out in chunks. */
size_t (*get_encoded_block_size)(void *codec_info, size_t input_size);
/* Reduce encoder bitrate for codec, returns new write block size or zero
* if not changed, called when socket is not accepting encoded data fast
......
......@@ -459,6 +459,13 @@ static size_t get_block_size(void *codec_info, size_t link_mtu) {
return frame_count * 4 * 6;
}
static size_t get_encoded_block_size(void *codec_info, size_t input_size) {
/* input size should be aligned to codec input block size */
pa_assert_fp(input_size % (4 * 6) == 0);
return (input_size / (4 * 6)) * 4;
}
static size_t get_block_size_hd(void *codec_info, size_t link_mtu) {
/* aptX HD compression ratio is 4:1 and we need to process one aptX HD frame (6 bytes) at once, plus aptX HD frames are encapsulated in RTP */
size_t rtp_size = sizeof(struct rtp_header);
......@@ -467,6 +474,15 @@ static size_t get_block_size_hd(void *codec_info, size_t link_mtu) {
return frame_count * 6 * 4;
}
static size_t get_encoded_block_size_hd(void *codec_info, size_t input_size) {
size_t rtp_size = sizeof(struct rtp_header);
/* input size should be aligned to codec input block size */
pa_assert_fp(input_size % (4 * 6) == 0);
return (input_size / (4 * 6)) * 6 + rtp_size;
}
static size_t reduce_encoder_bitrate(void *codec_info, size_t write_link_mtu) {
return 0;
}
......@@ -554,6 +570,7 @@ const pa_a2dp_codec pa_a2dp_codec_aptx = {
.reset = reset,
.get_read_block_size = get_block_size,
.get_write_block_size = get_block_size,
.get_encoded_block_size = get_encoded_block_size,
.reduce_encoder_bitrate = reduce_encoder_bitrate,
.encode_buffer = encode_buffer,
.decode_buffer = decode_buffer,
......@@ -575,6 +592,7 @@ const pa_a2dp_codec pa_a2dp_codec_aptx_hd = {
.reset = reset_hd,
.get_read_block_size = get_block_size_hd,
.get_write_block_size = get_block_size_hd,
.get_encoded_block_size = get_encoded_block_size_hd,
.reduce_encoder_bitrate = reduce_encoder_bitrate,
.encode_buffer = encode_buffer_hd,
.decode_buffer = decode_buffer_hd,
......
......@@ -405,6 +405,11 @@ static size_t get_block_size(void *codec_info, size_t link_mtu) {
return get_ldac_num_samples(codec_info) * get_ldac_num_frames(codec_info, info->codec_type) * pa_frame_size(info->ss);
}
static size_t get_encoded_block_size(void *codec_info, size_t input_size) {
/* encoded block size is not exactly known, report input_size */
return input_size;
}
static size_t reduce_encoder_bitrate(void *codec_info, size_t write_link_mtu) {
return 0;
}
......@@ -435,6 +440,7 @@ const pa_a2dp_codec pa_a2dp_codec_ldac_eqmid_hq = {
.reset = reset,
.get_read_block_size = get_block_size,
.get_write_block_size = get_block_size,
.get_encoded_block_size = get_encoded_block_size,
.reduce_encoder_bitrate = reduce_encoder_bitrate,
.encode_buffer = encode_buffer,
};
......@@ -455,6 +461,7 @@ const pa_a2dp_codec pa_a2dp_codec_ldac_eqmid_sq = {
.reset = reset,
.get_read_block_size = get_block_size,
.get_write_block_size = get_block_size,
.get_encoded_block_size = get_encoded_block_size,
.reduce_encoder_bitrate = reduce_encoder_bitrate,
.encode_buffer = encode_buffer,
};
......@@ -475,6 +482,7 @@ const pa_a2dp_codec pa_a2dp_codec_ldac_eqmid_mq = {
.reset = reset,
.get_read_block_size = get_block_size,
.get_write_block_size = get_block_size,
.get_encoded_block_size = get_encoded_block_size,
.reduce_encoder_bitrate = reduce_encoder_bitrate,
.encode_buffer = encode_buffer,
};
......@@ -714,6 +714,16 @@ static size_t get_block_size(void *codec_info, size_t link_mtu) {
return frame_count * sbc_info->codesize;
}
static size_t get_encoded_block_size(void *codec_info, size_t input_size) {
struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
size_t rtp_size = sizeof(struct rtp_header) + sizeof(struct rtp_sbc_payload);
/* input size should be aligned to codec input block size */
pa_assert_fp(input_size % sbc_info->codesize == 0);
return (input_size / sbc_info->codesize) * sbc_info->frame_length + rtp_size;
}
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;
......@@ -902,6 +912,7 @@ const pa_a2dp_codec pa_a2dp_codec_sbc = {
.reset = reset,
.get_read_block_size = get_block_size,
.get_write_block_size = get_block_size,
.get_encoded_block_size = get_encoded_block_size,
.reduce_encoder_bitrate = reduce_encoder_bitrate,
.increase_encoder_bitrate = increase_encoder_bitrate,
.encode_buffer = encode_buffer,
......@@ -937,6 +948,7 @@ const pa_a2dp_codec pa_a2dp_codec_sbc_xq_453 = {
.reset = reset,
.get_read_block_size = get_block_size,
.get_write_block_size = get_block_size,
.get_encoded_block_size = get_encoded_block_size,
.reduce_encoder_bitrate = reduce_encoder_bitrate,
.increase_encoder_bitrate = increase_encoder_bitrate,
.encode_buffer = encode_buffer,
......@@ -959,6 +971,7 @@ const pa_a2dp_codec pa_a2dp_codec_sbc_xq_512 = {
.reset = reset,
.get_read_block_size = get_block_size,
.get_write_block_size = get_block_size,
.get_encoded_block_size = get_encoded_block_size,
.reduce_encoder_bitrate = reduce_encoder_bitrate,
.increase_encoder_bitrate = increase_encoder_bitrate,
.encode_buffer = encode_buffer,
......@@ -981,6 +994,7 @@ const pa_a2dp_codec pa_a2dp_codec_sbc_xq_552 = {
.reset = reset,
.get_read_block_size = get_block_size,
.get_write_block_size = get_block_size,
.get_encoded_block_size = get_encoded_block_size,
.reduce_encoder_bitrate = reduce_encoder_bitrate,
.increase_encoder_bitrate = increase_encoder_bitrate,
.encode_buffer = encode_buffer,
......
......@@ -334,6 +334,52 @@ static void sco_release_cb(pa_bluetooth_transport *t) {
/* device will close the SCO socket for us */
}
static ssize_t sco_transport_write(pa_bluetooth_transport *t, int fd, const void* buffer, size_t size, size_t write_mtu) {
ssize_t l = 0;
size_t written = 0;
size_t write_size;
pa_assert(t);
/* if encoder buffer has less data than required to make complete packet */
if (size < write_mtu)
return 0;
/* write out MTU sized chunks only */
while (written < size) {
write_size = PA_MIN(size - written, write_mtu);
if (write_size < write_mtu)
break;
l = pa_write(fd, buffer + written, write_size, &t->stream_write_type);
if (l < 0)
break;
written += l;
}
if (l < 0) {
if (errno == EAGAIN) {
/* Hmm, apparently the socket was not writable, give up for now */
pa_log_debug("Got EAGAIN on write() after POLLOUT, probably there is a temporary connection loss.");
/* Drain write buffer */
written = size;
} else {
pa_log_error("Failed to write data to socket: %s", pa_cstrerror(errno));
/* Report error from write call */
return -1;
}
}
/* if too much data left discard it all */
if (size - written >= write_mtu) {
pa_log_warn("Wrote memory block to socket only partially! %lu written, discarding pending write size %lu larger than write_mtu %lu",
written, size, write_mtu);
/* Drain write buffer */
written = size;
}
return written;
}
static void sco_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) {
pa_bluetooth_transport *t = userdata;
......@@ -730,6 +776,7 @@ static DBusMessage *profile_new_connection(DBusConnection *conn, DBusMessage *m,
t->acquire = sco_acquire_cb;
t->release = sco_release_cb;
t->write = sco_transport_write;
t->destroy = transport_destroy;
/* If PA is the HF/HS we are in control of volume attenuation and
......
......@@ -83,6 +83,52 @@ struct pa_bluetooth_backend {
PA_LLIST_HEAD(pa_dbus_pending, pending);
};
static ssize_t sco_transport_write(pa_bluetooth_transport *t, int fd, const void* buffer, size_t size, size_t write_mtu) {
ssize_t l = 0;
size_t written = 0;
size_t write_size;
pa_assert(t);
/* if encoder buffer has less data than required to make complete packet */
if (size < write_mtu)
return 0;
/* write out MTU sized chunks only */
while (written < size) {
write_size = PA_MIN(size - written, write_mtu);
if (write_size < write_mtu)
break;
l = pa_write(fd, buffer + written, write_size, &t->stream_write_type);
if (l < 0)
break;
written += l;
}
if (l < 0) {
if (errno == EAGAIN) {
/* Hmm, apparently the socket was not writable, give up for now */
pa_log_debug("Got EAGAIN on write() after POLLOUT, probably there is a temporary connection loss.");
/* Drain write buffer */
written = size;
} else {
pa_log_error("Failed to write data to socket: %s", pa_cstrerror(errno));
/* Report error from write call */
return -1;
}
}
/* if too much data left discard it all */
if (size - written >= write_mtu) {
pa_log_warn("Wrote memory block to socket only partially! %lu written, discarding pending write size %lu larger than write_mtu %lu",
written, size, write_mtu);
/* Drain write buffer */
written = size;
}
return written;
}
static pa_dbus_pending* hf_dbus_send_and_add_to_pending(pa_bluetooth_backend *backend, DBusMessage *m,
DBusPendingCallNotifyFunction func, void *call_data) {
pa_dbus_pending *p;
......@@ -354,6 +400,7 @@ static void hf_audio_agent_card_found(pa_bluetooth_backend *backend, const char
card->transport = pa_bluetooth_transport_new(d, backend->ofono_bus_id, path, p, NULL, 0);
card->transport->acquire = hf_audio_agent_transport_acquire;
card->transport->release = hf_audio_agent_transport_release;
card->transport->write = sco_transport_write;
card->transport->userdata = card;
pa_bluetooth_transport_put(card->transport);
......
......@@ -22,11 +22,14 @@
#include <config.h>
#endif
#include <errno.h>
#include <pulse/rtclock.h>
#include <pulse/timeval.h>
#include <pulse/xmalloc.h>
#include <pulsecore/core.h>
#include <pulsecore/core-error.h>
#include <pulsecore/core-util.h>
#include <pulsecore/dbus-shared.h>
#include <pulsecore/log.h>
......@@ -582,6 +585,45 @@ static void bluez5_transport_release_cb(pa_bluetooth_transport *t) {
pa_log_info("Transport %s released", t->path);
}
static ssize_t a2dp_transport_write(pa_bluetooth_transport *t, int fd, const void* buffer, size_t size, size_t write_mtu) {
ssize_t l = 0;
size_t written = 0;
size_t write_size;
pa_assert(t);
while (written < size) {
write_size = PA_MIN(size - written, write_mtu);
l = pa_write(fd, buffer + written, write_size, &t->stream_write_type);
if (l < 0)
break;
written += l;
}
if (l < 0) {
if (errno == EAGAIN) {
/* Hmm, apparently the socket was not writable, give up for now */
pa_log_debug("Got EAGAIN on write() after POLLOUT, probably there is a temporary connection loss.");
/* Drain write buffer */
written = size;
} else {
pa_log_error("Failed to write data to socket: %s", pa_cstrerror(errno));
/* Report error from write call */
return -1;
}
}
/* if too much data left discard it all */
if (size - written >= write_mtu) {
pa_log_warn("Wrote memory block to socket only partially! %lu written, discarding pending write size %lu larger than write_mtu %lu",
written, size, write_mtu);
/* Drain write buffer */
written = size;
}
return written;
}
bool pa_bluetooth_device_any_transport_connected(const pa_bluetooth_device *d) {
unsigned i;
......@@ -1906,6 +1948,7 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
t->a2dp_codec = a2dp_codec;
t->acquire = bluez5_transport_acquire_cb;
t->release = bluez5_transport_release_cb;
t->write = a2dp_transport_write;
pa_bluetooth_transport_put(t);
pa_log_debug("Transport %s available for profile %s", t->path, pa_bluetooth_profile_to_string(t->profile));
......
......@@ -88,6 +88,7 @@ typedef int (*pa_bluetooth_transport_acquire_cb)(pa_bluetooth_transport *t, bool
typedef void (*pa_bluetooth_transport_release_cb)(pa_bluetooth_transport *t);
typedef void (*pa_bluetooth_transport_destroy_cb)(pa_bluetooth_transport *t);
typedef pa_volume_t (*pa_bluetooth_transport_set_volume_cb)(pa_bluetooth_transport *t, pa_volume_t volume);
typedef ssize_t (*pa_bluetooth_transport_write_cb)(pa_bluetooth_transport *t, int fd, const void* buffer, size_t size, size_t write_mtu);
struct pa_bluetooth_transport {
pa_bluetooth_device *device;
......@@ -101,6 +102,7 @@ struct pa_bluetooth_transport {
size_t config_size;
const pa_a2dp_codec *a2dp_codec;
int stream_write_type;
pa_volume_t source_volume;
pa_volume_t sink_volume;
......@@ -109,6 +111,7 @@ struct pa_bluetooth_transport {
pa_bluetooth_transport_acquire_cb acquire;
pa_bluetooth_transport_release_cb release;
pa_bluetooth_transport_write_cb write;
pa_bluetooth_transport_destroy_cb destroy;
pa_bluetooth_transport_set_volume_cb set_sink_volume;
pa_bluetooth_transport_set_volume_cb set_source_volume;
......
......@@ -128,7 +128,6 @@ struct userdata {
bluetooth_msg *msg;
int stream_fd;
int stream_write_type;
size_t read_link_mtu;
size_t write_link_mtu;
size_t read_block_size;
......@@ -139,12 +138,13 @@ struct userdata {
pa_smoother *read_smoother;
pa_memchunk write_memchunk;
const pa_a2dp_codec *a2dp_codec;
const pa_a2dp_codec *bt_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 */
size_t encoder_buffer_used; /* Used space in the buffer */
void *decoder_info;
pa_sample_spec decoder_sample_spec;
......@@ -255,69 +255,107 @@ static void connect_ports(struct userdata *u, void *new_data, pa_direction_t dir
}
}
static void bt_prepare_encoder_buffer(struct userdata *u)
{
size_t size;
pa_assert(u);
pa_assert(u->bt_codec);
/* If socket write MTU is less than encoded frame size, there could be
* up to one write MTU of data left in encoder buffer from previous round.
*
* Reserve space for 2 encoded frames to cover that.
*
* Note for A2DP codecs it is expected that size of encoded frame is less
* than write link MTU therefore each encoded frame is sent out completely.
*/
if (u->bt_codec->get_encoded_block_size)
size = 2 * u->bt_codec->get_encoded_block_size(u->encoder_info, u->write_block_size);
else
size = 2 * u->write_block_size;
if (u->encoder_buffer_size < size) {
u->encoder_buffer = pa_xrealloc(u->encoder_buffer, size);
u->encoder_buffer_size = size;
}
}
/* Run from IO thread */
static int sco_process_render(struct userdata *u) {
ssize_t l;
pa_memchunk memchunk;
int saved_errno;
static int bt_write_buffer(struct userdata *u) {
ssize_t written = 0;
pa_assert(u);
pa_assert(u->profile == PA_BLUETOOTH_PROFILE_HSP_HS ||
u->profile == PA_BLUETOOTH_PROFILE_HSP_AG ||
u->profile == PA_BLUETOOTH_PROFILE_HFP_HF ||
u->profile == PA_BLUETOOTH_PROFILE_HFP_AG);
pa_assert(u->sink);
pa_assert(u->transport);
pa_sink_render_full(u->sink, u->write_block_size, &memchunk);
written = u->transport->write(u->transport, u->stream_fd, u->encoder_buffer, u->encoder_buffer_used, u->write_link_mtu);
pa_assert(memchunk.length == u->write_block_size);
if (written < 0)
return -1;
for (;;) {
const void *p;
/* calculate remainder */
u->encoder_buffer_used -= written;
/* Now write that data to the socket. The socket is of type
* SEQPACKET, and we generated the data of the MTU size, so this
* should just work. */
/* move any remainder back to start of u->encoder_buffer */
if (u->encoder_buffer_used)
memmove(u->encoder_buffer, u->encoder_buffer + written, u->encoder_buffer_used);
p = (const uint8_t *) pa_memblock_acquire_chunk(&memchunk);
l = pa_write(u->stream_fd, p, memchunk.length, &u->stream_write_type);
pa_memblock_release(memchunk.memblock);
return 1;
}
pa_assert(l != 0);
/* Run from IO thread */
static int bt_process_render(struct userdata *u) {
int ret;
if (l > 0)
break;
const uint8_t *ptr;
size_t processed;
size_t length;
saved_errno = errno;
pa_assert(u);
pa_assert(u->sink);
pa_assert(u->bt_codec);
pa_memblock_unref(memchunk.memblock);
/* First, render some data */
if (!u->write_memchunk.memblock)
pa_sink_render_full(u->sink, u->write_block_size, &u->write_memchunk);
if (saved_errno == EAGAIN) {
/* Hmm, apparently the socket was not writable, give up for now.
* Because the data was already rendered, let's discard the block. */
pa_log_debug("Got EAGAIN on write() after POLLOUT, probably there is a temporary connection loss.");
return 1;
}
pa_assert(u->write_memchunk.length == u->write_block_size);
pa_log_error("Failed to write data to SCO socket: %s", pa_cstrerror(saved_errno));
return -1;
}
bt_prepare_encoder_buffer(u);
pa_assert((size_t) l <= memchunk.length);
ptr = (const uint8_t *) pa_memblock_acquire_chunk(&u->write_memchunk);
if ((size_t) l != memchunk.length) {
pa_log_error("Wrote memory block to socket only partially! %llu written, wanted to write %llu.",
(unsigned long long) l,
(unsigned long long) memchunk.length);
length = u->bt_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_used, u->encoder_buffer_size - u->encoder_buffer_used, &processed);
pa_memblock_unref(memchunk.memblock);
pa_memblock_release(u->write_memchunk.memblock);
if (processed != u->write_memchunk.length) {
pa_log_error("Encoding error");
return -1;
}
u->write_index += (uint64_t) memchunk.length;
pa_memblock_unref(memchunk.memblock);
/* Encoder function of BT codec may provide empty buffer, in this case do
* not post any empty buffer via BT socket. It may be because of codec
* internal state, e.g. encoder is waiting for more samples so it can
* provide encoded data. */
return 1;
if (PA_LIKELY(length)) {
u->encoder_buffer_used += length;
ret = bt_write_buffer(u);
if (ret < 0) {
/* Reset encoder sequence number and buffer positions */
u->bt_codec->reset(u->encoder_info);
u->encoder_buffer_used = 0;
}
} else
ret = 0;
u->write_index += (uint64_t) u->write_memchunk.length;
pa_memblock_unref(u->write_memchunk.memblock);
pa_memchunk_reset(&u->write_memchunk);
return ret;
}
/* Run from IO thread */
......@@ -447,97 +485,6 @@ static void a2dp_prepare_decoder_buffer(struct userdata *u) {
u->decoder_buffer_size = u->read_link_mtu;
}
/* Run from IO thread */
static int a2dp_write_buffer(struct userdata *u, size_t nbytes) {
int ret = 0;
/* Encoder function of A2DP codec may provide empty buffer, in this case do
* not post any empty buffer via A2DP socket. It may be because of codec
* internal state, e.g. encoder is waiting for more samples so it can
* provide encoded data. */
if (PA_UNLIKELY(!nbytes)) {
u->write_index += (uint64_t) u->write_memchunk.length;
pa_memblock_unref(u->write_memchunk.memblock);
pa_memchunk_reset(&u->write_memchunk);
return 0;
}
for (;;) {
ssize_t l;
l = pa_write(u->stream_fd, u->encoder_buffer, nbytes, &u->stream_write_type);
pa_assert(l != 0);
if (l < 0) {
if (errno == EAGAIN) {
/* Hmm, apparently the socket was not writable, give up for now */
pa_log_debug("Got EAGAIN on write() after POLLOUT, probably there is a temporary connection loss.");
break;
}
pa_log_error("Failed to write data to socket: %s", pa_cstrerror(errno));
ret = -1;
break;
}
pa_assert((size_t) l <= nbytes);
if ((size_t) l != nbytes) {
pa_log_warn("Wrote memory block to socket only partially! %llu written, wanted to write %llu.",
(unsigned long long) l,
(unsigned long long) nbytes);
ret = -1;
break;
}
u->write_index += (uint64_t) u->write_memchunk.length;
pa_memblock_unref(u->write_memchunk.memblock);
pa_memchunk_reset(&u->write_memchunk);
ret = 1;
break;
}
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);