bluez5: midi-node: implement server role

This commit is contained in:
Pauli Virtanen 2022-11-05 22:25:40 +02:00 committed by Wim Taymans
parent d30a0c5ee6
commit 2ef126885a
4 changed files with 208 additions and 38 deletions

View file

@ -129,6 +129,7 @@ extern "C" {
#define SPA_KEY_API_BLUEZ5_CODEC "api.bluez5.codec" /**< a bluetooth codec */ #define SPA_KEY_API_BLUEZ5_CODEC "api.bluez5.codec" /**< a bluetooth codec */
#define SPA_KEY_API_BLUEZ5_CLASS "api.bluez5.class" /**< a bluetooth class */ #define SPA_KEY_API_BLUEZ5_CLASS "api.bluez5.class" /**< a bluetooth class */
#define SPA_KEY_API_BLUEZ5_ICON "api.bluez5.icon" /**< a bluetooth icon */ #define SPA_KEY_API_BLUEZ5_ICON "api.bluez5.icon" /**< a bluetooth icon */
#define SPA_KEY_API_BLUEZ5_ROLE "api.bluez5.role" /**< "client" or "server" */
/** keys for jack api */ /** keys for jack api */
#define SPA_KEY_API_JACK "api.jack" /**< key for the JACK api */ #define SPA_KEY_API_JACK "api.jack" /**< key for the JACK api */

View file

@ -145,6 +145,7 @@ static void emit_chr_node(struct impl *impl, struct chr *chr, struct device *dev
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_ADDRESS, device->address); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_ADDRESS, device->address);
snprintf(class, sizeof(class), "0x%06x", device->class); snprintf(class, sizeof(class), "0x%06x", device->class);
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_CLASS, class); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_CLASS, class);
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_ROLE, "client");
info.props = &SPA_DICT_INIT(items, n_items); info.props = &SPA_DICT_INIT(items, n_items);
spa_device_emit_object_info(&impl->hooks, chr->id, &info); spa_device_emit_object_info(&impl->hooks, chr->id, &info);

View file

