diff options
Diffstat (limited to '')
-rw-r--r-- | spa/plugins/bluez5/sco-io.c | 289 |
1 files changed, 289 insertions, 0 deletions
diff --git a/spa/plugins/bluez5/sco-io.c b/spa/plugins/bluez5/sco-io.c new file mode 100644 index 0000000..0657750 --- /dev/null +++ b/spa/plugins/bluez5/sco-io.c @@ -0,0 +1,289 @@ +/* Spa SCO I/O + * + * Copyright © 2019 Collabora Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <unistd.h> +#include <stddef.h> +#include <stdio.h> +#include <arpa/inet.h> +#include <sys/ioctl.h> + +#include <spa/support/plugin.h> +#include <spa/support/loop.h> +#include <spa/support/log.h> +#include <spa/support/system.h> +#include <spa/utils/list.h> +#include <spa/utils/keys.h> +#include <spa/utils/names.h> +#include <spa/monitor/device.h> + +#include <spa/node/node.h> +#include <spa/node/utils.h> +#include <spa/node/io.h> +#include <spa/node/keys.h> +#include <spa/param/param.h> +#include <spa/param/audio/format.h> +#include <spa/param/audio/format-utils.h> +#include <spa/pod/filter.h> + +#include <sbc/sbc.h> + +#include "defs.h" + + +/* We'll use the read rx data size to find the correct packet size for writing, + * since kernel might not report it as the socket MTU, see + * https://lore.kernel.org/linux-bluetooth/20201210003528.3pmaxvubiwegxmhl@pali/T/ + * + * We continue reading also when there's no source connected, to keep socket + * flushed. + * + * XXX: when the kernel/backends start giving the right values, the heuristic + * XXX: can be removed + */ +#define MAX_MTU 1024 + + +struct spa_bt_sco_io { + bool started; + + uint8_t read_buffer[MAX_MTU]; + uint32_t read_size; + + int fd; + uint16_t read_mtu; + uint16_t write_mtu; + + struct spa_loop *data_loop; + struct spa_source source; + + int (*source_cb)(void *userdata, uint8_t *data, int size); + void *source_userdata; + + int (*sink_cb)(void *userdata); + void *sink_userdata; +}; + + +static void update_source(struct spa_bt_sco_io *io) +{ + int enabled; + int changed = 0; + + enabled = io->sink_cb != NULL; + if (SPA_FLAG_IS_SET(io->source.mask, SPA_IO_OUT) != enabled) { + SPA_FLAG_UPDATE(io->source.mask, SPA_IO_OUT, enabled); + changed = 1; + } + + if (changed) { + spa_loop_update_source(io->data_loop, &io->source); + } +} + +static void sco_io_on_ready(struct spa_source *source) +{ + struct spa_bt_sco_io *io = source->data; + + if (SPA_FLAG_IS_SET(source->rmask, SPA_IO_IN)) { + int res; + + read_again: + res = read(io->fd, io->read_buffer, SPA_MIN(io->read_mtu, MAX_MTU)); + if (res <= 0) { + if (errno == EINTR) { + /* retry if interrupted */ + goto read_again; + } else if (errno == EAGAIN || errno == EWOULDBLOCK) { + /* no data: try it next time */ + goto read_done; + } + + /* error */ + goto stop; + } + + io->read_size = res; + + if (io->source_cb) { + int res; + res = io->source_cb(io->source_userdata, io->read_buffer, io->read_size); + if (res) { + io->source_cb = NULL; + } + } + } + +read_done: + if (SPA_FLAG_IS_SET(source->rmask, SPA_IO_OUT)) { + if (io->sink_cb) { + int res; + res = io->sink_cb(io->sink_userdata); + if (res) { + io->sink_cb = NULL; + } + } + } + + if (SPA_FLAG_IS_SET(source->rmask, SPA_IO_ERR) || SPA_FLAG_IS_SET(source->rmask, SPA_IO_HUP)) { + goto stop; + } + + /* Poll socket in/out only if necessary */ + update_source(io); + + return; + +stop: + if (io->source.loop) { + spa_loop_remove_source(io->data_loop, &io->source); + io->started = false; + } +} + +/* + * Write data to socket in correctly sized blocks. + * Returns the number of bytes written, 0 when data cannot be written now or + * there is too little of it to write, and <0 on write error. + */ +int spa_bt_sco_io_write(struct spa_bt_sco_io *io, uint8_t *buf, int size) +{ + uint16_t packet_size; + uint8_t *buf_start = buf; + + if (io->read_size == 0) { + /* The proper write packet size is not known yet */ + return 0; + } + + packet_size = SPA_MIN(io->write_mtu, io->read_size); + + if (size < packet_size) { + return 0; + } + + do { + int written; + + written = write(io->fd, buf, packet_size); + if (written < 0) { + if (errno == EINTR) { + /* retry if interrupted */ + continue; + } else if (errno == EAGAIN || errno == EWOULDBLOCK) { + /* Don't continue writing */ + break; + } + return -errno; + } + + buf += written; + size -= written; + } while (size >= packet_size); + + return buf - buf_start; +} + + +struct spa_bt_sco_io *spa_bt_sco_io_create(struct spa_loop *data_loop, + int fd, + uint16_t read_mtu, + uint16_t write_mtu) +{ + struct spa_bt_sco_io *io; + + io = calloc(1, sizeof(struct spa_bt_sco_io)); + if (io == NULL) + return io; + + io->fd = fd; + io->read_mtu = read_mtu; + io->write_mtu = write_mtu; + io->data_loop = data_loop; + + io->read_size = 0; + + /* Add the ready callback */ + io->source.data = io; + io->source.fd = io->fd; + io->source.func = sco_io_on_ready; + io->source.mask = SPA_IO_IN | SPA_IO_OUT | SPA_IO_ERR | SPA_IO_HUP; + io->source.rmask = 0; + spa_loop_add_source(io->data_loop, &io->source); + + io->started = true; + + return io; +} + +static int do_remove_source(struct spa_loop *loop, + bool async, + uint32_t seq, + const void *data, + size_t size, + void *user_data) +{ + struct spa_bt_sco_io *io = user_data; + + if (io->source.loop) + spa_loop_remove_source(io->data_loop, &io->source); + + return 0; +} + +void spa_bt_sco_io_destroy(struct spa_bt_sco_io *io) +{ + if (io->started) + spa_loop_invoke(io->data_loop, do_remove_source, 0, NULL, 0, true, io); + + io->started = false; + free(io); +} + +/* Set source callback. + * This function should only be called from the data thread. + * Callback is called (in data loop) with data just read from the socket. + */ +void spa_bt_sco_io_set_source_cb(struct spa_bt_sco_io *io, int (*source_cb)(void *, uint8_t *, int), void *userdata) +{ + io->source_cb = source_cb; + io->source_userdata = userdata; + + if (io->started) { + update_source(io); + } +} + +/* Set sink callback. + * This function should only be called from the data thread. + * Callback is called (in data loop) when socket can be written to. + */ +void spa_bt_sco_io_set_sink_cb(struct spa_bt_sco_io *io, int (*sink_cb)(void *), void *userdata) +{ + io->sink_cb = sink_cb; + io->sink_userdata = userdata; + + if (io->started) { + update_source(io); + } +} |