diff options
9 files changed, 289 insertions, 2826 deletions
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0016-a2dpsink-fix-infinite-loop-when-buffer-could-not-be-.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0016-a2dpsink-fix-infinite-loop-when-buffer-could-not-be-.patch deleted file mode 100644 index d747a7a9..00000000 --- a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0016-a2dpsink-fix-infinite-loop-when-buffer-could-not-be-.patch +++ /dev/null @@ -1,32 +0,0 @@ -From c186e40905f78f41cbc015da0e204735a0398450 Mon Sep 17 00:00:00 2001 -From: Julian Bouzas <julian.bouzas@collabora.com> -Date: Fri, 19 Jul 2019 08:38:21 -0400 -Subject: [PATCH] a2dpsink: fix infinite loop when buffer could not be encoded - -Upstream-Status: Backport [4b202b965665bbcb55194b4ab827984e5804e3e0] ---- - spa/plugins/bluez5/a2dp-sink.c | 7 ++++++- - 1 file changed, 6 insertions(+), 1 deletion(-) - -diff --git a/spa/plugins/bluez5/a2dp-sink.c b/spa/plugins/bluez5/a2dp-sink.c -index 731577e5..d6d9e7d6 100644 ---- a/spa/plugins/bluez5/a2dp-sink.c -+++ b/spa/plugins/bluez5/a2dp-sink.c -@@ -558,8 +558,13 @@ static int flush_data(struct impl *this, uint64_t now_time) - n_bytes = add_data(this, src + offs, l0); - if (n_bytes > 0 && l1 > 0) - n_bytes += add_data(this, src, l1); -- if (n_bytes <= 0) -+ if (n_bytes <= 0) { -+ spa_list_remove(&b->link); -+ b->outstanding = true; -+ spa_node_call_reuse_buffer(&this->callbacks, 0, b->id); -+ port->ready_offset = 0; - break; -+ } - - n_frames = n_bytes / port->frame_size; - --- -2.23.0.rc1 - diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0021-gst-pwaudioringbuffer-set-node.latency-to-get-schedu.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0016-gst-pwaudioringbuffer-set-node.latency-to-get-schedu.patch index 9b51a297..b97e21ff 100644 --- a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0021-gst-pwaudioringbuffer-set-node.latency-to-get-schedu.patch +++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0016-gst-pwaudioringbuffer-set-node.latency-to-get-schedu.patch @@ -1,4 +1,4 @@ -From 851738e3c5970a699d2313dec1cfeedb9d051d83 Mon Sep 17 00:00:00 2001 +From 3af64cf4e1d33c33a9757c0f30c7de1068202540 Mon Sep 17 00:00:00 2001 From: George Kiagiadakis <george.kiagiadakis@collabora.com> Date: Tue, 20 Aug 2019 18:33:35 +0300 Subject: [PATCH] gst: pwaudioringbuffer: set node.latency to get scheduled @@ -37,5 +37,5 @@ index b92b5feb..2314dd77 100644 pw_thread_loop_lock (self->main_loop); -- -2.23.0.rc1 +2.23.0 diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0017-bluez5-add-sco-sink-and-sco-src-nodes.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0017-bluez5-add-sco-sink-and-sco-src-nodes.patch deleted file mode 100644 index 389ebbda..00000000 --- a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0017-bluez5-add-sco-sink-and-sco-src-nodes.patch +++ /dev/null @@ -1,2620 +0,0 @@ -From c1d18d46b48d056b6458ec6ac1075cb74411145a Mon Sep 17 00:00:00 2001 -From: Julian Bouzas <julian.bouzas@collabora.com> -Date: Tue, 23 Jul 2019 12:52:46 -0400 -Subject: [PATCH] bluez5: add sco-sink and sco-src nodes - -Upstream-Status: Backport [de031b42b1d5e89dfb23d39317955c4b56f17c1b] ---- - spa/plugins/bluez5/bluez5-device.c | 140 +--- - spa/plugins/bluez5/bluez5-monitor.c | 24 +- - spa/plugins/bluez5/meson.build | 2 + - spa/plugins/bluez5/sco-sink.c | 1200 +++++++++++++++++++++++++++ - spa/plugins/bluez5/sco-source.c | 1136 +++++++++++++++++++++++++ - 5 files changed, 2396 insertions(+), 106 deletions(-) - create mode 100644 spa/plugins/bluez5/sco-sink.c - create mode 100644 spa/plugins/bluez5/sco-source.c - -diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c -index d6ea467e..40a340c9 100644 ---- a/spa/plugins/bluez5/bluez5-device.c -+++ b/spa/plugins/bluez5/bluez5-device.c -@@ -44,6 +44,8 @@ - - extern const struct spa_handle_factory spa_a2dp_source_factory; - extern const struct spa_handle_factory spa_a2dp_sink_factory; -+extern const struct spa_handle_factory spa_sco_sink_factory; -+extern const struct spa_handle_factory spa_sco_source_factory; - - static const char default_device[] = ""; - -@@ -68,117 +70,61 @@ struct impl { - struct props props; - - struct spa_bt_device *bt_dev; -+ -+ uint32_t next_id; - }; - --static int emit_source_node(struct impl *this) -+static void emit_node (struct impl *this, struct spa_bt_transport *t, const struct spa_handle_factory *factory) - { -- struct spa_dict_item items[1]; -- struct spa_bt_transport *t; -- struct spa_bt_device *device = this->bt_dev; -- enum spa_bt_profile profile = SPA_BT_PROFILE_NULL; -- -- if (device->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE) { -- spa_log_info(this->log, "A2DP (source) profile found"); -- profile = SPA_BT_PROFILE_A2DP_SOURCE; -- } else if (device->connected_profiles & SPA_BT_PROFILE_HSP_HS) { -- spa_log_info(this->log, "HSP (source) profile found (Not implemented yet)"); -- profile = SPA_BT_PROFILE_HSP_HS; -- return -ENODEV; -- } else if (device->connected_profiles & SPA_BT_PROFILE_HFP_HF) { -- spa_log_info(this->log, "HFP (source) profile found (Not implemented yet)"); -- profile = SPA_BT_PROFILE_HFP_HF; -- return -ENODEV; -- } -- -- /* Return if no profiles are connected */ -- if (profile == SPA_BT_PROFILE_NULL) -- return -ENODEV; -- -- spa_list_for_each(t, &device->transport_list, device_link) { -- if (t->profile == profile) { -- struct spa_device_object_info info; -- char transport[16]; -- -- snprintf(transport, 16, "%p", t); -- items[0] = SPA_DICT_ITEM_INIT("bluez5.transport", transport); -- -- spa_bt_transport_acquire(t, true); -- -- info = SPA_DEVICE_OBJECT_INFO_INIT(); -- info.type = SPA_TYPE_INTERFACE_Node; -- info.factory = &spa_a2dp_source_factory; -- info.change_mask = SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS; -- info.props = &SPA_DICT_INIT_ARRAY(items); -- -- spa_device_emit_object_info(&this->hooks, 0, &info); -- break; -- } -- } -- -- spa_log_info (this->log, "bluez5 source nodes emitted"); -- return 0; -+ struct spa_device_object_info info; -+ struct spa_dict_item items[1]; -+ char transport[16]; -+ -+ /* Set the info */ -+ info = SPA_DEVICE_OBJECT_INFO_INIT(); -+ info.type = SPA_TYPE_INTERFACE_Node; -+ info.factory = factory; -+ info.change_mask = SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS; -+ -+ /* Pass the transport pointer as a property */ -+ snprintf(transport, 16, "%p", t); -+ items[0] = SPA_DICT_ITEM_INIT("bluez5.transport", transport); -+ info.props = &SPA_DICT_INIT_ARRAY(items); -+ -+ /* Emit the node */ -+ spa_device_emit_object_info(&this->hooks, this->next_id++, &info); - } - --static int emit_sink_node(struct impl *this) -+static int emit_nodes(struct impl *this) - { -- struct spa_dict_item items[1]; -- struct spa_bt_transport *t; - struct spa_bt_device *device = this->bt_dev; -- enum spa_bt_profile profile = SPA_BT_PROFILE_NULL; -- -- if (device->connected_profiles & SPA_BT_PROFILE_A2DP_SINK) { -- spa_log_info(this->log, "A2DP (sink) profile found"); -- profile = SPA_BT_PROFILE_A2DP_SINK; -- } else if (device->connected_profiles & SPA_BT_PROFILE_HSP_AG) { -- spa_log_info(this->log, "HSP (sink) profile found (Not implemented yet)"); -- profile = SPA_BT_PROFILE_HSP_AG; -- return -ENODEV; -- } else if (device->connected_profiles & SPA_BT_PROFILE_HFP_AG) { -- spa_log_info(this->log, "HFP (sink) profile found (Not implemented yet)"); -- profile = SPA_BT_PROFILE_HFP_AG; -- return -ENODEV; -- } -- -- /* Return if no profiles are connected */ -- if (profile == SPA_BT_PROFILE_NULL) -- return -ENODEV; -+ struct spa_bt_transport *t; - - spa_list_for_each(t, &device->transport_list, device_link) { -- if (t->profile == profile) { -- struct spa_device_object_info info; -- char transport[16]; -- -- snprintf(transport, 16, "%p", t); -- items[0] = SPA_DICT_ITEM_INIT("bluez5.transport", transport); -- -- info = SPA_DEVICE_OBJECT_INFO_INIT(); -- info.type = SPA_TYPE_INTERFACE_Node; -- info.factory = &spa_a2dp_sink_factory; -- info.change_mask = SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS; -- info.props = &SPA_DICT_INIT_ARRAY(items); -- -- spa_device_emit_object_info(&this->hooks, 0, &info); -- break; -+ if (t->profile & device->connected_profiles) { -+ switch (t->profile) { -+ case SPA_BT_PROFILE_A2DP_SOURCE: -+ emit_node (this, t, &spa_a2dp_source_factory); -+ break; -+ case SPA_BT_PROFILE_A2DP_SINK: -+ emit_node (this, t, &spa_a2dp_sink_factory); -+ break; -+ case SPA_BT_PROFILE_HSP_HS: -+ case SPA_BT_PROFILE_HSP_AG: -+ case SPA_BT_PROFILE_HFP_HF: -+ case SPA_BT_PROFILE_HFP_AG: -+ emit_node (this, t, &spa_sco_source_factory); -+ emit_node (this, t, &spa_sco_sink_factory); -+ break; -+ default: -+ return -EINVAL; -+ } - } - } - -- spa_log_info(this->log, "bluez5 sink nodes emitted"); - return 0; - } - --static int emit_nodes(struct impl *this) --{ -- int sink, src; -- -- sink = emit_sink_node(this); -- src = emit_source_node(this); -- -- if (sink == -ENODEV && src == -ENODEV) -- spa_log_warn(this->log, "no profile available"); -- -- return SPA_MAX(sink, src); --} -- - static const struct spa_dict_item info_items[] = { - { "media.class", "Audio/Device" }, - }; -@@ -314,6 +260,8 @@ impl_init(const struct spa_handle_factory *factory, - - reset_props(&this->props); - -+ this->next_id = 0; -+ - return 0; - } - -diff --git a/spa/plugins/bluez5/bluez5-monitor.c b/spa/plugins/bluez5/bluez5-monitor.c -index 5b8ff495..2a243715 100644 ---- a/spa/plugins/bluez5/bluez5-monitor.c -+++ b/spa/plugins/bluez5/bluez5-monitor.c -@@ -1392,7 +1392,10 @@ static void rfcomm_event(struct spa_source *source) - { - struct spa_bt_transport *t = source->data; - struct spa_bt_monitor *monitor = t->monitor; -+ char buf[512]; -+ ssize_t len; - -+ /* Check for errors */ - if (source->rmask & (SPA_IO_HUP | SPA_IO_ERR)) { - spa_log_info(monitor->log, "lost RFCOMM connection."); - if (source->loop) -@@ -1400,20 +1403,19 @@ static void rfcomm_event(struct spa_source *source) - goto fail; - } - -+ /* Read the command */ -+ len = read(source->fd, buf, 511); -+ if (len < 0) { -+ spa_log_error(monitor->log, "RFCOMM read error: %s", strerror(errno)); -+ goto fail; -+ } -+ buf[len] = 0; -+ printf ("RFCOMM AT COMMAND: %s\n", buf); -+ - if (source->rmask & SPA_IO_IN) { -- char buf[512]; -- ssize_t len; - int gain, dummy; - bool do_reply = false; - -- len = read(source->fd, buf, 511); -- if (len < 0) { -- spa_log_error(monitor->log, "RFCOMM read error: %s", strerror(errno)); -- goto fail; -- } -- buf[len] = 0; -- spa_log_debug(monitor->log, "RFCOMM << %s", buf); -- - /* There are only four HSP AT commands: - * AT+VGS=value: value between 0 and 15, sent by the HS to AG to set the speaker gain. - * +VGS=value is sent by AG to HS as a response to an AT+VGS command or when the gain -@@ -1669,12 +1671,14 @@ static int sco_destroy_cb(void *data) - spa_loop_remove_source(td->sco.loop, &td->sco); - shutdown(td->sco.fd, SHUT_RDWR); - close (td->sco.fd); -+ td->sco.fd = -1; - } - if (td->rfcomm.data) { - if (td->rfcomm.loop) - spa_loop_remove_source(td->rfcomm.loop, &td->rfcomm); - shutdown(td->rfcomm.fd, SHUT_RDWR); - close (td->rfcomm.fd); -+ td->rfcomm.fd = -1; - } - return 0; - } -diff --git a/spa/plugins/bluez5/meson.build b/spa/plugins/bluez5/meson.build -index 5fb285ec..ddcc74fa 100644 ---- a/spa/plugins/bluez5/meson.build -+++ b/spa/plugins/bluez5/meson.build -@@ -3,6 +3,8 @@ bluez5_sources = ['plugin.c', - 'a2dp-codecs.c', - 'a2dp-sink.c', - 'a2dp-source.c', -+ 'sco-sink.c', -+ 'sco-source.c', - 'bluez5-device.c', - 'bluez5-monitor.c'] - -diff --git a/spa/plugins/bluez5/sco-sink.c b/spa/plugins/bluez5/sco-sink.c -new file mode 100644 -index 00000000..52307e10 ---- /dev/null -+++ b/spa/plugins/bluez5/sco-sink.c -@@ -0,0 +1,1200 @@ -+/* Spa SCO Sink -+ * -+ * Copyright © 2018 Wim Taymans -+ * 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 <sys/timerfd.h> -+#include <arpa/inet.h> -+#include <sys/ioctl.h> -+ -+#include <spa/support/loop.h> -+#include <spa/support/log.h> -+#include <spa/utils/list.h> -+ -+#include <spa/node/node.h> -+#include <spa/node/io.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" -+ -+struct props { -+ uint32_t min_latency; -+ uint32_t max_latency; -+}; -+ -+#define MAX_BUFFERS 32 -+ -+struct buffer { -+ uint32_t id; -+ unsigned int outstanding:1; -+ struct spa_buffer *buf; -+ struct spa_meta_header *h; -+ struct spa_list link; -+}; -+ -+struct port { -+ struct spa_audio_info current_format; -+ int frame_size; -+ unsigned int have_format:1; -+ -+ uint64_t info_all; -+ struct spa_port_info info; -+ struct spa_io_buffers *io; -+ struct spa_param_info params[8]; -+ -+ struct buffer buffers[MAX_BUFFERS]; -+ uint32_t n_buffers; -+ -+ struct spa_list free; -+ struct spa_list ready; -+}; -+ -+struct impl { -+ struct spa_handle handle; -+ struct spa_node node; -+ -+ /* Support */ -+ struct spa_log *log; -+ struct spa_loop *main_loop; -+ struct spa_loop *data_loop; -+ -+ /* Hooks and callbacks */ -+ struct spa_hook_list hooks; -+ struct spa_callbacks callbacks; -+ -+ /* Info */ -+ uint64_t info_all; -+ struct spa_node_info info; -+ struct spa_param_info params[8]; -+ struct props props; -+ -+ /* Transport */ -+ struct spa_bt_transport *transport; -+ struct spa_hook transport_listener; -+ int sock_fd; -+ -+ /* Port */ -+ struct port port; -+ -+ /* Flags */ -+ unsigned int started:1; -+ unsigned int slaved:1; -+ -+ /* Sources */ -+ struct spa_source source; -+ struct spa_source flush_source; -+ -+ /* Timer */ -+ int timerfd; -+ struct timespec now; -+ struct spa_io_clock *clock; -+ struct spa_io_position *position; -+ int threshold; -+ -+ /* Times */ -+ uint64_t start_time; -+ -+ /* Counts */ -+ uint64_t sample_count; -+}; -+ -+#define NAME "sco-sink" -+ -+#define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) == 0) -+ -+static const uint32_t default_min_latency = 128; -+static const uint32_t default_max_latency = 1024; -+ -+static void reset_props(struct props *props) -+{ -+ props->min_latency = default_min_latency; -+ props->max_latency = default_max_latency; -+} -+ -+static int impl_node_enum_params(struct spa_node *node, int seq, -+ uint32_t id, uint32_t start, uint32_t num, -+ const struct spa_pod *filter) -+{ -+ struct impl *this; -+ struct spa_pod *param; -+ struct spa_pod_builder b = { 0 }; -+ uint8_t buffer[1024]; -+ struct spa_result_node_params result; -+ uint32_t count = 0; -+ -+ spa_return_val_if_fail(node != NULL, -EINVAL); -+ spa_return_val_if_fail(num != 0, -EINVAL); -+ -+ this = SPA_CONTAINER_OF(node, struct impl, node); -+ -+ result.id = id; -+ result.next = start; -+ next: -+ result.index = result.next++; -+ -+ spa_pod_builder_init(&b, buffer, sizeof(buffer)); -+ -+ switch (id) { -+ case SPA_PARAM_PropInfo: -+ { -+ struct props *p = &this->props; -+ -+ switch (result.index) { -+ case 0: -+ param = spa_pod_builder_add_object(&b, -+ SPA_TYPE_OBJECT_PropInfo, id, -+ SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_minLatency), -+ SPA_PROP_INFO_name, SPA_POD_String("The minimum latency"), -+ SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->min_latency, 1, INT32_MAX)); -+ break; -+ case 1: -+ param = spa_pod_builder_add_object(&b, -+ SPA_TYPE_OBJECT_PropInfo, id, -+ SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_maxLatency), -+ SPA_PROP_INFO_name, SPA_POD_String("The maximum latency"), -+ SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->max_latency, 1, INT32_MAX)); -+ break; -+ default: -+ return 0; -+ } -+ break; -+ } -+ case SPA_PARAM_Props: -+ { -+ struct props *p = &this->props; -+ -+ switch (result.index) { -+ case 0: -+ param = spa_pod_builder_add_object(&b, -+ SPA_TYPE_OBJECT_Props, id, -+ SPA_PROP_minLatency, SPA_POD_Int(p->min_latency), -+ SPA_PROP_maxLatency, SPA_POD_Int(p->max_latency)); -+ break; -+ default: -+ return 0; -+ } -+ break; -+ } -+ default: -+ return -ENOENT; -+ } -+ -+ if (spa_pod_filter(&b, &result.param, param, filter) < 0) -+ goto next; -+ -+ spa_node_emit_result(&this->hooks, seq, 0, &result); -+ -+ if (++count != num) -+ goto next; -+ -+ return 0; -+} -+ -+static void set_timeout(struct impl *this, time_t sec, long nsec) -+{ -+ struct itimerspec ts; -+ -+ ts.it_value.tv_sec = sec; -+ ts.it_value.tv_nsec = nsec; -+ ts.it_interval.tv_sec = 0; -+ ts.it_interval.tv_nsec = 0; -+ -+ timerfd_settime(this->timerfd, TFD_TIMER_ABSTIME, &ts, NULL); -+ this->source.mask = SPA_IO_IN; -+ spa_loop_update_source(this->data_loop, &this->source); -+} -+ -+static void reset_timeout(struct impl *this) -+{ -+ set_timeout(this, 0, this->slaved ? 0 : 1); -+} -+ -+ -+static void set_next_timeout(struct impl *this, uint64_t now_time) -+{ -+ struct port *port = &this->port; -+ -+ /* Set the next timeout if not slaved, otherwise reset values */ -+ if (!this->slaved) { -+ /* Get the elapsed time */ -+ const uint64_t elapsed_time = now_time - this->start_time; -+ -+ /* Get the elapsed samples */ -+ const uint64_t elapsed_samples = elapsed_time * port->current_format.info.raw.rate / SPA_NSEC_PER_SEC; -+ -+ /* Get the queued samples (processed - elapsed) */ -+ const uint64_t queued_samples = this->sample_count - elapsed_samples; -+ -+ /* Get the queued time */ -+ const uint64_t queued_time = (queued_samples * SPA_NSEC_PER_SEC) / port->current_format.info.raw.rate; -+ -+ /* Get the next time */ -+ const uint64_t next_time = now_time + queued_time; -+ -+ /* Set the next timeout */ -+ set_timeout (this, next_time / SPA_NSEC_PER_SEC, next_time % SPA_NSEC_PER_SEC); -+ } else { -+ this->start_time = now_time; -+ this->sample_count = 0; -+ } -+} -+ -+static int do_reslave(struct spa_loop *loop, -+ bool async, -+ uint32_t seq, -+ const void *data, -+ size_t size, -+ void *user_data) -+{ -+ struct impl *this = user_data; -+ reset_timeout(this); -+ return 0; -+} -+ -+static inline bool is_slaved(struct impl *this) -+{ -+ return this->position && this->clock && this->position->clock.id != this->clock->id; -+} -+ -+static int impl_node_set_io(struct spa_node *node, uint32_t id, void *data, size_t size) -+{ -+ struct impl *this; -+ bool slaved; -+ -+ spa_return_val_if_fail(node != NULL, -EINVAL); -+ -+ this = SPA_CONTAINER_OF(node, struct impl, node); -+ -+ switch (id) { -+ case SPA_IO_Clock: -+ this->clock = data; -+ break; -+ case SPA_IO_Position: -+ this->position = data; -+ break; -+ default: -+ return -ENOENT; -+ } -+ -+ slaved = is_slaved(this); -+ if (this->started && slaved != this->slaved) { -+ spa_log_debug(this->log, "sco-sink %p: reslave %d->%d", this, this->slaved, slaved); -+ this->slaved = slaved; -+ spa_loop_invoke(this->data_loop, do_reslave, 0, NULL, 0, true, this); -+ } -+ return 0; -+} -+ -+static int impl_node_set_param(struct spa_node *node, uint32_t id, uint32_t flags, -+ const struct spa_pod *param) -+{ -+ struct impl *this; -+ -+ spa_return_val_if_fail(node != NULL, -EINVAL); -+ -+ this = SPA_CONTAINER_OF(node, struct impl, node); -+ -+ switch (id) { -+ case SPA_PARAM_Props: -+ { -+ struct props *p = &this->props; -+ -+ if (param == NULL) { -+ reset_props(p); -+ return 0; -+ } -+ spa_pod_parse_object(param, -+ SPA_TYPE_OBJECT_Props, NULL, -+ SPA_PROP_minLatency, SPA_POD_OPT_Int(&p->min_latency), -+ SPA_PROP_maxLatency, SPA_POD_OPT_Int(&p->max_latency)); -+ break; -+ } -+ default: -+ return -ENOENT; -+ } -+ -+ return 0; -+} -+ -+static bool write_data(struct impl *this, uint8_t *data, uint32_t size, uint32_t *total_written) -+{ -+ uint32_t local_total_written = 0; -+ const uint32_t mtu_size = this->transport->write_mtu; -+ -+ /* TODO: For now we assume the size is always a mutliple of mtu_size */ -+ while (local_total_written < (size - mtu_size)) { -+ const int bytes_written = write(this->sock_fd, data, mtu_size); -+ if (bytes_written < 0) { -+ spa_log_warn(this->log, "error writting data: %s", strerror(errno)); -+ return false; -+ } -+ -+ data += bytes_written; -+ local_total_written += bytes_written; -+ } -+ -+ if (total_written) -+ *total_written = local_total_written; -+ return true; -+} -+ -+static int render_buffers(struct impl *this, uint64_t now_time) -+{ -+ struct port *port = &this->port; -+ -+ /* Render the buffer */ -+ while (!spa_list_is_empty(&port->ready)) { -+ uint8_t *src; -+ struct buffer *b; -+ struct spa_data *d; -+ uint32_t offset, size; -+ uint32_t total_written = 0; -+ -+ /* Get the buffer and datas */ -+ b = spa_list_first(&port->ready, struct buffer, link); -+ d = b->buf->datas; -+ -+ /* Get the data, offset and size */ -+ src = d[0].data; -+ offset = d[0].chunk->offset; -+ size = d[0].chunk->size; -+ -+ /* Write data */ -+ write_data(this, src + offset, size, &total_written); -+ -+ /* Update the cample count */ -+ this->sample_count += total_written / port->frame_size; -+ -+ /* Remove the buffer and mark it as reusable */ -+ spa_list_remove(&b->link); -+ b->outstanding = true; -+ spa_node_call_reuse_buffer(&this->callbacks, 0, b->id); -+ } -+ -+ /* Set next timeout */ -+ set_next_timeout(this, now_time); -+ -+ return 0; -+} -+ -+static void sco_on_flush(struct spa_source *source) -+{ -+ struct impl *this = source->data; -+ uint64_t now_time; -+ -+ if ((source->rmask & SPA_IO_OUT) == 0) { -+ spa_log_warn(this->log, "error %d", source->rmask); -+ if (this->flush_source.loop) -+ spa_loop_remove_source(this->data_loop, &this->flush_source); -+ this->source.mask = 0; -+ spa_loop_update_source(this->data_loop, &this->source); -+ return; -+ } -+ -+ /* Get the current time */ -+ clock_gettime(CLOCK_MONOTONIC, &this->now); -+ now_time = SPA_TIMESPEC_TO_NSEC(&this->now); -+ -+ /* Render buffers */ -+ render_buffers(this, now_time); -+} -+ -+static void sco_on_timeout(struct spa_source *source) -+{ -+ struct impl *this = source->data; -+ struct port *port = &this->port; -+ uint64_t exp, now_time; -+ struct spa_io_buffers *io = port->io; -+ -+ /* Read the timerfd */ -+ if (this->started && read(this->timerfd, &exp, sizeof(uint64_t)) != sizeof(uint64_t)) -+ spa_log_warn(this->log, "error reading timerfd: %s", strerror(errno)); -+ -+ /* Get the current time */ -+ clock_gettime(CLOCK_MONOTONIC, &this->now); -+ now_time = SPA_TIMESPEC_TO_NSEC(&this->now); -+ -+ /* Set the start time to the current time */ -+ if (this->start_time == 0) -+ this->start_time = now_time; -+ -+ /* Notify we need a new buffer if we have processed all of them */ -+ if (spa_list_is_empty(&port->ready)) { -+ io->status = SPA_STATUS_NEED_BUFFER; -+ spa_node_call_ready(&this->callbacks, SPA_STATUS_NEED_BUFFER); -+ } -+ -+ /* Render the buffers */ -+ render_buffers(this, now_time); -+} -+ -+static int do_start(struct impl *this) -+{ -+ int val; -+ bool do_accept; -+ -+ /* Dont do anything if the node has already started */ -+ if (this->started) -+ return 0; -+ -+ /* Make sure the transport is valid */ -+ spa_return_val_if_fail (this->transport != NULL, -EIO); -+ -+ /* Set the slaved flag */ -+ this->slaved = is_slaved(this); -+ -+ /* Do accept if Gateway; otherwise do connect for Head Unit */ -+ do_accept = this->transport->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY; -+ -+ /* acquire the socked fd (false -> connect | true -> accept) */ -+ this->sock_fd = spa_bt_transport_acquire(this->transport, do_accept); -+ if (this->sock_fd < 0) -+ return -1; -+ -+ /* Set the write MTU */ -+ val = this->transport->write_mtu; -+ if (setsockopt(this->sock_fd, SOL_SOCKET, SO_SNDBUF, &val, sizeof(val)) < 0) -+ spa_log_warn(this->log, "sco-sink %p: SO_SNDBUF %m", this); -+ -+ /* Set the read MTU */ -+ val = this->transport->read_mtu; -+ if (setsockopt(this->sock_fd, SOL_SOCKET, SO_RCVBUF, &val, sizeof(val)) < 0) -+ spa_log_warn(this->log, "sco-sink %p: SO_RCVBUF %m", this); -+ -+ /* Set the priority */ -+ val = 6; -+ if (setsockopt(this->sock_fd, SOL_SOCKET, SO_PRIORITY, &val, sizeof(val)) < 0) -+ spa_log_warn(this->log, "SO_PRIORITY failed: %m"); -+ -+ /* Add the timeout callback */ -+ this->source.data = this; -+ this->source.fd = this->timerfd; -+ this->source.func = sco_on_timeout; -+ this->source.mask = SPA_IO_IN; -+ this->source.rmask = 0; -+ spa_loop_add_source(this->data_loop, &this->source); -+ -+ /* Add the flush callback */ -+ this->flush_source.data = this; -+ this->flush_source.fd = this->sock_fd; -+ this->flush_source.func = sco_on_flush; -+ this->flush_source.mask = 0; -+ this->flush_source.rmask = 0; -+ spa_loop_add_source(this->data_loop, &this->flush_source); -+ -+ /* Reset timeout to start processing */ -+ reset_timeout(this); -+ -+ /* Set the started flag */ -+ this->started = true; -+ -+ return 0; -+} -+ -+static int do_remove_source(struct spa_loop *loop, -+ bool async, -+ uint32_t seq, -+ const void *data, -+ size_t size, -+ void *user_data) -+{ -+ struct impl *this = user_data; -+ struct itimerspec ts; -+ -+ if (this->source.loop) -+ spa_loop_remove_source(this->data_loop, &this->source); -+ ts.it_value.tv_sec = 0; -+ ts.it_value.tv_nsec = 0; -+ ts.it_interval.tv_sec = 0; -+ ts.it_interval.tv_nsec = 0; -+ timerfd_settime(this->timerfd, 0, &ts, NULL); -+ if (this->flush_source.loop) -+ spa_loop_remove_source(this->data_loop, &this->flush_source); -+ -+ return 0; -+} -+ -+static int do_stop(struct impl *this) -+{ -+ int res = 0; -+ -+ if (!this->started) -+ return 0; -+ -+ spa_log_trace(this->log, "sco-sink %p: stop", this); -+ -+ spa_loop_invoke(this->data_loop, do_remove_source, 0, NULL, 0, true, this); -+ -+ this->started = false; -+ -+ if (this->transport) { -+ /* Release the transport */ -+ res = spa_bt_transport_release(this->transport); -+ -+ /* Shutdown and close the socket */ -+ shutdown(this->sock_fd, SHUT_RDWR); -+ close(this->sock_fd); -+ this->sock_fd = -1; -+ } -+ -+ return res; -+} -+ -+static int impl_node_send_command(struct spa_node *node, const struct spa_command *command) -+{ -+ struct impl *this; -+ struct port *port; -+ int res; -+ -+ spa_return_val_if_fail(node != NULL, -EINVAL); -+ spa_return_val_if_fail(command != NULL, -EINVAL); -+ -+ this = SPA_CONTAINER_OF(node, struct impl, node); -+ port = &this->port; -+ -+ switch (SPA_NODE_COMMAND_ID(command)) { -+ case SPA_NODE_COMMAND_Start: -+ if (!port->have_format) -+ return -EIO; -+ if (port->n_buffers == 0) -+ return -EIO; -+ if ((res = do_start(this)) < 0) -+ return res; -+ break; -+ case SPA_NODE_COMMAND_Pause: -+ if ((res = do_stop(this)) < 0) -+ return res; -+ break; -+ default: -+ return -ENOTSUP; -+ } -+ return 0; -+} -+ -+static const struct spa_dict_item node_info_items[] = { -+ { "media.class", "Audio/Sink" }, -+ { "node.driver", "true" }, -+}; -+ -+static void emit_node_info(struct impl *this, bool full) -+{ -+ if (full) -+ this->info.change_mask = this->info_all; -+ if (this->info.change_mask) { -+ this->info.props = &SPA_DICT_INIT_ARRAY(node_info_items); -+ spa_node_emit_info(&this->hooks, &this->info); -+ this->info.change_mask = 0; -+ } -+} -+ -+static void emit_port_info(struct impl *this, struct port *port, bool full) -+{ -+ if (full) -+ port->info.change_mask = port->info_all; -+ if (port->info.change_mask) { -+ spa_node_emit_port_info(&this->hooks, -+ SPA_DIRECTION_INPUT, 0, &port->info); -+ port->info.change_mask = 0; -+ } -+} -+ -+static int -+impl_node_add_listener(struct spa_node *node, -+ struct spa_hook *listener, -+ const struct spa_node_events *events, -+ void *data) -+{ -+ struct impl *this; -+ struct spa_hook_list save; -+ -+ spa_return_val_if_fail(node != NULL, -EINVAL); -+ -+ this = SPA_CONTAINER_OF(node, struct impl, node); -+ spa_hook_list_isolate(&this->hooks, &save, listener, events, data); -+ -+ emit_node_info(this, true); -+ emit_port_info(this, &this->port, true); -+ -+ spa_hook_list_join(&this->hooks, &save); -+ -+ return 0; -+} -+ -+static int -+impl_node_set_callbacks(struct spa_node *node, -+ const struct spa_node_callbacks *callbacks, -+ void *data) -+{ -+ struct impl *this; -+ -+ spa_return_val_if_fail(node != NULL, -EINVAL); -+ -+ this = SPA_CONTAINER_OF(node, struct impl, node); -+ -+ this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); -+ -+ return 0; -+} -+ -+static int impl_node_add_port(struct spa_node *node, enum spa_direction direction, uint32_t port_id, -+ const struct spa_dict *props) -+{ -+ return -ENOTSUP; -+} -+ -+static int impl_node_remove_port(struct spa_node *node, enum spa_direction direction, uint32_t port_id) -+{ -+ return -ENOTSUP; -+} -+ -+static int -+impl_node_port_enum_params(struct spa_node *node, int seq, -+ enum spa_direction direction, uint32_t port_id, -+ uint32_t id, uint32_t start, uint32_t num, -+ const struct spa_pod *filter) -+{ -+ -+ struct impl *this; -+ struct port *port; -+ struct spa_pod *param; -+ struct spa_pod_builder b = { 0 }; -+ uint8_t buffer[1024]; -+ struct spa_result_node_params result; -+ uint32_t count = 0; -+ -+ spa_return_val_if_fail(node != NULL, -EINVAL); -+ spa_return_val_if_fail(num != 0, -EINVAL); -+ -+ this = SPA_CONTAINER_OF(node, struct impl, node); -+ -+ spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); -+ port = &this->port; -+ -+ result.id = id; -+ result.next = start; -+ next: -+ result.index = result.next++; -+ -+ spa_pod_builder_init(&b, buffer, sizeof(buffer)); -+ -+ switch (id) { -+ case SPA_PARAM_EnumFormat: -+ if (result.index > 0) -+ return 0; -+ -+ /* set the info structure */ -+ struct spa_audio_info_raw info = { 0, }; -+ info.format = SPA_AUDIO_FORMAT_S16; -+ info.channels = 1; -+ info.position[0] = SPA_AUDIO_CHANNEL_MONO; -+ -+ /* TODO: For now we only handle HSP profiles which has always CVSD format, -+ * but we eventually need to support HFP that can have both CVSD and MSBC formats */ -+ -+ /* CVSD format has a rate of 8kHz -+ * MSBC format has a rate of 16kHz */ -+ info.rate = 8000; -+ -+ /* build the param */ -+ param = spa_format_audio_raw_build(&b, id, &info); -+ -+ break; -+ -+ case SPA_PARAM_Format: -+ if (!port->have_format) -+ return -EIO; -+ if (result.index > 0) -+ return 0; -+ -+ param = spa_format_audio_raw_build(&b, id, &port->current_format.info.raw); -+ break; -+ -+ case SPA_PARAM_Buffers: -+ if (!port->have_format) -+ return -EIO; -+ if (result.index > 0) -+ return 0; -+ -+ param = spa_pod_builder_add_object(&b, -+ SPA_TYPE_OBJECT_ParamBuffers, id, -+ SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 2, MAX_BUFFERS), -+ SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), -+ SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( -+ this->props.min_latency * port->frame_size, -+ this->props.min_latency * port->frame_size, -+ INT32_MAX), -+ SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->frame_size), -+ SPA_PARAM_BUFFERS_align, SPA_POD_Int(16)); -+ break; -+ -+ case SPA_PARAM_Meta: -+ switch (result.index) { -+ case 0: -+ param = spa_pod_builder_add_object(&b, -+ SPA_TYPE_OBJECT_ParamMeta, id, -+ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), -+ SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); -+ break; -+ default: -+ return 0; -+ } -+ break; -+ -+ default: -+ return -ENOENT; -+ } -+ -+ if (spa_pod_filter(&b, &result.param, param, filter) < 0) -+ goto next; -+ -+ spa_node_emit_result(&this->hooks, seq, 0, &result); -+ -+ if (++count != num) -+ goto next; -+ -+ return 0; -+} -+ -+static int clear_buffers(struct impl *this, struct port *port) -+{ -+ do_stop(this); -+ if (port->n_buffers > 0) { -+ spa_list_init(&port->ready); -+ port->n_buffers = 0; -+ } -+ return 0; -+} -+ -+static int port_set_format(struct impl *this, struct port *port, -+ uint32_t flags, -+ const struct spa_pod *format) -+{ -+ int err; -+ -+ if (format == NULL) { -+ spa_log_info(this->log, "clear format"); -+ clear_buffers(this, port); -+ port->have_format = false; -+ } else { -+ struct spa_audio_info info = { 0 }; -+ -+ if ((err = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) -+ return err; -+ -+ if (info.media_type != SPA_MEDIA_TYPE_audio || -+ info.media_subtype != SPA_MEDIA_SUBTYPE_raw) -+ return -EINVAL; -+ -+ if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) -+ return -EINVAL; -+ -+ port->frame_size = info.info.raw.channels * 2; -+ port->current_format = info; -+ port->have_format = true; -+ this->threshold = this->props.min_latency; -+ } -+ -+ port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; -+ if (port->have_format) { -+ port->info.change_mask |= SPA_PORT_CHANGE_MASK_FLAGS; -+ port->info.flags = SPA_PORT_FLAG_CAN_USE_BUFFERS | SPA_PORT_FLAG_LIVE; -+ port->info.change_mask |= SPA_PORT_CHANGE_MASK_RATE; -+ port->info.rate = SPA_FRACTION(1, port->current_format.info.raw.rate); -+ port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); -+ port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); -+ } else { -+ port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); -+ port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); -+ } -+ emit_port_info(this, port, false); -+ -+ return 0; -+} -+ -+static int -+impl_node_port_set_param(struct spa_node *node, -+ enum spa_direction direction, uint32_t port_id, -+ uint32_t id, uint32_t flags, -+ const struct spa_pod *param) -+{ -+ struct impl *this; -+ struct port *port; -+ int res; -+ -+ spa_return_val_if_fail(node != NULL, -EINVAL); -+ this = SPA_CONTAINER_OF(node, struct impl, node); -+ -+ spa_return_val_if_fail(CHECK_PORT(node, direction, port_id), -EINVAL); -+ port = &this->port; -+ -+ switch (id) { -+ case SPA_PARAM_Format: -+ res = port_set_format(this, port, flags, param); -+ break; -+ default: -+ res = -ENOENT; -+ break; -+ } -+ return res; -+} -+ -+static int -+impl_node_port_use_buffers(struct spa_node *node, -+ enum spa_direction direction, -+ uint32_t port_id, struct spa_buffer **buffers, uint32_t n_buffers) -+{ -+ struct impl *this; -+ struct port *port; -+ uint32_t i; -+ -+ spa_return_val_if_fail(node != NULL, -EINVAL); -+ this = SPA_CONTAINER_OF(node, struct impl, node); -+ -+ spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); -+ port = &this->port; -+ -+ spa_log_info(this->log, "use buffers %d", n_buffers); -+ -+ if (!port->have_format) -+ return -EIO; -+ -+ clear_buffers(this, port); -+ -+ for (i = 0; i < n_buffers; i++) { -+ struct buffer *b = &port->buffers[i]; -+ uint32_t type; -+ -+ b->buf = buffers[i]; -+ b->id = i; -+ b->outstanding = true; -+ -+ b->h = spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h)); -+ -+ type = buffers[i]->datas[0].type; -+ if ((type == SPA_DATA_MemFd || -+ type == SPA_DATA_DmaBuf || -+ type == SPA_DATA_MemPtr) && buffers[i]->datas[0].data == NULL) { -+ spa_log_error(this->log, NAME " %p: need mapped memory", this); -+ return -EINVAL; -+ } -+ this->threshold = buffers[i]->datas[0].maxsize / port->frame_size; -+ } -+ port->n_buffers = n_buffers; -+ -+ return 0; -+} -+ -+static int -+impl_node_port_alloc_buffers(struct spa_node *node, -+ enum spa_direction direction, -+ uint32_t port_id, -+ struct spa_pod **params, -+ uint32_t n_params, -+ struct spa_buffer **buffers, -+ uint32_t *n_buffers) -+{ -+ struct impl *this; -+ struct port *port; -+ -+ spa_return_val_if_fail(node != NULL, -EINVAL); -+ spa_return_val_if_fail(buffers != NULL, -EINVAL); -+ -+ this = SPA_CONTAINER_OF(node, struct impl, node); -+ -+ spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); -+ port = &this->port; -+ -+ if (!port->have_format) -+ return -EIO; -+ -+ return -ENOTSUP; -+} -+ -+static int -+impl_node_port_set_io(struct spa_node *node, -+ enum spa_direction direction, -+ uint32_t port_id, -+ uint32_t id, -+ void *data, size_t size) -+{ -+ struct impl *this; -+ struct port *port; -+ -+ spa_return_val_if_fail(node != NULL, -EINVAL); -+ -+ this = SPA_CONTAINER_OF(node, struct impl, node); -+ -+ spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); -+ port = &this->port; -+ -+ switch (id) { -+ case SPA_IO_Buffers: -+ port->io = data; -+ break; -+ default: -+ return -ENOENT; -+ } -+ return 0; -+} -+ -+static int impl_node_port_reuse_buffer(struct spa_node *node, uint32_t port_id, uint32_t buffer_id) -+{ -+ return -ENOTSUP; -+} -+ -+static int impl_node_process(struct spa_node *node) -+{ -+ struct impl *this; -+ struct port *port; -+ struct spa_io_buffers *io; -+ uint64_t now_time; -+ -+ spa_return_val_if_fail(node != NULL, -EINVAL); -+ -+ this = SPA_CONTAINER_OF(node, struct impl, node); -+ port = &this->port; -+ io = port->io; -+ spa_return_val_if_fail(io != NULL, -EIO); -+ -+ /* Get the current time */ -+ clock_gettime(CLOCK_MONOTONIC, &this->now); -+ now_time = SPA_TIMESPEC_TO_NSEC(&this->now); -+ -+ /* Make sure we process all the previous buffers */ -+ if (!spa_list_is_empty(&port->ready)) -+ render_buffers(this, now_time); -+ -+ /* Process the new buffers */ -+ if (io->status == SPA_STATUS_HAVE_BUFFER && io->buffer_id < port->n_buffers) { -+ struct buffer *b = &port->buffers[io->buffer_id]; -+ -+ if (!b->outstanding) { -+ spa_log_warn(this->log, NAME " %p: buffer %u in use", this, io->buffer_id); -+ io->status = -EINVAL; -+ return -EINVAL; -+ } -+ -+ spa_log_trace(this->log, NAME " %p: queue buffer %u", this, io->buffer_id); -+ -+ spa_list_append(&port->ready, &b->link); -+ b->outstanding = false; -+ -+ this->threshold = SPA_MIN(b->buf->datas[0].chunk->size / port->frame_size, -+ this->props.max_latency); -+ -+ render_buffers(this, now_time); -+ -+ io->status = SPA_STATUS_OK; -+ } -+ -+ return SPA_STATUS_HAVE_BUFFER; -+} -+ -+static const struct spa_node impl_node = { -+ SPA_VERSION_NODE, -+ .add_listener = impl_node_add_listener, -+ .set_callbacks = impl_node_set_callbacks, -+ .enum_params = impl_node_enum_params, -+ .set_param = impl_node_set_param, -+ .set_io = impl_node_set_io, -+ .send_command = impl_node_send_command, -+ .add_port = impl_node_add_port, -+ .remove_port = impl_node_remove_port, -+ .port_enum_params = impl_node_port_enum_params, -+ .port_set_param = impl_node_port_set_param, -+ .port_use_buffers = impl_node_port_use_buffers, -+ .port_alloc_buffers = impl_node_port_alloc_buffers, -+ .port_set_io = impl_node_port_set_io, -+ .port_reuse_buffer = impl_node_port_reuse_buffer, -+ .process = impl_node_process, -+}; -+ -+static void transport_destroy(void *data) -+{ -+ struct impl *this = data; -+ spa_log_debug(this->log, "transport %p destroy", this->transport); -+ this->transport = NULL; -+} -+ -+static const struct spa_bt_transport_events transport_events = { -+ SPA_VERSION_BT_TRANSPORT_EVENTS, -+ .destroy = transport_destroy, -+}; -+ -+static int impl_get_interface(struct spa_handle *handle, uint32_t type, void **interface) -+{ -+ struct impl *this; -+ -+ spa_return_val_if_fail(handle != NULL, -EINVAL); -+ spa_return_val_if_fail(interface != NULL, -EINVAL); -+ -+ this = (struct impl *) handle; -+ -+ if (type == SPA_TYPE_INTERFACE_Node) -+ *interface = &this->node; -+ else -+ return -ENOENT; -+ -+ return 0; -+} -+ -+static int impl_clear(struct spa_handle *handle) -+{ -+ return 0; -+} -+ -+static size_t -+impl_get_size(const struct spa_handle_factory *factory, -+ const struct spa_dict *params) -+{ -+ return sizeof(struct impl); -+} -+ -+static int -+impl_init(const struct spa_handle_factory *factory, -+ struct spa_handle *handle, -+ const struct spa_dict *info, -+ const struct spa_support *support, -+ uint32_t n_support) -+{ -+ struct impl *this; -+ struct port *port; -+ uint32_t i; -+ -+ spa_return_val_if_fail(factory != NULL, -EINVAL); -+ spa_return_val_if_fail(handle != NULL, -EINVAL); -+ -+ handle->get_interface = impl_get_interface; -+ handle->clear = impl_clear; -+ -+ this = (struct impl *) handle; -+ -+ for (i = 0; i < n_support; i++) { -+ if (support[i].type == SPA_TYPE_INTERFACE_Log) -+ this->log = support[i].data; -+ else if (support[i].type == SPA_TYPE_INTERFACE_DataLoop) -+ this->data_loop = support[i].data; -+ else if (support[i].type == SPA_TYPE_INTERFACE_MainLoop) -+ this->main_loop = support[i].data; -+ } -+ if (this->data_loop == NULL) { -+ spa_log_error(this->log, "a data loop is needed"); -+ return -EINVAL; -+ } -+ if (this->main_loop == NULL) { -+ spa_log_error(this->log, "a main loop is needed"); -+ return -EINVAL; -+ } -+ -+ this->node = impl_node; -+ spa_hook_list_init(&this->hooks); -+ -+ reset_props(&this->props); -+ -+ this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | -+ SPA_NODE_CHANGE_MASK_PARAMS | -+ SPA_NODE_CHANGE_MASK_PROPS; -+ this->info = SPA_NODE_INFO_INIT(); -+ this->info.flags = SPA_NODE_FLAG_RT; -+ this->params[0] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); -+ this->params[1] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); -+ this->info.params = this->params; -+ this->info.n_params = 2; -+ -+ port = &this->port; -+ port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | -+ SPA_PORT_CHANGE_MASK_PARAMS; -+ port->info = SPA_PORT_INFO_INIT(); -+ port->info.flags = SPA_PORT_FLAG_CAN_USE_BUFFERS; -+ port->params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); -+ port->params[1] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); -+ port->params[2] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); -+ port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); -+ port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); -+ port->info.params = port->params; -+ port->info.n_params = 5; -+ spa_list_init(&port->ready); -+ -+ for (i = 0; info && i < info->n_items; i++) { -+ if (strcmp(info->items[i].key, "bluez5.transport") == 0) -+ sscanf(info->items[i].value, "%p", &this->transport); -+ } -+ if (this->transport == NULL) { -+ spa_log_error(this->log, "a transport is needed"); -+ return -EINVAL; -+ } -+ spa_bt_transport_add_listener(this->transport, -+ &this->transport_listener, &transport_events, this); -+ this->sock_fd = -1; -+ -+ this->timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK); -+ -+ return 0; -+} -+ -+static const struct spa_interface_info impl_interfaces[] = { -+ {SPA_TYPE_INTERFACE_Node,}, -+}; -+ -+static int -+impl_enum_interface_info(const struct spa_handle_factory *factory, -+ const struct spa_interface_info **info, uint32_t *index) -+{ -+ spa_return_val_if_fail(factory != NULL, -EINVAL); -+ spa_return_val_if_fail(info != NULL, -EINVAL); -+ spa_return_val_if_fail(index != NULL, -EINVAL); -+ -+ switch (*index) { -+ case 0: -+ *info = &impl_interfaces[*index]; -+ break; -+ default: -+ return 0; -+ } -+ (*index)++; -+ return 1; -+} -+ -+static const struct spa_dict_item info_items[] = { -+ { "factory.author", "Wim Taymans <wim.taymans@gmail.com>" }, -+ { "factory.description", "Play audio with the sco (hsp/hfp)" }, -+}; -+ -+static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items); -+ -+struct spa_handle_factory spa_sco_sink_factory = { -+ SPA_VERSION_HANDLE_FACTORY, -+ NAME, -+ &info, -+ impl_get_size, -+ impl_init, -+ impl_enum_interface_info, -+}; -diff --git a/spa/plugins/bluez5/sco-source.c b/spa/plugins/bluez5/sco-source.c -new file mode 100644 -index 00000000..ce83ee24 ---- /dev/null -+++ b/spa/plugins/bluez5/sco-source.c -@@ -0,0 +1,1136 @@ -+/* Spa SCO Source -+ * -+ * Copyright © 2018 Wim Taymans -+ * 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 <time.h> -+#include <fcntl.h> -+#include <sys/types.h> -+#include <sys/socket.h> -+ -+#include <spa/support/loop.h> -+#include <spa/support/log.h> -+#include <spa/utils/list.h> -+ -+#include <spa/node/node.h> -+#include <spa/node/io.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 "defs.h" -+ -+struct props { -+ uint32_t min_latency; -+ uint32_t max_latency; -+}; -+ -+#define MAX_BUFFERS 32 -+ -+struct buffer { -+ uint32_t id; -+ unsigned int outstanding:1; -+ struct spa_buffer *buf; -+ struct spa_meta_header *h; -+ struct spa_list link; -+}; -+ -+struct port { -+ struct spa_audio_info current_format; -+ int frame_size; -+ unsigned int have_format:1; -+ -+ uint64_t info_all; -+ struct spa_port_info info; -+ struct spa_io_buffers *io; -+ struct spa_param_info params[8]; -+ -+ struct buffer buffers[MAX_BUFFERS]; -+ uint32_t n_buffers; -+ -+ struct spa_list free; -+ struct spa_list ready; -+ -+ size_t ready_offset; -+}; -+ -+struct impl { -+ struct spa_handle handle; -+ struct spa_node node; -+ -+ struct spa_log *log; -+ struct spa_loop *main_loop; -+ struct spa_loop *data_loop; -+ -+ struct spa_hook_list hooks; -+ struct spa_callbacks callbacks; -+ -+ uint64_t info_all; -+ struct spa_node_info info; -+ struct spa_param_info params[8]; -+ struct props props; -+ -+ struct spa_bt_transport *transport; -+ struct spa_hook transport_listener; -+ int sock_fd; -+ -+ struct port port; -+ -+ unsigned int started:1; -+ unsigned int slaved:1; -+ -+ struct spa_source source; -+ -+ struct spa_io_clock *clock; -+ struct spa_io_position *position; -+ -+ struct timespec now; -+ uint32_t sample_count; -+}; -+ -+#define NAME "sco-source" -+ -+#define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_OUTPUT && (p) == 0) -+ -+static const uint32_t default_min_latency = 64; -+static const uint32_t default_max_latency = 256; -+ -+static void reset_props(struct props *props) -+{ -+ props->min_latency = default_min_latency; -+ props->max_latency = default_max_latency; -+} -+ -+static int impl_node_enum_params(struct spa_node *node, int seq, -+ uint32_t id, uint32_t start, uint32_t num, -+ const struct spa_pod *filter) -+{ -+ struct impl *this; -+ struct spa_pod *param; -+ struct spa_pod_builder b = { 0 }; -+ uint8_t buffer[1024]; -+ struct spa_result_node_params result; -+ uint32_t count = 0; -+ -+ spa_return_val_if_fail(node != NULL, -EINVAL); -+ spa_return_val_if_fail(num != 0, -EINVAL); -+ -+ this = SPA_CONTAINER_OF(node, struct impl, node); -+ -+ result.id = id; -+ result.next = start; -+ next: -+ result.index = result.next++; -+ -+ spa_pod_builder_init(&b, buffer, sizeof(buffer)); -+ -+ switch (id) { -+ case SPA_PARAM_PropInfo: -+ { -+ struct props *p = &this->props; -+ -+ switch (result.index) { -+ case 0: -+ param = spa_pod_builder_add_object(&b, -+ SPA_TYPE_OBJECT_PropInfo, id, -+ SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_minLatency), -+ SPA_PROP_INFO_name, SPA_POD_String("The minimum latency"), -+ SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->min_latency, 1, INT32_MAX)); -+ break; -+ case 1: -+ param = spa_pod_builder_add_object(&b, -+ SPA_TYPE_OBJECT_PropInfo, id, -+ SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_maxLatency), -+ SPA_PROP_INFO_name, SPA_POD_String("The maximum latency"), -+ SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->max_latency, 1, INT32_MAX)); -+ break; -+ default: -+ return 0; -+ } -+ break; -+ } -+ case SPA_PARAM_Props: -+ { -+ struct props *p = &this->props; -+ -+ switch (result.index) { -+ case 0: -+ param = spa_pod_builder_add_object(&b, -+ SPA_TYPE_OBJECT_Props, id, -+ SPA_PROP_minLatency, SPA_POD_Int(p->min_latency), -+ SPA_PROP_maxLatency, SPA_POD_Int(p->max_latency)); -+ break; -+ default: -+ return 0; -+ } -+ break; -+ } -+ default: -+ return -ENOENT; -+ } -+ -+ if (spa_pod_filter(&b, &result.param, param, filter) < 0) -+ goto next; -+ -+ spa_node_emit_result(&this->hooks, seq, 0, &result); -+ -+ if (++count != num) -+ goto next; -+ -+ return 0; -+} -+ -+static int do_reslave(struct spa_loop *loop, -+ bool async, -+ uint32_t seq, -+ const void *data, -+ size_t size, -+ void *user_data) -+{ -+ return 0; -+} -+ -+static inline bool is_slaved(struct impl *this) -+{ -+ return this->position && this->clock && this->position->clock.id != this->clock->id; -+} -+ -+static int impl_node_set_io(struct spa_node *node, uint32_t id, void *data, size_t size) -+{ -+ struct impl *this; -+ bool slaved; -+ -+ spa_return_val_if_fail(node != NULL, -EINVAL); -+ -+ this = SPA_CONTAINER_OF(node, struct impl, node); -+ -+ switch (id) { -+ case SPA_IO_Clock: -+ this->clock = data; -+ break; -+ case SPA_IO_Position: -+ this->position = data; -+ break; -+ default: -+ return -ENOENT; -+ } -+ -+ slaved = is_slaved(this); -+ if (this->started && slaved != this->slaved) { -+ spa_log_debug(this->log, "sco-source %p: reslave %d->%d", this, this->slaved, slaved); -+ this->slaved = slaved; -+ spa_loop_invoke(this->data_loop, do_reslave, 0, NULL, 0, true, this); -+ } -+ return 0; -+} -+ -+static int impl_node_set_param(struct spa_node *node, uint32_t id, uint32_t flags, -+ const struct spa_pod *param) -+{ -+ struct impl *this; -+ -+ spa_return_val_if_fail(node != NULL, -EINVAL); -+ -+ this = SPA_CONTAINER_OF(node, struct impl, node); -+ -+ switch (id) { -+ case SPA_PARAM_Props: -+ { -+ struct props *p = &this->props; -+ -+ if (param == NULL) { -+ reset_props(p); -+ return 0; -+ } -+ spa_pod_parse_object(param, -+ SPA_TYPE_OBJECT_Props, NULL, -+ SPA_PROP_minLatency, SPA_POD_OPT_Int(&p->min_latency), -+ SPA_PROP_maxLatency, SPA_POD_OPT_Int(&p->max_latency)); -+ break; -+ } -+ default: -+ return -ENOENT; -+ } -+ -+ return 0; -+} -+ -+static void reset_buffers(struct port *port) -+{ -+ uint32_t i; -+ -+ spa_list_init(&port->free); -+ spa_list_init(&port->ready); -+ -+ for (i = 0; i < port->n_buffers; i++) { -+ struct buffer *b = &port->buffers[i]; -+ spa_list_append(&port->free, &b->link); -+ b->outstanding = false; -+ } -+} -+ -+static bool read_data(struct impl *this, uint8_t *data, uint32_t size, uint32_t *total_read) { -+ const uint32_t mtu_size = this->transport->read_mtu; -+ uint32_t local_total_read = 0; -+ -+ /* TODO: For now we assume the size is always a mutliple of mtu_size */ -+ while (local_total_read < (size - mtu_size)) { -+ const int bytes_read = read(this->sock_fd, data, mtu_size); -+ if (bytes_read == 0) { -+ /* Stop */ -+ return false; -+ } else if (bytes_read < 0) { -+ /* Retry */ -+ if (errno == EINTR) -+ continue; -+ -+ /* Socked has no data so return total data read */ -+ if (errno == EAGAIN || errno == EWOULDBLOCK) -+ goto done; -+ -+ /* Print error and stop */ -+ spa_log_error(this->log, "read error: %s", strerror(errno)); -+ return false; -+ } -+ -+ data += bytes_read; -+ local_total_read += bytes_read; -+ } -+ -+done: -+ if (total_read) -+ *total_read = local_total_read; -+ return true; -+} -+ -+static void sco_on_ready_read(struct spa_source *source) -+{ -+ struct impl *this = source->data; -+ struct port *port = &this->port; -+ struct buffer *buffer; -+ struct spa_data *buffer_data; -+ uint32_t total_read; -+ -+ /* update the current pts */ -+ clock_gettime(CLOCK_MONOTONIC, &this->now); -+ -+ /* check if we have a new buffer */ -+ if (spa_list_is_empty(&port->free)) { -+ spa_log_warn(this->log, "waiting for buffer"); -+ return; -+ } -+ -+ /* get the buffer data */ -+ buffer = spa_list_first(&port->free, struct buffer, link); -+ buffer_data = &buffer->buf->datas[0]; -+ spa_assert(buffer_data->data); -+ -+ /* read data */ -+ if (!read_data(this, buffer_data->data, buffer_data->maxsize, &total_read)) -+ goto stop; -+ if (total_read == 0) -+ return; -+ -+ /* update the buffer offset, size and stride */ -+ buffer_data->chunk->offset = 0; -+ buffer_data->chunk->size = total_read; -+ buffer_data->chunk->stride = port->frame_size; -+ -+ /* update the sample count */ -+ this->sample_count += buffer_data->chunk->size / port->frame_size; -+ -+ /* remove the buffer from the free list and add it to the ready list */ -+ spa_list_remove(&buffer->link); -+ buffer->outstanding = true; -+ spa_list_append(&port->ready, &buffer->link); -+ -+ /* Notify we are ready for the next buffer */ -+ spa_node_call_ready(&this->callbacks, SPA_STATUS_HAVE_BUFFER); -+ -+ return; -+ -+stop: -+ if (this->source.loop) -+ spa_loop_remove_source(this->data_loop, &this->source); -+} -+ -+static int do_start(struct impl *this) -+{ -+ int val; -+ bool do_accept; -+ -+ /* Dont do anything if the node has already started */ -+ if (this->started) -+ return 0; -+ -+ /* Make sure the transport is valid */ -+ spa_return_val_if_fail (this->transport != NULL, -EIO); -+ -+ /* Do accept if Gateway; otherwise do connect for Head Unit */ -+ do_accept = this->transport->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY; -+ -+ /* acquire the socked fd (false -> connect | true -> accept) */ -+ this->sock_fd = spa_bt_transport_acquire(this->transport, do_accept); -+ if (this->sock_fd < 0) -+ return -1; -+ -+ /* Set the write MTU */ -+ val = this->transport->write_mtu; -+ if (setsockopt(this->sock_fd, SOL_SOCKET, SO_SNDBUF, &val, sizeof(val)) < 0) -+ spa_log_warn(this->log, "sco-source %p: SO_SNDBUF %m", this); -+ -+ /* Set the read MTU */ -+ val = this->transport->read_mtu; -+ if (setsockopt(this->sock_fd, SOL_SOCKET, SO_RCVBUF, &val, sizeof(val)) < 0) -+ spa_log_warn(this->log, "sco-source %p: SO_RCVBUF %m", this); -+ -+ /* Set the priority */ -+ val = 6; -+ if (setsockopt(this->sock_fd, SOL_SOCKET, SO_PRIORITY, &val, sizeof(val)) < 0) -+ spa_log_warn(this->log, "SO_PRIORITY failed: %m"); -+ -+ /* Reset the buffers and sample count */ -+ reset_buffers(&this->port); -+ this->sample_count = 0; -+ -+ /* Add the ready read callback */ -+ this->source.data = this; -+ this->source.fd = this->sock_fd; -+ this->source.func = sco_on_ready_read; -+ this->source.mask = SPA_IO_IN; -+ this->source.rmask = 0; -+ spa_loop_add_source(this->data_loop, &this->source); -+ -+ /* Set the started flag */ -+ this->started = true; -+ -+ return 0; -+} -+ -+static int do_remove_source(struct spa_loop *loop, -+ bool async, -+ uint32_t seq, -+ const void *data, -+ size_t size, -+ void *user_data) -+{ -+ struct impl *this = user_data; -+ -+ if (this->source.loop) -+ spa_loop_remove_source(this->data_loop, &this->source); -+ -+ return 0; -+} -+ -+static int do_stop(struct impl *this) -+{ -+ int res = 0; -+ -+ if (!this->started) -+ return 0; -+ -+ spa_log_debug(this->log, "sco-source %p: stop", this); -+ -+ spa_loop_invoke(this->data_loop, do_remove_source, 0, NULL, 0, true, this); -+ -+ this->started = false; -+ -+ if (this->transport) { -+ /* Release the transport */ -+ res = spa_bt_transport_release(this->transport); -+ -+ /* Shutdown and close the socket */ -+ shutdown(this->sock_fd, SHUT_RDWR); -+ close(this->sock_fd); -+ this->sock_fd = -1; -+ } -+ -+ return res; -+} -+ -+static int impl_node_send_command(struct spa_node *node, const struct spa_command *command) -+{ -+ struct impl *this; -+ struct port *port; -+ int res; -+ -+ spa_return_val_if_fail(node != NULL, -EINVAL); -+ spa_return_val_if_fail(command != NULL, -EINVAL); -+ -+ this = SPA_CONTAINER_OF(node, struct impl, node); -+ port = &this->port; -+ -+ switch (SPA_NODE_COMMAND_ID(command)) { -+ case SPA_NODE_COMMAND_Start: -+ if (!port->have_format) -+ return -EIO; -+ if (port->n_buffers == 0) -+ return -EIO; -+ if ((res = do_start(this)) < 0) -+ return res; -+ break; -+ case SPA_NODE_COMMAND_Pause: -+ if ((res = do_stop(this)) < 0) -+ return res; -+ break; -+ default: -+ return -ENOTSUP; -+ } -+ return 0; -+} -+ -+static const struct spa_dict_item node_info_items[] = { -+ { "media.class", "Audio/Source" }, -+ { "node.driver", "true" }, -+}; -+ -+static void emit_node_info(struct impl *this, bool full) -+{ -+ if (full) -+ this->info.change_mask = this->info_all; -+ if (this->info.change_mask) { -+ this->info.props = &SPA_DICT_INIT_ARRAY(node_info_items); -+ spa_node_emit_info(&this->hooks, &this->info); -+ this->info.change_mask = 0; -+ } -+} -+ -+static void emit_port_info(struct impl *this, struct port *port, bool full) -+{ -+ if (full) -+ port->info.change_mask = port->info_all; -+ if (port->info.change_mask) { -+ spa_node_emit_port_info(&this->hooks, -+ SPA_DIRECTION_OUTPUT, 0, &port->info); -+ port->info.change_mask = 0; -+ } -+} -+ -+static int -+impl_node_add_listener(struct spa_node *node, -+ struct spa_hook *listener, -+ const struct spa_node_events *events, -+ void *data) -+{ -+ struct impl *this; -+ struct spa_hook_list save; -+ -+ spa_return_val_if_fail(node != NULL, -EINVAL); -+ -+ this = SPA_CONTAINER_OF(node, struct impl, node); -+ spa_hook_list_isolate(&this->hooks, &save, listener, events, data); -+ -+ emit_node_info(this, true); -+ emit_port_info(this, &this->port, true); -+ -+ spa_hook_list_join(&this->hooks, &save); -+ -+ return 0; -+} -+ -+static int -+impl_node_set_callbacks(struct spa_node *node, -+ const struct spa_node_callbacks *callbacks, -+ void *data) -+{ -+ struct impl *this; -+ -+ spa_return_val_if_fail(node != NULL, -EINVAL); -+ -+ this = SPA_CONTAINER_OF(node, struct impl, node); -+ -+ this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); -+ -+ return 0; -+} -+ -+static int impl_node_add_port(struct spa_node *node, enum spa_direction direction, uint32_t port_id, -+ const struct spa_dict *props) -+{ -+ return -ENOTSUP; -+} -+ -+static int impl_node_remove_port(struct spa_node *node, enum spa_direction direction, uint32_t port_id) -+{ -+ return -ENOTSUP; -+} -+ -+static int -+impl_node_port_enum_params(struct spa_node *node, int seq, -+ enum spa_direction direction, uint32_t port_id, -+ uint32_t id, uint32_t start, uint32_t num, -+ const struct spa_pod *filter) -+{ -+ -+ struct impl *this; -+ struct port *port; -+ struct spa_pod *param; -+ struct spa_pod_builder b = { 0 }; -+ uint8_t buffer[1024]; -+ struct spa_result_node_params result; -+ uint32_t count = 0; -+ -+ spa_return_val_if_fail(node != NULL, -EINVAL); -+ spa_return_val_if_fail(num != 0, -EINVAL); -+ -+ this = SPA_CONTAINER_OF(node, struct impl, node); -+ -+ spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); -+ port = &this->port; -+ -+ result.id = id; -+ result.next = start; -+ next: -+ result.index = result.next++; -+ -+ spa_pod_builder_init(&b, buffer, sizeof(buffer)); -+ -+ switch (id) { -+ case SPA_PARAM_EnumFormat: -+ if (result.index > 0) -+ return 0; -+ -+ if (this->transport == NULL) -+ return -EIO; -+ -+ /* set the info structure */ -+ struct spa_audio_info_raw info = { 0, }; -+ info.format = SPA_AUDIO_FORMAT_S16; -+ info.channels = 1; -+ info.position[0] = SPA_AUDIO_CHANNEL_MONO; -+ -+ /* TODO: For now we only handle HSP profiles which has always CVSD format, -+ * but we eventually need to support HFP that can have both CVSD and MSBC formats */ -+ -+ /* CVSD format has a rate of 8kHz -+ * MSBC format has a rate of 16kHz */ -+ info.rate = 8000; -+ -+ /* build the param */ -+ param = spa_format_audio_raw_build(&b, id, &info); -+ break; -+ -+ case SPA_PARAM_Format: -+ if (!port->have_format) -+ return -EIO; -+ if (result.index > 0) -+ return 0; -+ -+ param = spa_format_audio_raw_build(&b, id, &port->current_format.info.raw); -+ break; -+ -+ case SPA_PARAM_Buffers: -+ if (!port->have_format) -+ return -EIO; -+ if (result.index > 0) -+ return 0; -+ -+ param = spa_pod_builder_add_object(&b, -+ SPA_TYPE_OBJECT_ParamBuffers, id, -+ /* 8 buffers are enough to make sure we always have one available when decoding */ -+ SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 8, MAX_BUFFERS), -+ SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), -+ SPA_PARAM_BUFFERS_size, SPA_POD_Int(this->props.max_latency * port->frame_size), -+ SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->frame_size), -+ SPA_PARAM_BUFFERS_align, SPA_POD_Int(16)); -+ break; -+ -+ case SPA_PARAM_Meta: -+ switch (result.index) { -+ case 0: -+ param = spa_pod_builder_add_object(&b, -+ SPA_TYPE_OBJECT_ParamMeta, id, -+ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), -+ SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); -+ break; -+ default: -+ return 0; -+ } -+ break; -+ -+ default: -+ return -ENOENT; -+ } -+ -+ /* TODO: why filer is != NULL when linking it with sco-sink? */ -+ /* if filter is null sco-source cannot be linked with sco-sink, -+ * so for now we always pass NULL */ -+ if (spa_pod_filter(&b, &result.param, param, NULL) < 0) -+ goto next; -+ -+ spa_node_emit_result(&this->hooks, seq, 0, &result); -+ -+ if (++count != num) -+ goto next; -+ -+ return 0; -+} -+ -+static int clear_buffers(struct impl *this, struct port *port) -+{ -+ do_stop(this); -+ if (port->n_buffers > 0) { -+ spa_list_init(&port->free); -+ spa_list_init(&port->ready); -+ port->n_buffers = 0; -+ } -+ return 0; -+} -+ -+static int port_set_format(struct impl *this, struct port *port, -+ uint32_t flags, -+ const struct spa_pod *format) -+{ -+ int err; -+ -+ if (format == NULL) { -+ spa_log_info(this->log, "clear format"); -+ clear_buffers(this, port); -+ port->have_format = false; -+ } else { -+ struct spa_audio_info info = { 0 }; -+ -+ if ((err = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) -+ return err; -+ -+ if (info.media_type != SPA_MEDIA_TYPE_audio || -+ info.media_subtype != SPA_MEDIA_SUBTYPE_raw) -+ return -EINVAL; -+ -+ if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) -+ return -EINVAL; -+ -+ port->frame_size = info.info.raw.channels * 2; -+ port->current_format = info; -+ port->have_format = true; -+ } -+ -+ port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; -+ if (port->have_format) { -+ port->info.change_mask |= SPA_PORT_CHANGE_MASK_FLAGS; -+ port->info.flags = SPA_PORT_FLAG_CAN_USE_BUFFERS | SPA_PORT_FLAG_LIVE; -+ port->info.change_mask |= SPA_PORT_CHANGE_MASK_RATE; -+ port->info.rate = SPA_FRACTION(1, port->current_format.info.raw.rate); -+ port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); -+ port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); -+ } else { -+ port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); -+ port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); -+ } -+ emit_port_info(this, port, false); -+ -+ return 0; -+} -+ -+static int -+impl_node_port_set_param(struct spa_node *node, -+ enum spa_direction direction, uint32_t port_id, -+ uint32_t id, uint32_t flags, -+ const struct spa_pod *param) -+{ -+ struct impl *this; -+ struct port *port; -+ int res; -+ -+ spa_return_val_if_fail(node != NULL, -EINVAL); -+ this = SPA_CONTAINER_OF(node, struct impl, node); -+ -+ spa_return_val_if_fail(CHECK_PORT(node, direction, port_id), -EINVAL); -+ port = &this->port; -+ -+ switch (id) { -+ case SPA_PARAM_Format: -+ res = port_set_format(this, port, flags, param); -+ break; -+ default: -+ res = -ENOENT; -+ break; -+ } -+ return res; -+} -+ -+static int -+impl_node_port_use_buffers(struct spa_node *node, -+ enum spa_direction direction, -+ uint32_t port_id, struct spa_buffer **buffers, uint32_t n_buffers) -+{ -+ struct impl *this; -+ struct port *port; -+ uint32_t i; -+ -+ spa_return_val_if_fail(node != NULL, -EINVAL); -+ -+ this = SPA_CONTAINER_OF(node, struct impl, node); -+ -+ spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); -+ port = &this->port; -+ -+ spa_log_info(this->log, "use buffers %d", n_buffers); -+ -+ if (!port->have_format) -+ return -EIO; -+ -+ clear_buffers(this, port); -+ -+ for (i = 0; i < n_buffers; i++) { -+ struct buffer *b = &port->buffers[i]; -+ struct spa_data *d = buffers[i]->datas; -+ -+ b->buf = buffers[i]; -+ b->id = i; -+ -+ b->h = spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h)); -+ -+ if (!((d[0].type == SPA_DATA_MemFd || -+ d[0].type == SPA_DATA_DmaBuf || -+ d[0].type == SPA_DATA_MemPtr) && d[0].data != NULL)) { -+ spa_log_error(this->log, NAME " %p: need mapped memory", this); -+ return -EINVAL; -+ } -+ spa_list_append(&port->free, &b->link); -+ b->outstanding = false; -+ } -+ port->n_buffers = n_buffers; -+ -+ return 0; -+} -+ -+static int -+impl_node_port_alloc_buffers(struct spa_node *node, -+ enum spa_direction direction, -+ uint32_t port_id, -+ struct spa_pod **params, -+ uint32_t n_params, -+ struct spa_buffer **buffers, -+ uint32_t *n_buffers) -+{ -+ struct impl *this; -+ struct port *port; -+ -+ spa_return_val_if_fail(node != NULL, -EINVAL); -+ spa_return_val_if_fail(buffers != NULL, -EINVAL); -+ -+ this = SPA_CONTAINER_OF(node, struct impl, node); -+ -+ spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); -+ port = &this->port; -+ -+ if (!port->have_format) -+ return -EIO; -+ -+ return -ENOTSUP; -+} -+ -+static int -+impl_node_port_set_io(struct spa_node *node, -+ enum spa_direction direction, -+ uint32_t port_id, -+ uint32_t id, -+ void *data, size_t size) -+{ -+ struct impl *this; -+ struct port *port; -+ -+ spa_return_val_if_fail(node != NULL, -EINVAL); -+ -+ this = SPA_CONTAINER_OF(node, struct impl, node); -+ -+ spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); -+ port = &this->port; -+ -+ switch (id) { -+ case SPA_IO_Buffers: -+ port->io = data; -+ break; -+ default: -+ return -ENOENT; -+ } -+ return 0; -+} -+ -+static void recycle_buffer(struct impl *this, struct port *port, uint32_t buffer_id) -+{ -+ struct buffer *b = &port->buffers[buffer_id]; -+ -+ if (b->outstanding) { -+ spa_log_trace(this->log, NAME " %p: recycle buffer %u", this, buffer_id); -+ spa_list_append(&port->free, &b->link); -+ b->outstanding = false; -+ } -+} -+ -+static int impl_node_port_reuse_buffer(struct spa_node *node, uint32_t port_id, uint32_t buffer_id) -+{ -+ struct impl *this; -+ struct port *port; -+ -+ spa_return_val_if_fail(node != NULL, -EINVAL); -+ -+ this = SPA_CONTAINER_OF(node, struct impl, node); -+ -+ spa_return_val_if_fail(port_id == 0, -EINVAL); -+ port = &this->port; -+ -+ if (port->n_buffers == 0) -+ return -EIO; -+ -+ if (buffer_id >= port->n_buffers) -+ return -EINVAL; -+ -+ recycle_buffer(this, port, buffer_id); -+ -+ return 0; -+} -+ -+static int impl_node_process(struct spa_node *node) -+{ -+ struct impl *this; -+ struct port *port; -+ struct spa_io_buffers *io; -+ struct buffer *b; -+ -+ /* get IO */ -+ spa_return_val_if_fail(node != NULL, -EINVAL); -+ -+ this = SPA_CONTAINER_OF(node, struct impl, node); -+ port = &this->port; -+ io = port->io; -+ spa_return_val_if_fail(io != NULL, -EIO); -+ -+ /* don't do anything if IO does not need a buffer */ -+ if (io->status != SPA_STATUS_NEED_BUFFER) -+ return io->status; -+ -+ /* Recycle previously played buffer */ -+ if (io->buffer_id != SPA_ID_INVALID && -+ io->buffer_id < port->n_buffers) { -+ spa_log_debug(this->log, "recycling buffer_id=%d", io->buffer_id); -+ recycle_buffer(this, port, io->buffer_id); -+ io->buffer_id = SPA_ID_INVALID; -+ } -+ -+ /* Check if we have new buffers in the queue */ -+ if (spa_list_is_empty(&port->ready)) -+ return SPA_STATUS_HAVE_BUFFER; -+ -+ /* Pop the new buffer from the queue */ -+ b = spa_list_first(&port->ready, struct buffer, link); -+ spa_list_remove(&b->link); -+ -+ /* Set the new buffer in IO to be played */ -+ io->buffer_id = b->id; -+ io->status = SPA_STATUS_HAVE_BUFFER; -+ -+ return SPA_STATUS_HAVE_BUFFER; -+} -+ -+static const struct spa_node impl_node = { -+ SPA_VERSION_NODE, -+ .add_listener = impl_node_add_listener, -+ .set_callbacks = impl_node_set_callbacks, -+ .enum_params = impl_node_enum_params, -+ .set_param = impl_node_set_param, -+ .set_io = impl_node_set_io, -+ .send_command = impl_node_send_command, -+ .add_port = impl_node_add_port, -+ .remove_port = impl_node_remove_port, -+ .port_enum_params = impl_node_port_enum_params, -+ .port_set_param = impl_node_port_set_param, -+ .port_use_buffers = impl_node_port_use_buffers, -+ .port_alloc_buffers = impl_node_port_alloc_buffers, -+ .port_set_io = impl_node_port_set_io, -+ .port_reuse_buffer = impl_node_port_reuse_buffer, -+ .process = impl_node_process, -+}; -+ -+static void transport_destroy(void *data) -+{ -+ struct impl *this = data; -+ spa_log_debug(this->log, "transport %p destroy", this->transport); -+ this->transport = NULL; -+} -+ -+static const struct spa_bt_transport_events transport_events = { -+ SPA_VERSION_BT_TRANSPORT_EVENTS, -+ .destroy = transport_destroy, -+}; -+ -+static int impl_get_interface(struct spa_handle *handle, uint32_t type, void **interface) -+{ -+ struct impl *this; -+ -+ spa_return_val_if_fail(handle != NULL, -EINVAL); -+ spa_return_val_if_fail(interface != NULL, -EINVAL); -+ -+ this = (struct impl *) handle; -+ -+ if (type == SPA_TYPE_INTERFACE_Node) -+ *interface = &this->node; -+ else -+ return -ENOENT; -+ -+ return 0; -+} -+ -+static int impl_clear(struct spa_handle *handle) -+{ -+ return 0; -+} -+ -+static size_t -+impl_get_size(const struct spa_handle_factory *factory, -+ const struct spa_dict *params) -+{ -+ return sizeof(struct impl); -+} -+ -+static int -+impl_init(const struct spa_handle_factory *factory, -+ struct spa_handle *handle, -+ const struct spa_dict *info, -+ const struct spa_support *support, -+ uint32_t n_support) -+{ -+ struct impl *this; -+ struct port *port; -+ uint32_t i; -+ -+ spa_return_val_if_fail(factory != NULL, -EINVAL); -+ spa_return_val_if_fail(handle != NULL, -EINVAL); -+ -+ handle->get_interface = impl_get_interface; -+ handle->clear = impl_clear; -+ -+ this = (struct impl *) handle; -+ -+ for (i = 0; i < n_support; i++) { -+ if (support[i].type == SPA_TYPE_INTERFACE_Log) -+ this->log = support[i].data; -+ else if (support[i].type == SPA_TYPE_INTERFACE_DataLoop) -+ this->data_loop = support[i].data; -+ else if (support[i].type == SPA_TYPE_INTERFACE_MainLoop) -+ this->main_loop = support[i].data; -+ } -+ if (this->data_loop == NULL) { -+ spa_log_error(this->log, "a data loop is needed"); -+ return -EINVAL; -+ } -+ if (this->main_loop == NULL) { -+ spa_log_error(this->log, "a main loop is needed"); -+ return -EINVAL; -+ } -+ -+ this->node = impl_node; -+ spa_hook_list_init(&this->hooks); -+ -+ reset_props(&this->props); -+ -+ /* set the node info */ -+ this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | -+ SPA_NODE_CHANGE_MASK_PROPS | -+ SPA_NODE_CHANGE_MASK_PARAMS; -+ this->info = SPA_NODE_INFO_INIT(); -+ this->info.flags = SPA_NODE_FLAG_RT; -+ this->params[0] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); -+ this->params[1] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); -+ this->info.params = this->params; -+ this->info.n_params = 2; -+ -+ /* set the port info */ -+ port = &this->port; -+ port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | -+ SPA_PORT_CHANGE_MASK_PARAMS; -+ port->info = SPA_PORT_INFO_INIT(); -+ port->info.change_mask = SPA_PORT_CHANGE_MASK_FLAGS; -+ port->info.flags = SPA_PORT_FLAG_CAN_USE_BUFFERS | -+ SPA_PORT_FLAG_LIVE | -+ SPA_PORT_FLAG_TERMINAL; -+ port->params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); -+ port->params[1] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); -+ port->params[2] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); -+ port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); -+ port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); -+ port->info.params = port->params; -+ port->info.n_params = 5; -+ -+ /* Init the buffer lists */ -+ spa_list_init(&port->ready); -+ spa_list_init(&port->free); -+ -+ for (i = 0; info && i < info->n_items; i++) { -+ if (strcmp(info->items[i].key, "bluez5.transport") == 0) -+ sscanf(info->items[i].value, "%p", &this->transport); -+ } -+ if (this->transport == NULL) { -+ spa_log_error(this->log, "a transport is needed"); -+ return -EINVAL; -+ } -+ spa_bt_transport_add_listener(this->transport, -+ &this->transport_listener, &transport_events, this); -+ this->sock_fd = -1; -+ -+ return 0; -+} -+ -+static const struct spa_interface_info impl_interfaces[] = { -+ {SPA_TYPE_INTERFACE_Node,}, -+}; -+ -+static int -+impl_enum_interface_info(const struct spa_handle_factory *factory, -+ const struct spa_interface_info **info, uint32_t *index) -+{ -+ spa_return_val_if_fail(factory != NULL, -EINVAL); -+ spa_return_val_if_fail(info != NULL, -EINVAL); -+ spa_return_val_if_fail(index != NULL, -EINVAL); -+ -+ switch (*index) { -+ case 0: -+ *info = &impl_interfaces[*index]; -+ break; -+ default: -+ return 0; -+ } -+ (*index)++; -+ return 1; -+} -+ -+static const struct spa_dict_item info_items[] = { -+ { "factory.author", "Collabora Ltd. <contact@collabora.com>" }, -+ { "factory.description", "Capture bluetooth audio with sco (hsp/hfp)" }, -+}; -+ -+static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items); -+ -+struct spa_handle_factory spa_sco_source_factory = { -+ SPA_VERSION_HANDLE_FACTORY, -+ NAME, -+ &info, -+ impl_get_size, -+ impl_init, -+ impl_enum_interface_info, -+}; --- -2.23.0.rc1 - diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0017-connection-move-remaining-data-and-fds.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0017-connection-move-remaining-data-and-fds.patch new file mode 100644 index 00000000..be5ac5ea --- /dev/null +++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0017-connection-move-remaining-data-and-fds.patch @@ -0,0 +1,61 @@ +From 75247f77eae2c473b18cf8d7e117216f73d2e127 Mon Sep 17 00:00:00 2001 +From: Wim Taymans <wtaymans@redhat.com> +Date: Tue, 1 Oct 2019 10:43:48 +0200 +Subject: [PATCH] connection: move remaining data and fds + +If we can't send all of the data, move the remaining data to the +start of the buffer so that we can send it again later. + +See #111 + +Upstream-Status: Backport [3d48ba8394396fc8d8cadb1bff3514217ddd70e6] +--- + .../module-protocol-native/connection.c | 23 +++++++++++-------- + 1 file changed, 14 insertions(+), 9 deletions(-) + +diff --git a/src/modules/module-protocol-native/connection.c b/src/modules/module-protocol-native/connection.c +index dbb6a3cf..cb592e41 100644 +--- a/src/modules/module-protocol-native/connection.c ++++ b/src/modules/module-protocol-native/connection.c +@@ -491,8 +491,12 @@ int pw_protocol_native_connection_flush(struct pw_protocol_native_connection *co + if (sent < 0) { + if (errno == EINTR) + continue; +- else +- goto send_error; ++ else { ++ res = -errno; ++ pw_log_error("could not sendmsg on fd:%d n_fds:%d: %s", ++ conn->fd, n_fds, spa_strerror(res)); ++ goto exit; ++ } + } + break; + } +@@ -504,15 +508,16 @@ int pw_protocol_native_connection_flush(struct pw_protocol_native_connection *co + n_fds -= outfds; + fds += outfds; + } +- buf->buffer_size = size; +- buf->n_fds = n_fds; + +- return 0; ++ res = 0; + +- /* ERRORS */ +- send_error: +- res = -errno; +- pw_log_error("could not sendmsg: %s", strerror(errno)); ++exit: ++ if (size > 0) ++ memmove(buf->buffer_data, data, size); ++ buf->buffer_size = size; ++ if (n_fds > 0) ++ memmove(buf->fds, fds, n_fds * sizeof(int)); ++ buf->n_fds = n_fds; + return res; + } + +-- +2.23.0 + diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0018-device-add-name-field-in-spa_device_object_info.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0018-device-add-name-field-in-spa_device_object_info.patch deleted file mode 100644 index ef1bd61e..00000000 --- a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0018-device-add-name-field-in-spa_device_object_info.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 0e3df7c3612fadf5319efb231fcd16ef16cd6e1a Mon Sep 17 00:00:00 2001 -From: Julian Bouzas <julian.bouzas@collabora.com> -Date: Thu, 29 Aug 2019 13:58:13 -0400 -Subject: [PATCH] device: add name field in spa_device_object_info - -Upstream-Status: Pending ---- - spa/include/spa/monitor/device.h | 1 + - src/pipewire/device.c | 2 +- - 2 files changed, 2 insertions(+), 1 deletion(-) - -diff --git a/spa/include/spa/monitor/device.h b/spa/include/spa/monitor/device.h -index 765e96f8..51a467b1 100644 ---- a/spa/include/spa/monitor/device.h -+++ b/spa/include/spa/monitor/device.h -@@ -59,6 +59,7 @@ struct spa_device_object_info { - - uint32_t type; - const struct spa_handle_factory *factory; -+ const char *name; - - #define SPA_DEVICE_OBJECT_CHANGE_MASK_FLAGS (1u<<0) - #define SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS (1u<<1) -diff --git a/src/pipewire/device.c b/src/pipewire/device.c -index 11907d13..4cfac06e 100644 ---- a/src/pipewire/device.c -+++ b/src/pipewire/device.c -@@ -389,7 +389,7 @@ static void device_add(struct pw_device *device, uint32_t id, - pw_properties_update(props, info->props); - - node = pw_node_new(device->core, -- device->info.name, -+ info->name ? info->name : device->info.name, - props, - sizeof(struct node_data) + - spa_handle_factory_get_size(info->factory, info->props)); --- -2.23.0.rc1 - diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0018-protocol-improve-flushing.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0018-protocol-improve-flushing.patch new file mode 100644 index 00000000..e027765e --- /dev/null +++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0018-protocol-improve-flushing.patch @@ -0,0 +1,222 @@ +From 7e885c029a6cc66ce1881f6f2c50d5f76a7d3372 Mon Sep 17 00:00:00 2001 +From: Wim Taymans <wtaymans@redhat.com> +Date: Tue, 1 Oct 2019 12:53:56 +0200 +Subject: [PATCH] protocol: improve flushing + +Use the IO_OUT flag to schedule flushing instead of a flush_event. + +Handle EGAIN and wait for IO_OUT to try again. + +Fixes #111 + +Upstream-Status: Backport [cc8e992cd155b4f19312a5036c7b744fc547410f] +--- + src/modules/module-protocol-native.c | 89 +++++++++++++------ + .../module-protocol-native/connection.c | 2 - + 2 files changed, 62 insertions(+), 29 deletions(-) + +diff --git a/src/modules/module-protocol-native.c b/src/modules/module-protocol-native.c +index 411bad6c..b7cd3140 100644 +--- a/src/modules/module-protocol-native.c ++++ b/src/modules/module-protocol-native.c +@@ -83,8 +83,7 @@ struct client { + struct spa_hook conn_listener; + + bool disconnecting; +- bool flush_signaled; +- struct spa_source *flush_event; ++ bool flushing; + }; + + struct server { +@@ -106,6 +105,7 @@ struct client_data { + struct spa_source *source; + struct pw_protocol_native_connection *connection; + bool busy; ++ bool need_flush; + }; + + static void +@@ -194,12 +194,14 @@ client_busy_changed(void *data, bool busy) + { + struct client_data *c = data; + struct pw_client *client = c->client; +- enum spa_io mask = SPA_IO_ERR | SPA_IO_HUP; ++ uint32_t mask = c->source->mask; + + c->busy = busy; + +- if (!busy) +- mask |= SPA_IO_IN; ++ if (busy) ++ SPA_FLAG_UNSET(mask, SPA_IO_IN); ++ else ++ SPA_FLAG_SET(mask, SPA_IO_IN); + + pw_log_debug("protocol-native %p: busy changed %d", client->protocol, busy); + pw_loop_update_io(client->core->main_loop, c->source, mask); +@@ -214,13 +216,32 @@ connection_data(void *data, int fd, enum spa_io mask) + { + struct client_data *this = data; + struct pw_client *client = this->client; ++ int res; + +- if (mask & (SPA_IO_ERR | SPA_IO_HUP)) { ++ if (mask & SPA_IO_HUP) { + pw_log_info("protocol-native %p: client %p disconnected", client->protocol, client); + pw_client_destroy(client); + return; + } +- ++ if (mask & SPA_IO_ERR) { ++ pw_log_error("protocol-native %p: client %p error", client->protocol, client); ++ pw_client_destroy(client); ++ return; ++ } ++ if (mask & SPA_IO_OUT) { ++ res = pw_protocol_native_connection_flush(this->connection); ++ if (res >= 0) { ++ int mask = this->source->mask; ++ SPA_FLAG_UNSET(mask, SPA_IO_OUT); ++ pw_loop_update_io(client->protocol->core->main_loop, ++ this->source, mask); ++ } else if (res != EAGAIN) { ++ pw_log_error("client %p: could not flush: %s", ++ client, spa_strerror(res)); ++ pw_client_destroy(client); ++ return; ++ } ++ } + if (mask & SPA_IO_IN) + process_messages(this); + } +@@ -288,7 +309,8 @@ static struct pw_client *client_new(struct server *s, int fd) + + this->client = client; + this->source = pw_loop_add_io(pw_core_get_main_loop(core), +- fd, SPA_IO_ERR | SPA_IO_HUP, true, connection_data, this); ++ fd, SPA_IO_ERR | SPA_IO_HUP, true, ++ connection_data, this); + if (this->source == NULL) + goto cleanup_client; + +@@ -396,7 +418,7 @@ socket_data(void *data, int fd, enum spa_io mask) + + if (!client->busy) + pw_loop_update_io(client->protocol->core->main_loop, +- c->source, SPA_IO_IN | SPA_IO_ERR | SPA_IO_HUP); ++ c->source, c->source->mask | SPA_IO_IN); + } + + static bool add_socket(struct pw_protocol *protocol, struct server *s) +@@ -479,6 +501,17 @@ on_remote_data(void *data, int fd, enum spa_io mask) + res = -EPIPE; + goto error; + } ++ if (mask & SPA_IO_OUT) { ++ res = pw_protocol_native_connection_flush(conn); ++ if (res >= 0) { ++ int mask = impl->source->mask; ++ SPA_FLAG_UNSET(mask, SPA_IO_OUT); ++ pw_loop_update_io(core->main_loop, ++ impl->source, mask); ++ impl->flushing = false; ++ } else if (res != EAGAIN) ++ goto error; ++ } + + if (mask & SPA_IO_IN) { + const struct pw_protocol_native_message *msg; +@@ -545,23 +578,17 @@ error: + } + + +-static void do_flush_event(void *data, uint64_t count) +-{ +- struct client *impl = data; +- impl->flush_signaled = false; +- if (impl->connection) +- if (pw_protocol_native_connection_flush(impl->connection) < 0) +- impl->this.disconnect(&impl->this); +-} +- + static void on_need_flush(void *data) + { + struct client *impl = data; + struct pw_remote *remote = impl->this.remote; + +- if (!impl->flush_signaled) { +- impl->flush_signaled = true; +- pw_loop_signal_event(remote->core->main_loop, impl->flush_event); ++ if (!impl->flushing) { ++ int mask = impl->source->mask; ++ impl->flushing = true; ++ SPA_FLAG_SET(mask, SPA_IO_OUT); ++ pw_loop_update_io(remote->core->main_loop, ++ impl->source, mask); + } + } + +@@ -619,12 +646,9 @@ static void impl_disconnect(struct pw_protocol_client *client) + static void impl_destroy(struct pw_protocol_client *client) + { + struct client *impl = SPA_CONTAINER_OF(client, struct client, this); +- struct pw_remote *remote = client->remote; + + impl_disconnect(client); + +- pw_loop_destroy_source(remote->core->main_loop, impl->flush_event); +- + if (impl->properties) + pw_properties_free(impl->properties); + +@@ -665,8 +689,6 @@ impl_new_client(struct pw_protocol *protocol, + this->disconnect = impl_disconnect; + this->destroy = impl_destroy; + +- impl->flush_event = pw_loop_add_event(remote->core->main_loop, do_flush_event, impl); +- + spa_list_append(&protocol->client_list, &this->link); + + return this; +@@ -701,10 +723,23 @@ static void on_before_hook(void *_data) + struct pw_protocol_server *this = &server->this; + struct pw_client *client, *tmp; + struct client_data *data; ++ int res; + + spa_list_for_each_safe(client, tmp, &this->client_list, protocol_link) { + data = client->user_data; +- pw_protocol_native_connection_flush(data->connection); ++ ++ res = pw_protocol_native_connection_flush(data->connection); ++ if (res == -EAGAIN) { ++ int mask = data->source->mask; ++ SPA_FLAG_SET(mask, SPA_IO_OUT); ++ pw_loop_update_io(client->protocol->core->main_loop, ++ data->source, mask); ++ } else if (res < 0) { ++ pw_log_warn("client %p: could not flush: %s", ++ data->client, spa_strerror(res)); ++ pw_client_destroy(client); ++ } ++ + } + } + +diff --git a/src/modules/module-protocol-native/connection.c b/src/modules/module-protocol-native/connection.c +index cb592e41..8b9e919d 100644 +--- a/src/modules/module-protocol-native/connection.c ++++ b/src/modules/module-protocol-native/connection.c +@@ -493,8 +493,6 @@ int pw_protocol_native_connection_flush(struct pw_protocol_native_connection *co + continue; + else { + res = -errno; +- pw_log_error("could not sendmsg on fd:%d n_fds:%d: %s", +- conn->fd, n_fds, spa_strerror(res)); + goto exit; + } + } +-- +2.23.0 + diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0019-bluez-add-transport-name-and-use-it-when-emitting-no.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0019-bluez-add-transport-name-and-use-it-when-emitting-no.patch deleted file mode 100644 index 746707e0..00000000 --- a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0019-bluez-add-transport-name-and-use-it-when-emitting-no.patch +++ /dev/null @@ -1,90 +0,0 @@ -From df485216dde74507e5ecb27b9663ab5107c6c5be Mon Sep 17 00:00:00 2001 -From: Julian Bouzas <julian.bouzas@collabora.com> -Date: Thu, 29 Aug 2019 13:59:10 -0400 -Subject: [PATCH] bluez: add transport name and use it when emitting nodes - -Upstream-Status: Pending ---- - spa/plugins/bluez5/bluez5-device.c | 1 + - spa/plugins/bluez5/bluez5-monitor.c | 23 +++++++++++++++++++++++ - spa/plugins/bluez5/defs.h | 1 + - 3 files changed, 25 insertions(+) - -diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c -index 40a340c9..c4380e7a 100644 ---- a/spa/plugins/bluez5/bluez5-device.c -+++ b/spa/plugins/bluez5/bluez5-device.c -@@ -84,6 +84,7 @@ static void emit_node (struct impl *this, struct spa_bt_transport *t, const stru - info = SPA_DEVICE_OBJECT_INFO_INIT(); - info.type = SPA_TYPE_INTERFACE_Node; - info.factory = factory; -+ info.name = t->name; - info.change_mask = SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS; - - /* Pass the transport pointer as a property */ -diff --git a/spa/plugins/bluez5/bluez5-monitor.c b/spa/plugins/bluez5/bluez5-monitor.c -index 2a243715..2914323b 100644 ---- a/spa/plugins/bluez5/bluez5-monitor.c -+++ b/spa/plugins/bluez5/bluez5-monitor.c -@@ -864,6 +864,26 @@ static void transport_free(struct spa_bt_transport *transport) - free(transport); - } - -+static void transport_update_name(struct spa_bt_transport *t) { -+ switch (t->profile) { -+ case SPA_BT_PROFILE_A2DP_SOURCE: -+ case SPA_BT_PROFILE_A2DP_SINK: -+ snprintf (t->name, 256, "bluez5.a2dp %s", t->device->name); -+ break; -+ case SPA_BT_PROFILE_HSP_HS: -+ case SPA_BT_PROFILE_HFP_HF: -+ snprintf (t->name, 256, "bluez5.headunit %s", t->device->name); -+ break; -+ case SPA_BT_PROFILE_HSP_AG: -+ case SPA_BT_PROFILE_HFP_AG: -+ snprintf (t->name, 256, "bluez5.gateway %s", t->device->name); -+ break; -+ default: -+ snprintf (t->name, 256, "bluez5.unknown %s", t->device->name); -+ break; -+ } -+} -+ - static int transport_update_props(struct spa_bt_transport *transport, - DBusMessageIter *props_iter, - DBusMessageIter *invalidated_iter) -@@ -893,9 +913,11 @@ static int transport_update_props(struct spa_bt_transport *transport, - switch (spa_bt_profile_from_uuid(value)) { - case SPA_BT_PROFILE_A2DP_SOURCE: - transport->profile = SPA_BT_PROFILE_A2DP_SINK; -+ transport_update_name(transport); - break; - case SPA_BT_PROFILE_A2DP_SINK: - transport->profile = SPA_BT_PROFILE_A2DP_SOURCE; -+ transport_update_name(transport); - break; - default: - spa_log_warn(monitor->log, "unknown profile %s", value); -@@ -1743,6 +1765,7 @@ static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessag - t->device = d; - spa_list_append(&t->device->transport_list, &t->device_link); - t->profile = profile; -+ transport_update_name(t); - - td = t->user_data; - td->rfcomm.func = rfcomm_event; -diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h -index 7402cdf4..933a6413 100644 ---- a/spa/plugins/bluez5/defs.h -+++ b/spa/plugins/bluez5/defs.h -@@ -207,6 +207,7 @@ struct spa_bt_transport { - struct spa_bt_device *device; - struct spa_list device_link; - enum spa_bt_profile profile; -+ char name[256]; - enum spa_bt_transport_state state; - int codec; - void *configuration; --- -2.23.0.rc1 - diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0020-a2dp-sink-check-if-transport-is-valid-before-releasi.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0020-a2dp-sink-check-if-transport-is-valid-before-releasi.patch deleted file mode 100644 index 746d2451..00000000 --- a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0020-a2dp-sink-check-if-transport-is-valid-before-releasi.patch +++ /dev/null @@ -1,36 +0,0 @@ -From 411fd3d4e7b3f076a14c8eae7be976ce17b686ca Mon Sep 17 00:00:00 2001 -From: Julian Bouzas <julian.bouzas@collabora.com> -Date: Fri, 30 Aug 2019 08:45:11 -0400 -Subject: [PATCH] a2dp-sink: check if transport is valid before releasing it - -Upstream-Status: Pending ---- - spa/plugins/bluez5/a2dp-sink.c | 5 +++-- - 1 file changed, 3 insertions(+), 2 deletions(-) - -diff --git a/spa/plugins/bluez5/a2dp-sink.c b/spa/plugins/bluez5/a2dp-sink.c -index d6d9e7d6..041d75bb 100644 ---- a/spa/plugins/bluez5/a2dp-sink.c -+++ b/spa/plugins/bluez5/a2dp-sink.c -@@ -866,7 +866,7 @@ static int do_remove_source(struct spa_loop *loop, - - static int do_stop(struct impl *this) - { -- int res; -+ int res = 0; - - if (!this->started) - return 0; -@@ -877,7 +877,8 @@ static int do_stop(struct impl *this) - - this->started = false; - -- res = spa_bt_transport_release(this->transport); -+ if (this->transport) -+ res = spa_bt_transport_release(this->transport); - - return res; - } --- -2.23.0.rc1 - diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire_git.bb b/meta-pipewire/recipes-multimedia/pipewire/pipewire_git.bb index 011354d8..823da421 100644 --- a/meta-pipewire/recipes-multimedia/pipewire/pipewire_git.bb +++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire_git.bb @@ -16,17 +16,14 @@ SRC_URI = "gitsm://github.com/PipeWire/pipewire;protocol=https;branch=work \ file://0013-gst-pwaudioringbuffer-wait-only-for-STREAM_STATE_CON.patch \ file://0014-gst-pwaudiosink-set-the-default-latency-time-buffer-.patch \ file://0015-audioconvert-fmtconvert-assume-F32-on-the-other-port.patch \ - file://0016-a2dpsink-fix-infinite-loop-when-buffer-could-not-be-.patch \ - file://0017-bluez5-add-sco-sink-and-sco-src-nodes.patch \ - file://0018-device-add-name-field-in-spa_device_object_info.patch \ - file://0019-bluez-add-transport-name-and-use-it-when-emitting-no.patch \ - file://0020-a2dp-sink-check-if-transport-is-valid-before-releasi.patch \ - file://0021-gst-pwaudioringbuffer-set-node.latency-to-get-schedu.patch \ + file://0016-gst-pwaudioringbuffer-set-node.latency-to-get-schedu.patch \ + file://0017-connection-move-remaining-data-and-fds.patch \ + file://0018-protocol-improve-flushing.patch \ " SRCREV = "4be788962e60891237f1f018627bf709ae3981e6" -PV = "0.2.90+git${SRCPV}+2" +PV = "0.2.90+git${SRCPV}+3" S = "${WORKDIR}/git" RDEPENDS_${PN} += "virtual/pipewire-sessionmanager virtual/pipewire-config" |