@ -74,6 +74,11 @@ static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.bluez5.midi.node")
#define MIDI_RINGBUF_SIZE (8192*4) #define MIDI_RINGBUF_SIZE (8192*4)
enum node_role {
NODE_SERVER,
NODE_CLIENT,
};
struct props { struct props {
char clock_name[64]; char clock_name[64];
int64_t latency_offset; int64_t latency_offset;
@ -147,6 +152,8 @@ struct port {
unsigned int acquired:1; unsigned int acquired:1;
DBusPendingCall *acquire_call; DBusPendingCall *acquire_call;
struct spa_source source;
struct impl *impl; struct impl *impl;
}; };
@ -155,6 +162,7 @@ struct impl {
struct spa_node node; struct spa_node node;
struct spa_log *log; struct spa_log *log;
struct spa_loop *main_loop;
struct spa_loop *data_loop; struct spa_loop *data_loop;
struct spa_system *data_system; struct spa_system *data_system;
struct spa_dbus *dbus; struct spa_dbus *dbus;
@ -184,7 +192,6 @@ struct impl {
unsigned int started:1; unsigned int started:1;
unsigned int following:1; unsigned int following:1;
struct spa_source source;
struct spa_source timer_source; struct spa_source timer_source;
int timerfd; int timerfd;
@ -205,6 +212,10 @@ struct impl {
uint8_t read_buffer[MIDI_MAX_MTU]; uint8_t read_buffer[MIDI_MAX_MTU];
struct spa_bt_midi_writer writer; struct spa_bt_midi_writer writer;
enum node_role role;
struct spa_bt_midi_server *server;
}; };
#define CHECK_PORT(this,d,p) ((p) == 0 && ((d) == SPA_DIRECTION_INPUT || (d) == SPA_DIRECTION_OUTPUT)) #define CHECK_PORT(this,d,p) ((p) == 0 && ((d) == SPA_DIRECTION_INPUT || (d) == SPA_DIRECTION_OUTPUT))
@ -474,13 +485,50 @@ static void midi_event_recv(void *user_data, uint16_t timestamp, uint8_t *data,
} }
} }
static int unacquire_port(struct port *port)
{
struct impl *this = port->impl;
if (!port->acquired)
return 0;
spa_log_debug(this->log, "%p: unacquire port:%d", this, port->direction);
shutdown(port->fd, SHUT_RDWR);
close(port->fd);
port->fd = -1;
port->acquired = false;
if (this->server)
spa_bt_midi_server_released(this->server,
(port->direction == SPA_DIRECTION_OUTPUT));
return 0;
}
static int do_unacquire_port(struct spa_loop *loop, bool async, uint32_t seq,
const void *data, size_t size, void *user_data)
{
struct port *port = user_data;
/* in main thread */
unacquire_port(port);
return 0;
}
static void on_ready_read(struct spa_source *source) static void on_ready_read(struct spa_source *source)
{ {
struct impl *this = source->data; struct port *port = source->data;
struct port *port = &this->ports[PORT_OUT]; struct impl *this = port->impl;
struct timespec now; struct timespec now;
int res, size, last_timestamp; int res, size, last_timestamp;
if (SPA_FLAG_IS_SET(source->rmask, SPA_IO_ERR) ||
SPA_FLAG_IS_SET(source->rmask, SPA_IO_HUP)) {
spa_log_debug(this->log, "%p: port:%d ERR/HUP", this, port->direction);
goto stop;
}
spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &now); spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &now);
/* read data from socket */ /* read data from socket */
@ -500,6 +548,13 @@ again:
if (SPA_UNLIKELY(spa_log_level_topic_enabled(this->log, SPA_LOG_TOPIC_DEFAULT, SPA_LOG_LEVEL_TRACE))) if (SPA_UNLIKELY(spa_log_level_topic_enabled(this->log, SPA_LOG_TOPIC_DEFAULT, SPA_LOG_LEVEL_TRACE)))
spa_log_hexdump(this->log, SPA_LOG_LEVEL_DEBUG, 4, this->read_buffer, size); spa_log_hexdump(this->log, SPA_LOG_LEVEL_DEBUG, 4, this->read_buffer, size);
if (port->direction != SPA_DIRECTION_OUTPUT) {
/* Just monitor errors for the input port */
spa_log_debug(this->log, "%p: port:%d is not RX port; ignoring data",
this, port->direction);
return;
}
/* prepare for producing events */ /* prepare for producing events */
if (port->io == NULL || port->n_buffers == 0 || !this->started) if (port->io == NULL || port->n_buffers == 0 || !this->started)
return; return;
@ -599,8 +654,13 @@ again:
return; return;
stop: stop:
if (this->source.loop) spa_log_debug(this->log, "%p: port:%d stopping port", this, port->direction);
spa_loop_remove_source(this->data_loop, &this->source);
if (port->source.loop)
spa_loop_remove_source(this->data_loop, &port->source);
/* port->acquired is updated only from the main thread */
spa_loop_invoke(this->main_loop, do_unacquire_port, 0, NULL, 0, false, port);
} }
static int process_output(struct impl *this) static int process_output(struct impl *this)
@ -870,6 +930,7 @@ static void acquire_reply(DBusPendingCall **call_ptr, DBusMessage *r)
spa_log_error(this->log, "%s.%s() failed for %s: %s", spa_log_error(this->log, "%s.%s() failed for %s: %s",
BLUEZ_GATT_CHR_INTERFACE, method, BLUEZ_GATT_CHR_INTERFACE, method,
this->chr_path, dbus_message_get_error_name(r)); this->chr_path, dbus_message_get_error_name(r));
do_stop(this);
do_release(this); do_release(this);
return; return;
} }
@ -880,6 +941,7 @@ static void acquire_reply(DBusPendingCall **call_ptr, DBusMessage *r)
DBUS_TYPE_INVALID)) { DBUS_TYPE_INVALID)) {
spa_log_error(this->log, "%s.%s for %s: invalid return value", spa_log_error(this->log, "%s.%s for %s: invalid return value",
BLUEZ_GATT_CHR_INTERFACE, method, this->chr_path); BLUEZ_GATT_CHR_INTERFACE, method, this->chr_path);
do_stop(this);
do_release(this); do_release(this);
return; return;
} }
@ -893,15 +955,13 @@ static void acquire_reply(DBusPendingCall **call_ptr, DBusMessage *r)
if (port->direction == SPA_DIRECTION_OUTPUT) { if (port->direction == SPA_DIRECTION_OUTPUT) {
spa_bt_midi_parser_init(&this->parser); spa_bt_midi_parser_init(&this->parser);
update_position(this);
/* Start source */ /* Start source */
this->source.data = this; port->source.data = port;
this->source.fd = port->fd; port->source.fd = port->fd;
this->source.func = on_ready_read; port->source.func = on_ready_read;
this->source.mask = SPA_IO_IN; port->source.mask = SPA_IO_IN | SPA_IO_HUP | SPA_IO_ERR;
this->source.rmask = 0; port->source.rmask = 0;
spa_loop_add_source(this->data_loop, &this->source); spa_loop_add_source(this->data_loop, &port->source);
} }
} }
@ -918,9 +978,9 @@ static int do_acquire(struct port *port)
if (port->acquire_call) if (port->acquire_call)
return 0; return 0;
spa_log_debug(this->log, spa_log_info(this->log,
"%p: acquiring BLE MIDI device characteristic %s", "%p: port %d: client %s for BLE MIDI device characteristic %s",
this, this->chr_path); this, port->direction, method, this->chr_path);
m = dbus_message_new_method_call(BLUEZ_SERVICE, m = dbus_message_new_method_call(BLUEZ_SERVICE,
this->chr_path, this->chr_path,
@ -937,6 +997,78 @@ static int do_acquire(struct port *port)
acquire_reply); acquire_reply);
} }
static int server_do_acquire(struct port *port, int fd, uint16_t mtu)
{
struct impl *this = port->impl;
const char *method = (port->direction == SPA_DIRECTION_OUTPUT) ?
"AcquireWrite" : "AcquireNotify";
spa_log_info(this->log,
"%p: port %d: server %s for BLE MIDI device characteristic %s",
this, port->direction, method, this->server->chr_path);
if (port->acquired) {
spa_log_info(this->log,
"%p: port %d: %s failed: already acquired",
this, port->direction, method);
return -EBUSY;
}
port->fd = fd;
port->mtu = mtu;
if (port->direction == SPA_DIRECTION_OUTPUT)
spa_bt_midi_parser_init(&this->parser);
/* Start source */
port->source.data = port;
port->source.fd = port->fd;
port->source.func = on_ready_read;
port->source.mask = SPA_IO_HUP | SPA_IO_ERR;
if (port->direction == SPA_DIRECTION_OUTPUT)
port->source.mask |= SPA_IO_IN;
port->source.rmask = 0;
spa_loop_add_source(this->data_loop, &port->source);
port->acquired = true;
return 0;
}
static int server_acquire_write(void *user_data, int fd, uint16_t mtu)
{
struct impl *this = user_data;
return server_do_acquire(&this->ports[PORT_OUT], fd, mtu);
}
static int server_acquire_notify(void *user_data, int fd, uint16_t mtu)
{
struct impl *this = user_data;
return server_do_acquire(&this->ports[PORT_IN], fd, mtu);
}
static int server_release(void *user_data)
{
struct impl *this = user_data;
do_release(this);
return 0;
}
static int do_remove_port_source(struct spa_loop *loop,
bool async, uint32_t seq, const void *data, size_t size, void *user_data)
{
struct impl *this = user_data;
int i;
for (i = 0; i < N_PORTS; ++i) {
struct port *port = &this->ports[i];
if (port->source.loop)
spa_loop_remove_source(this->data_loop, &port->source);
}
return 0;
}
static int do_remove_source(struct spa_loop *loop, static int do_remove_source(struct spa_loop *loop,
bool async, bool async,
uint32_t seq, uint32_t seq,
@ -947,9 +1079,6 @@ static int do_remove_source(struct spa_loop *loop,
struct impl *this = user_data; struct impl *this = user_data;
struct itimerspec ts; struct itimerspec ts;
if (this->source.loop)
spa_loop_remove_source(this->data_loop, &this->source);
if (this->timer_source.loop) if (this->timer_source.loop)
spa_loop_remove_source(this->data_loop, &this->timer_source); spa_loop_remove_source(this->data_loop, &this->timer_source);
@ -971,6 +1100,7 @@ static int do_stop(struct impl *this)
spa_loop_invoke(this->data_loop, do_remove_source, 0, NULL, 0, true, this); spa_loop_invoke(this->data_loop, do_remove_source, 0, NULL, 0, true, this);
this->started = false; this->started = false;
return res; return res;
} }
@ -981,19 +1111,14 @@ static int do_release(struct impl *this)
spa_log_debug(this->log, "%p: release", this); spa_log_debug(this->log, "%p: release", this);
do_stop(this); spa_loop_invoke(this->data_loop, do_remove_port_source, 0, NULL, 0, true, this);
for (i = 0; i < N_PORTS; ++i) { for (i = 0; i < N_PORTS; ++i) {
struct port *port = &this->ports[i]; struct port *port = &this->ports[i];
spa_dbus_async_call_cancel(&port->acquire_call); spa_dbus_async_call_cancel(&port->acquire_call);
if (port->fd > 0) { unacquire_port(port);
shutdown(port->fd, SHUT_RDWR);
close(port->fd);
port->fd = -1;
port->mtu = 0;
}
} }
return res; return res;
@ -1017,10 +1142,23 @@ static int do_start(struct impl *this)
for (i = 0; i < N_PORTS; ++i) { for (i = 0; i < N_PORTS; ++i) {
struct port *port = &this->ports[i]; struct port *port = &this->ports[i];
/* Acquire Bluetooth I/O */ switch (this->role) {
if ((res = do_acquire(port)) < 0) { case NODE_CLIENT:
do_release(this); /* Acquire Bluetooth I/O */
return res; if ((res = do_acquire(port)) < 0) {
do_stop(this);
do_release(this);
return res;
}
break;
case NODE_SERVER:
/*
* In MIDI server role, the device/BlueZ invokes
* the acquire asynchronously as available/needed.
*/
break;
default:
spa_assert_not_reached();
} }
reset_buffers(port); reset_buffers(port);
@ -1223,7 +1361,7 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags,
static int impl_node_send_command(void *object, const struct spa_command *command) static int impl_node_send_command(void *object, const struct spa_command *command)
{ {
struct impl *this = object; struct impl *this = object;
int res; int res, res2;
spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(this != NULL, -EINVAL);
spa_return_val_if_fail(command != NULL, -EINVAL); spa_return_val_if_fail(command != NULL, -EINVAL);
@ -1234,10 +1372,20 @@ static int impl_node_send_command(void *object, const struct spa_command *comman
return res; return res;
break; break;
case SPA_NODE_COMMAND_Pause: case SPA_NODE_COMMAND_Pause:
case SPA_NODE_COMMAND_Suspend:
if ((res = do_stop(this)) < 0) if ((res = do_stop(this)) < 0)
return res; return res;
break; break;
case SPA_NODE_COMMAND_Suspend:
res = do_stop(this);
if (this->role == NODE_CLIENT)
res2 = do_release(this);
else
res2 = 0;
if (res < 0)
return res;
if (res2 < 0)
return res2;
break;
default: default:
return -ENOTSUP; return -ENOTSUP;
} }
@ -1662,6 +1810,12 @@ static const struct spa_node_methods impl_node = {
.process = impl_node_process, .process = impl_node_process,
}; };
static const struct spa_bt_midi_server_cb impl_server = {
.acquire_write = server_acquire_write,
.acquire_notify = server_acquire_notify,
.release = server_release,
};
static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface)
{ {
struct impl *this; struct impl *this;
@ -1683,6 +1837,7 @@ static int impl_clear(struct spa_handle *handle)
{ {
struct impl *this = (struct impl *) handle; struct impl *this = (struct impl *) handle;
do_stop(this);
do_release(this); do_release(this);
free(this->chr_path); free(this->chr_path);
@ -1692,6 +1847,8 @@ static int impl_clear(struct spa_handle *handle)
dbus_connection_unref(this->conn); dbus_connection_unref(this->conn);
if (this->dbus_connection) if (this->dbus_connection)
spa_dbus_connection_destroy(this->dbus_connection); spa_dbus_connection_destroy(this->dbus_connection);
if (this->server)
spa_bt_midi_server_destroy(this->server);
spa_zero(*this); spa_zero(*this);
@ -1725,6 +1882,7 @@ impl_init(const struct spa_handle_factory *factory,
this = (struct impl *) handle; this = (struct impl *) handle;
this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log);
this->main_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop);
this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop);
this->data_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataSystem); this->data_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataSystem);
this->dbus = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DBus); this->dbus = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DBus);
@ -1747,14 +1905,21 @@ impl_init(const struct spa_handle_factory *factory,
return -EINVAL; return -EINVAL;
} }
this->role = NODE_CLIENT;
if (info) { if (info) {
const char *str; const char *str;
if ((str = spa_dict_lookup(info, SPA_KEY_API_BLUEZ5_PATH)) != NULL) if ((str = spa_dict_lookup(info, SPA_KEY_API_BLUEZ5_PATH)) != NULL)
this->chr_path = strdup(str); this->chr_path = strdup(str);
if ((str = spa_dict_lookup(info, SPA_KEY_API_BLUEZ5_ROLE)) != NULL) {
if (spa_streq(str, "server"))
this->role = NODE_SERVER;
}
} }
if (this->chr_path == NULL) { if (this->role == NODE_CLIENT && this->chr_path == NULL) {
spa_log_error(this->log, "missing MIDI service characteristic path"); spa_log_error(this->log, "missing MIDI service characteristic path");
res = -EINVAL; res = -EINVAL;
goto fail; goto fail;
@ -1856,6 +2021,13 @@ impl_init(const struct spa_handle_factory *factory,
set_latency(this, false); set_latency(this, false);
if (this->role == NODE_SERVER) {
this->server = spa_bt_midi_server_new(this->conn,
&impl_server, this->log, this);
if (this->server == NULL)
goto fail;
}
this->timerfd = spa_system_timerfd_create(this->data_system, this->timerfd = spa_system_timerfd_create(this->data_system,
CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK);

View file

@ -424,11 +424,7 @@ static void adapter_remove(struct spa_dbus_object *object)
{ {
struct adapter *adapter = SPA_CONTAINER_OF(object, struct adapter, object); struct adapter *adapter = SPA_CONTAINER_OF(object, struct adapter, object);
if (adapter->register_call) { spa_dbus_async_call_cancel(&adapter->register_call);
dbus_pending_call_cancel(adapter->register_call);
dbus_pending_call_unref(adapter->register_call);
adapter->register_call = NULL;
}
} }
static void bluez_remove(struct spa_dbus_object *object) static void bluez_remove(struct spa_dbus_object *object)