#include <pulsecore/modargs.h>
#include <pulsecore/poll.h>
#include <pulsecore/rtpoll.h>
+#include <pulsecore/shared.h>
#include <pulsecore/socket-util.h>
#include <pulsecore/thread.h>
#include <pulsecore/thread-mq.h>
pa_core *core;
pa_hook_slot *device_connection_changed_slot;
+ pa_hook_slot *transport_state_changed_slot;
pa_bluetooth_discovery *discovery;
pa_bluetooth_device *device;
void *d;
ssize_t l;
size_t to_write, to_decode;
+ size_t total_written = 0;
a2dp_prepare_buffer(u);
pa_assert((size_t) l <= sbc_info->buffer_size);
- u->read_index += (uint64_t) l;
-
/* TODO: get timestamp from rtp */
if (!found_tstamp) {
/* pa_log_warn("Couldn't find SO_TIMESTAMP data in auxiliary recvmsg() data!"); */
tstamp = pa_rtclock_now();
}
- pa_smoother_put(u->read_smoother, tstamp, pa_bytes_to_usec(u->read_index, &u->sample_spec));
- pa_smoother_resume(u->read_smoother, tstamp, true);
-
p = (uint8_t*) sbc_info->buffer + sizeof(*header) + sizeof(*payload);
to_decode = l - sizeof(*header) - sizeof(*payload);
pa_log_error("SBC decoding error (%li)", (long) decoded);
pa_memblock_release(memchunk.memblock);
pa_memblock_unref(memchunk.memblock);
- return -1;
+ return 0;
}
+ total_written += written;
+
/* Reset frame length, it can be changed due to bitpool change */
sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc);
to_write -= written;
}
+ u->read_index += (uint64_t) total_written;
+ pa_smoother_put(u->read_smoother, tstamp, pa_bytes_to_usec(u->read_index, &u->sample_spec));
+ pa_smoother_resume(u->read_smoother, tstamp, true);
+
memchunk.length -= to_write;
pa_memblock_release(memchunk.memblock);
u->read_smoother = pa_smoother_new(PA_USEC_PER_SEC, 2*PA_USEC_PER_SEC, true, true, 10, pa_rtclock_now(), true);
}
+/* Run from IO thread */
+static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ struct userdata *u = PA_SOURCE(o)->userdata;
+ bool failed = false;
+ int r;
+
+ pa_assert(u->source == PA_SOURCE(o));
+ pa_assert(u->transport);
+
+ switch (code) {
+
+ case PA_SOURCE_MESSAGE_SET_STATE:
+
+ switch ((pa_source_state_t) PA_PTR_TO_UINT(data)) {
+
+ case PA_SOURCE_SUSPENDED:
+ /* Ignore if transition is PA_SOURCE_INIT->PA_SOURCE_SUSPENDED */
+ if (!PA_SOURCE_IS_OPENED(u->source->thread_info.state))
+ break;
+
+ /* Stop the device if the sink is suspended as well */
+ if (!u->sink || u->sink->state == PA_SINK_SUSPENDED)
+ transport_release(u);
+
+ if (u->read_smoother)
+ pa_smoother_pause(u->read_smoother, pa_rtclock_now());
+
+ break;
+
+ case PA_SOURCE_IDLE:
+ case PA_SOURCE_RUNNING:
+ if (u->source->thread_info.state != PA_SOURCE_SUSPENDED)
+ break;
+
+ /* Resume the device if the sink was suspended as well */
+ if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state)) {
+ if (transport_acquire(u, false) < 0)
+ failed = true;
+ else
+ setup_stream(u);
+ }
+
+ /* We don't resume the smoother here. Instead we
+ * wait until the first packet arrives */
+
+ break;
+
+ case PA_SOURCE_UNLINKED:
+ case PA_SOURCE_INIT:
+ case PA_SOURCE_INVALID_STATE:
+ break;
+ }
+
+ break;
+
+ case PA_SOURCE_MESSAGE_GET_LATENCY: {
+ pa_usec_t wi, ri;
+
+ if (u->read_smoother) {
+ wi = pa_smoother_get(u->read_smoother, pa_rtclock_now());
+ ri = pa_bytes_to_usec(u->read_index, &u->sample_spec);
+
+ *((pa_usec_t*) data) = FIXED_LATENCY_RECORD_A2DP + wi > ri ? FIXED_LATENCY_RECORD_A2DP + wi - ri : 0;
+ } else
+ *((pa_usec_t*) data) = 0;
+
+ return 0;
+ }
+
+ }
+
+ r = pa_source_process_msg(o, code, data, offset, chunk);
+
+ return (r < 0 || !failed) ? r : -1;
+}
+
/* Run from main thread */
static int add_source(struct userdata *u) {
pa_source_new_data data;
}
u->source->userdata = u;
+ u->source->parent.process_msg = source_process_msg;
return 0;
}
if (n_read < 0)
goto io_fail;
- /* We just read something, so we are supposed to write something, too */
- pending_read_bytes += n_read;
- do_write += pending_read_bytes / u->write_block_size;
- pending_read_bytes = pending_read_bytes % u->write_block_size;
+ if (n_read > 0) {
+ /* We just read something, so we are supposed to write something, too */
+ pending_read_bytes += n_read;
+ do_write += pending_read_bytes / u->write_block_size;
+ pending_read_bytes = pending_read_bytes % u->write_block_size;
+ }
}
}
pa_assert_se(output_port = pa_hashmap_get(ports, u->output_port_name));
if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SINK)) {
- /* TODO: Change this profile's name to a2dp_sink, to reflect the remote
- * device's role and be consistent with the a2dp source profile */
- cp = pa_card_profile_new("a2dp", _("High Fidelity Playback (A2DP Sink)"), sizeof(pa_bluetooth_profile_t));
+ cp = pa_card_profile_new("a2dp_sink", _("High Fidelity Playback (A2DP Sink)"), sizeof(pa_bluetooth_profile_t));
cp->priority = 10;
cp->n_sinks = 1;
cp->n_sources = 0;
off:
stop_thread(u);
- pa_assert_se(pa_card_set_profile(u->card, "off", false) >= 0);
+ pa_assert_se(pa_card_set_profile(u->card, pa_hashmap_get(u->card->profiles, "off"), false) >= 0);
return -PA_ERR_IO;
}
return 0;
}
+/* Run from main thread */
+static void handle_transport_state_change(struct userdata *u, struct pa_bluetooth_transport *t) {
+ bool acquire = false;
+ bool release = false;
+ pa_card_profile *cp;
+ pa_device_port *port;
+
+ pa_assert(u);
+ pa_assert(t);
+
+ /* Update profile availability */
+ if (!(cp = pa_hashmap_get(u->card->profiles, pa_bluetooth_profile_to_string(t->profile))))
+ return;
+ pa_card_profile_set_available(cp, transport_state_to_availability(t->state));
+
+ /* Update port availability */
+ pa_assert_se(port = pa_hashmap_get(u->card->ports, u->output_port_name));
+ pa_device_port_set_available(port, get_port_availability(u, PA_DIRECTION_OUTPUT));
+ pa_assert_se(port = pa_hashmap_get(u->card->ports, u->input_port_name));
+ pa_device_port_set_available(port, get_port_availability(u, PA_DIRECTION_INPUT));
+
+ /* Acquire or release transport as needed */
+ acquire = (t->state == PA_BLUETOOTH_TRANSPORT_STATE_PLAYING && u->profile == t->profile);
+ release = (t->state != PA_BLUETOOTH_TRANSPORT_STATE_PLAYING && u->profile == t->profile);
+
+ if (acquire && transport_acquire(u, true) >= 0) {
+ if (u->source) {
+ pa_log_debug("Resuming source %s because its transport state changed to playing", u->source->name);
+
+ /* We remove the IDLE suspend cause, because otherwise
+ * module-loopback doesn't uncork its streams. FIXME: Messing with
+ * the IDLE suspend cause here is wrong, the correct way to handle
+ * this would probably be to uncork the loopback streams not only
+ * when the other end is unsuspended, but also when the other end's
+ * suspend cause changes to IDLE only (currently there's no
+ * notification mechanism for suspend cause changes, though). */
+ pa_source_suspend(u->source, false, PA_SUSPEND_IDLE|PA_SUSPEND_USER);
+ }
+
+ if (u->sink) {
+ pa_log_debug("Resuming sink %s because its transport state changed to playing", u->sink->name);
+
+ /* FIXME: See the previous comment. */
+ pa_sink_suspend(u->sink, false, PA_SUSPEND_IDLE|PA_SUSPEND_USER);
+ }
+ }
+
+ if (release && u->transport_acquired) {
+ /* FIXME: this release is racy, since the audio stream might have
+ * been set up again in the meantime (but not processed yet by PA).
+ * BlueZ should probably release the transport automatically, and in
+ * that case we would just mark the transport as released */
+
+ /* Remote side closed the stream so we consider it PA_SUSPEND_USER */
+ if (u->source) {
+ pa_log_debug("Suspending source %s because the remote end closed the stream", u->source->name);
+ pa_source_suspend(u->source, true, PA_SUSPEND_USER);
+ }
+
+ if (u->sink) {
+ pa_log_debug("Suspending sink %s because the remote end closed the stream", u->sink->name);
+ pa_sink_suspend(u->sink, true, PA_SUSPEND_USER);
+ }
+ }
+}
+
/* Run from main thread */
static pa_hook_result_t device_connection_changed_cb(pa_bluetooth_discovery *y, const pa_bluetooth_device *d, struct userdata *u) {
pa_assert(d);
return PA_HOOK_OK;
}
+/* Run from main thread */
+static pa_hook_result_t transport_state_changed_cb(pa_bluetooth_discovery *y, pa_bluetooth_transport *t, struct userdata *u) {
+ pa_assert(t);
+ pa_assert(u);
+
+ if (t == u->transport && t->state <= PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED)
+ pa_assert_se(pa_card_set_profile(u->card, pa_hashmap_get(u->card->profiles, "off"), false) >= 0);
+
+ if (t->device == u->device)
+ handle_transport_state_change(u, t);
+
+ return PA_HOOK_OK;
+}
+
/* Run from main thread context */
static int device_process_msg(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) {
struct bluetooth_msg *u = BLUETOOTH_MSG(obj);
break;
pa_log_debug("Switching the profile to off due to IO thread failure.");
- pa_assert_se(pa_card_set_profile(u->card, "off", false) >= 0);
+ pa_assert_se(pa_card_set_profile(u->card, pa_hashmap_get(u->card->profiles, "off"), false) >= 0);
break;
}
goto fail;
}
- if (!(u->discovery = pa_bluetooth_discovery_get(m->core)))
+ if ((u->discovery = pa_shared_get(u->core, "bluetooth-discovery")))
+ pa_bluetooth_discovery_ref(u->discovery);
+ else {
+ pa_log_error("module-bluez5-discover doesn't seem to be loaded, refusing to load module-bluez5-device");
goto fail;
+ }
if (!(u->device = pa_bluetooth_discovery_get_device_by_path(u->discovery, path))) {
pa_log_error("%s is unknown", path);
pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED),
PA_HOOK_NORMAL, (pa_hook_cb_t) device_connection_changed_cb, u);
+ u->transport_state_changed_slot =
+ pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED),
+ PA_HOOK_NORMAL, (pa_hook_cb_t) transport_state_changed_cb, u);
+
if (add_card(u) < 0)
goto fail;
off:
stop_thread(u);
- pa_assert_se(pa_card_set_profile(u->card, "off", false) >= 0);
+ pa_assert_se(pa_card_set_profile(u->card, pa_hashmap_get(u->card->profiles, "off"), false) >= 0);
return 0;
if (u->device_connection_changed_slot)
pa_hook_slot_free(u->device_connection_changed_slot);
+ if (u->transport_state_changed_slot)
+ pa_hook_slot_free(u->transport_state_changed_slot);
+
if (u->sbc_info.buffer)
pa_xfree(u->sbc_info.buffer);
pa_xfree(u);
}
+
+int pa__get_n_used(pa_module *m) {
+ struct userdata *u;
+
+ pa_assert(m);
+ pa_assert_se(u = m->userdata);
+
+ return (u->sink ? pa_sink_linked_by(u->sink) : 0) + (u->source ? pa_source_linked_by(u->source) : 0);
+}