aboutsummaryrefslogtreecommitdiffstats
path: root/meta-audio-4a-framework/recipes-connectivity/bluez-alsa/bluez-alsa/0001-Added-a-connection-proxy-plugin.patch
diff options
context:
space:
mode:
Diffstat (limited to 'meta-audio-4a-framework/recipes-connectivity/bluez-alsa/bluez-alsa/0001-Added-a-connection-proxy-plugin.patch')
-rw-r--r--meta-audio-4a-framework/recipes-connectivity/bluez-alsa/bluez-alsa/0001-Added-a-connection-proxy-plugin.patch1010
1 files changed, 0 insertions, 1010 deletions
diff --git a/meta-audio-4a-framework/recipes-connectivity/bluez-alsa/bluez-alsa/0001-Added-a-connection-proxy-plugin.patch b/meta-audio-4a-framework/recipes-connectivity/bluez-alsa/bluez-alsa/0001-Added-a-connection-proxy-plugin.patch
deleted file mode 100644
index fd0f4ea8..00000000
--- a/meta-audio-4a-framework/recipes-connectivity/bluez-alsa/bluez-alsa/0001-Added-a-connection-proxy-plugin.patch
+++ /dev/null
@@ -1,1010 +0,0 @@
-From e730ec274c16d5ba390cb1cd29a8ada16204dd44 Mon Sep 17 00:00:00 2001
-From: Thierry Bultel <thierry.bultel@iot.bzh>
-Date: Wed, 29 Aug 2018 17:02:52 +0200
-Subject: [PATCH] Added a connection proxy plugin
-
-Introduces a new ioplug plugin for alsa.
-This plugin always keeps the opened PCM in a consitent state
-for the sound application, despite of bluealsa server or
-or device disconnection.
-For now, only playback is supported, and only one PCM per application
-is allowed.
-The sound application is in charge of telling the plugin what device
-to use; the bluealsa_proxy_set_remote_device can be opened through
-snd_dlopen. Please see the provided README.md for more details.
-
-Signed-off-by: Stephane Desneux <stephane.desneux@iot.bzh>
----
- src/asound/20-bluealsa_proxy.conf | 41 ++
- src/asound/Makefile.am | 19 +-
- src/asound/README.md | 61 +++
- src/asound/bluealsa-pcm-proxy.c | 811 ++++++++++++++++++++++++++++++++++++++
- 4 files changed, 931 insertions(+), 1 deletion(-)
- create mode 100644 src/asound/20-bluealsa_proxy.conf
- create mode 100644 src/asound/README.md
- create mode 100644 src/asound/bluealsa-pcm-proxy.c
-
-diff --git a/src/asound/20-bluealsa_proxy.conf b/src/asound/20-bluealsa_proxy.conf
-new file mode 100644
-index 0000000..dec1cd8
---- /dev/null
-+++ b/src/asound/20-bluealsa_proxy.conf
-@@ -0,0 +1,41 @@
-+# BlueALSA integration setup
-+
-+defaults.bluealsa.interface "hci0"
-+defaults.bluealsa.profile "a2dp"
-+defaults.bluealsa.delay 20000
-+defaults.bluealsa.battery "yes"
-+
-+ctl.bluealsa {
-+ @args [ HCI BAT ]
-+ @args.HCI {
-+ type string
-+ default {
-+ @func refer
-+ name defaults.bluealsa.interface
-+ }
-+ }
-+ @args.BAT {
-+ type string
-+ default {
-+ @func refer
-+ name defaults.bluealsa.battery
-+ }
-+ }
-+ type bluealsa
-+ interface $HCI
-+ battery $BAT
-+}
-+
-+pcm.bluealsa_proxy {
-+ type plug
-+ slave.pcm {
-+ type bluealsa_proxy
-+ }
-+ hint {
-+ show {
-+ @func refer
-+ name defaults.namehint.extended
-+ }
-+ description "Bluetooth Audio Hub Proxy"
-+ }
-+}
-diff --git a/src/asound/Makefile.am b/src/asound/Makefile.am
-index 75a2dc3..d3b2dfd 100644
---- a/src/asound/Makefile.am
-+++ b/src/asound/Makefile.am
-@@ -5,22 +5,34 @@ EXTRA_DIST = 20-bluealsa.conf
-
- asound_module_ctl_LTLIBRARIES = libasound_module_ctl_bluealsa.la
- asound_module_pcm_LTLIBRARIES = libasound_module_pcm_bluealsa.la
--asound_module_data_DATA = 20-bluealsa.conf
-+asound_module_pcm_proxy_LTLIBRARIES = libasound_module_pcm_bluealsa_proxy.la
-+
-+asound_module_data_DATA = 20-bluealsa.conf 20-bluealsa_proxy.conf
-
- libasound_module_ctl_bluealsa_la_SOURCES = \
- ../shared/ctl-client.c \
- ../shared/log.c \
- bluealsa-ctl.c
-+
- libasound_module_pcm_bluealsa_la_SOURCES = \
- ../shared/ctl-client.c \
- ../shared/log.c \
- ../shared/rt.c \
- bluealsa-pcm.c
-
-+libasound_module_pcm_bluealsa_proxy_la_SOURCES = \
-+ ../shared/ctl-client.c \
-+ ../shared/log.c \
-+ ../shared/rt.c \
-+ bluealsa-pcm-proxy.c
-+
-+
- asound_module_ctldir = @ALSA_PLUGIN_DIR@
- asound_module_pcmdir = @ALSA_PLUGIN_DIR@
-+asound_module_pcm_proxydir = @ALSA_PLUGIN_DIR@
- asound_module_datadir = @ALSA_DATA_DIR@/alsa.conf.d
-
-+
- AM_CFLAGS = \
- -I$(top_srcdir)/src \
- @ALSA_CFLAGS@ \
-@@ -34,3 +46,8 @@ libasound_module_ctl_bluealsa_la_LIBADD = \
- libasound_module_pcm_bluealsa_la_LIBADD = \
- @ALSA_LIBS@ \
- @BLUEZ_LIBS@
-+libasound_module_pcm_bluealsa_proxy_la_LIBADD = \
-+ @ALSA_LIBS@ \
-+ @BLUEZ_LIBS@
-+
-+
-\ No newline at end of file
-diff --git a/src/asound/README.md b/src/asound/README.md
-new file mode 100644
-index 0000000..8ab52a8
---- /dev/null
-+++ b/src/asound/README.md
-@@ -0,0 +1,61 @@
-+# ioplug proxy
-+
-+The ioplug proxy maintains a consitent state of the opened PCM.
-+
-+The application can call snd_pcm_open giving "bluealsa_proxy" as a card name.
-+Only one PCM of this type can be opened by the application, and for now the
-+hci interface is assumed to be hci0.
-+
-+To change to another one, just modify this line in 20-blualsa_proxu.conf
-+defaults.bluealsa.interface "hci0"
-+
-+One the PCM is opened, which always succeeds regardless of the fact that there is
-+a bluetooth device connected or not, the application can set it dynamically using
-+the bluealsa_proxy_set_remote_device function, like in this example code:
-+
-+```
-+#define ALSA_BLUEZ_PROXY_LIB "/usr/lib/alsa-lib/libasound_module_pcm_bluealsa_proxy.so"
-+#define ALSA_BLUEZ_PROXY_SETDEVICE "bluealsa_proxy_set_remote_device"
-+
-+typedef int (*bluealsa_set_remote_device_ptr) (const char * interface, const char * device, const char * profile);
-+
-+static bluealsa_set_remote_device_ptr bluealsa_proxy_set_remote_device = NULL;
-+
-+void alsa_bluez_init() {
-+ static bool initialized = false;
-+ if (initialized)
-+ goto failed;
-+
-+ char errbuf[256];
-+ void * dl = snd_dlopen(ALSA_BLUEZ_PROXY_LIB, RTLD_NOW, errbuf, 256);
-+ if (!dl) {
-+ printf("Failed to open bluealsa proxy plugin\n");
-+ goto failed;
-+ }
-+
-+ void * func = snd_dlsym(dl, ALSA_BLUEZ_PROXY_SETDEVICE, SND_DLSYM_VERSION(SND_PCM_DLSYM_VERSION));
-+ if (!func) {
-+ printf("Unable to find %s symbol\n", ALSA_BLUEZ_PROXY_SETDEVICE);
-+ goto failed;
-+ }
-+
-+ bluealsa_proxy_set_remote_device = func;
-+ initialized = true;
-+
-+failed:
-+ return;
-+}
-+
-+int alsa_bluez_set_device(const char * interface, const char * device, const char * profile) {
-+ if (!bluealsa_proxy_set_remote_device)
-+ return -1;
-+
-+ return bluealsa_proxy_set_remote_device(interface,device,profile);
-+}
-+
-+...
-+alsa_bluez_set_device("hci0","XX.XX.XX.XX.XX", "a2dp");
-+...
-+
-+```
-+
-diff --git a/src/asound/bluealsa-pcm-proxy.c b/src/asound/bluealsa-pcm-proxy.c
-new file mode 100644
-index 0000000..4abab2b
---- /dev/null
-+++ b/src/asound/bluealsa-pcm-proxy.c
-@@ -0,0 +1,811 @@
-+/*
-+ * bluealsa-proxy-pcm.c
-+ * Copyright (c) 2018 Thierry Bultel
-+ *
-+ * This file is a part of bluez-alsa.
-+ *
-+ * This project is licensed under the terms of the MIT license.
-+ *
-+ */
-+
-+#define _GNU_SOURCE
-+#include <errno.h>
-+#include <poll.h>
-+#include <pthread.h>
-+#include <signal.h>
-+#include <stdbool.h>
-+#include <stdlib.h>
-+#include <string.h>
-+#include <unistd.h>
-+#include <sys/eventfd.h>
-+#include <sys/ioctl.h>
-+
-+#include <alsa/asoundlib.h>
-+#include <alsa/pcm_external.h>
-+
-+#include "shared/ctl-client.h"
-+#include "shared/ctl-proto.h"
-+#include "shared/log.h"
-+#include "shared/rt.h"
-+
-+#undef debug
-+#define debug printf
-+
-+#define INTERFACE_STR_MAXLEN 256
-+#define BDADDR_STR_LEN 18
-+#define PROFILE_STR_MAXLEN 16
-+
-+struct bluealsa_pcm {
-+ snd_pcm_ioplug_t io;
-+
-+ /* bluealsa socket */
-+ int fd;
-+
-+ /* event file descriptor */
-+ int event_fd;
-+
-+ /* requested transport */
-+ struct msg_transport * transport;
-+ size_t pcm_buffer_size;
-+
-+ int pcm_fd;
-+
-+ /* virtual hardware - ring buffer */
-+ snd_pcm_uframes_t io_ptr;
-+ pthread_t io_thread;
-+ bool io_started;
-+
-+ /* communication and encoding/decoding delay */
-+ snd_pcm_sframes_t delay;
-+ /* user provided extra delay component */
-+ snd_pcm_sframes_t delay_ex;
-+
-+ /* ALSA operates on frames, we on bytes */
-+ size_t frame_size;
-+
-+ /* In order to see whether the PCM has reached under-run (or over-run), we
-+ * have to know the exact position of the hardware and software pointers.
-+ * To do so, we could use the HW pointer provided by the IO plug structure.
-+ * This pointer is updated by the snd_pcm_hwsync() function, which is not
-+ * thread-safe (total disaster is guaranteed). Since we can not call this
-+ * function, we are going to use our own HW pointer, which can be updated
-+ * safely in our IO thread. */
-+ snd_pcm_uframes_t io_hw_boundary;
-+ snd_pcm_uframes_t io_hw_ptr;
-+
-+ char interface[INTERFACE_STR_MAXLEN];
-+ bdaddr_t addr;
-+ char profile[PROFILE_STR_MAXLEN];
-+ enum pcm_type type;
-+ enum pcm_stream stream;
-+
-+};
-+
-+typedef struct bluealsa_pcm bluealsa_pcm_t;
-+
-+static bluealsa_pcm_t * the_pcm = NULL;
-+
-+static int close_bluez_connection();
-+static int open_bluez_connection();
-+
-+
-+/**
-+ * Helper function for closing PCM transport. */
-+static int close_transport(struct bluealsa_pcm *pcm) {
-+
-+ debug("%s ...\n", __func__);
-+ if (pcm->transport == NULL)
-+ return 0;
-+
-+ int rv = bluealsa_close_transport(pcm->fd, pcm->transport);
-+ int err = errno;
-+
-+ close(pcm->pcm_fd);
-+ pcm->pcm_fd = -1;
-+ errno = err;
-+ return rv;
-+}
-+
-+/**
-+ * IO thread, which facilitates ring buffer. */
-+static void *io_thread(void *arg) {
-+ snd_pcm_ioplug_t *io = (snd_pcm_ioplug_t *)arg;
-+
-+ struct bluealsa_pcm *pcm = io->private_data;
-+
-+ sigset_t sigset;
-+ sigemptyset(&sigset);
-+
-+ /* Block signal, which will be used for pause/resume actions. */
-+ sigaddset(&sigset, SIGIO);
-+ /* Block SIGPIPE, so we could receive EPIPE while writing to the pipe
-+ * whose reading end has been closed. This will allow clean playback
-+ * termination. */
-+ sigaddset(&sigset, SIGPIPE);
-+
-+ if ((errno = pthread_sigmask(SIG_BLOCK, &sigset, NULL)) != 0) {
-+ SNDERR("Thread signal mask error: %s", strerror(errno));
-+ goto final;
-+ }
-+
-+wait_pcm_fd:
-+
-+ debug("PLUGIN io-thread: wait for pcm_fd\n");
-+
-+ /* In the capture mode, the PCM FIFO is opened in the non-blocking mode.
-+ * So right now, we have to synchronize write and read sides, otherwise
-+ * reading might return 0, which will be incorrectly recognized as FIFO
-+ * close signal, but in fact it means, that it was not opened yet. */
-+ if (io->stream == SND_PCM_STREAM_CAPTURE) {
-+
-+ /* Is the data fd ready ? */
-+ if (pcm->pcm_fd == -1) {
-+ usleep(100*1000); // TODO This should be replaced by a semaphore
-+ goto wait_pcm_fd;
-+ }
-+
-+ /* check both fd status */
-+
-+ struct pollfd pfds[2] = {
-+ { pcm->pcm_fd, POLLIN, 0 },
-+ { pcm->fd, POLLIN|POLLPRI, 0 }};
-+
-+ int ret = poll(pfds, 2, -1);
-+ if (ret == -1) {
-+ SNDERR("PCM FIFO poll error: %s", strerror(errno));
-+ goto final;
-+ }
-+
-+ short int evt0 = pfds[0].revents;
-+ short int evt1 = pfds[1].revents;
-+
-+ if (evt1 & POLLHUP) {
-+ debug("Server closed the connection\n");
-+ close_bluez_connection();
-+ open_bluez_connection();
-+ usleep(100*1000); /* avoid spinning too fast /*/
-+ goto wait_pcm_fd;
-+ }
-+
-+ if (evt0 & POLLHUP) {
-+ debug("Remote device disconnected\n");
-+ close_bluez_connection();
-+ open_bluez_connection();
-+ usleep(100*1000);
-+ goto wait_pcm_fd;
-+ }
-+ }
-+
-+ const snd_pcm_channel_area_t *areas = snd_pcm_ioplug_mmap_areas(io);
-+
-+ struct asrsync asrs;
-+ asrsync_init(asrs, io->rate);
-+
-+ for (;;) {
-+
-+ int tmp;
-+ switch (io->state) {
-+ case SND_PCM_STATE_RUNNING:
-+ case SND_PCM_STATE_DRAINING:
-+ break;
-+ case SND_PCM_STATE_DISCONNECTED:
-+ debug("PLUGIN: DISCONNECTED\n");
-+ goto final;
-+ default:
-+ sigwait(&sigset, &tmp);
-+ asrsync_init(asrs, io->rate);
-+ }
-+
-+ snd_pcm_uframes_t io_ptr = pcm->io_ptr;
-+ snd_pcm_uframes_t io_buffer_size = io->buffer_size;
-+ snd_pcm_uframes_t io_hw_ptr = pcm->io_hw_ptr;
-+ snd_pcm_uframes_t io_hw_boundary = pcm->io_hw_boundary;
-+ snd_pcm_uframes_t frames = io->period_size;
-+ char *buffer = areas->addr + (areas->first + areas->step * io_ptr) / 8;
-+ char *head = buffer;
-+ ssize_t ret = 0;
-+ size_t len;
-+
-+ /* If the leftover in the buffer is less than a whole period sizes,
-+ * adjust the number of frames which should be transfered. It has
-+ * turned out, that the buffer might contain fractional number of
-+ * periods - it could be an ALSA bug, though, it has to be handled. */
-+ if (io_buffer_size - io_ptr < frames)
-+ frames = io_buffer_size - io_ptr;
-+
-+ /* IO operation size in bytes */
-+ len = frames * pcm->frame_size;
-+ io_ptr += frames;
-+
-+ if (io_ptr >= io_buffer_size)
-+ io_ptr -= io_buffer_size;
-+
-+ io_hw_ptr += frames;
-+ if (io_hw_ptr >= io_hw_boundary)
-+ io_hw_ptr -= io_hw_boundary;
-+
-+ if (io->stream == SND_PCM_STREAM_CAPTURE) {
-+
-+ /* Read the whole period "atomically". This will assure, that frames
-+ * are not fragmented, so the pointer can be correctly updated. */
-+ while (len != 0 && (ret = read(pcm->pcm_fd, head, len)) != 0) {
-+ if (ret == -1) {
-+ if (errno == EINTR)
-+ continue;
-+
-+ SNDERR("PCM FIFO read error: %s", strerror(errno));
-+ goto final;
-+ }
-+ head += ret;
-+ len -= ret;
-+ }
-+
-+ /* something went wrong. this can be a server,
-+ * or device disconnection, or a device change request
-+ * from the client application */
-+
-+ if (ret == 0)
-+ goto wait_pcm_fd;
-+
-+ }
-+ else {
-+
-+ /* check for under-run and act accordingly */
-+ if (io_hw_ptr > io->appl_ptr) {
-+ io->state = SND_PCM_STATE_XRUN;
-+ io_ptr = -1;
-+ goto sync;
-+ }
-+
-+ /* Perform atomic write - see the explanation above. */
-+ do {
-+ if ((ret = write(pcm->pcm_fd, head, len)) == -1) {
-+ if (errno == EINTR)
-+ continue;
-+ SNDERR("PCM FIFO write error: %s", strerror(errno));
-+ goto final;
-+ }
-+ head += ret;
-+ len -= ret;
-+ }
-+ while (len != 0);
-+
-+ /* synchronize playback time */
-+ asrsync_sync(&asrs, frames);
-+ }
-+
-+sync:
-+ pcm->io_ptr = io_ptr;
-+ pcm->io_hw_ptr = io_hw_ptr;
-+
-+ eventfd_write(pcm->event_fd, 1);
-+ }
-+
-+final:
-+
-+ debug("PLUGIN: io_thread exiting\n");
-+
-+ close_transport(pcm);
-+ eventfd_write(pcm->event_fd, 0xDEAD0000);
-+
-+ return NULL;
-+}
-+
-+static int bluealsa_proxy_start(snd_pcm_ioplug_t *io) {
-+ struct bluealsa_pcm *pcm = io->private_data;
-+
-+ debug("%s ...\n", __func__);
-+
-+ /* If the IO thread is already started, skip thread creation. Otherwise,
-+ * we might end up with a bunch of IO threads reading or writing to the
-+ * same FIFO simultaneously. Instead, just send resume signal. */
-+ if (pcm->io_started) {
-+ io->state = SND_PCM_STATE_RUNNING;
-+ pthread_kill(pcm->io_thread, SIGIO);
-+ return 0;
-+ }
-+
-+ /* initialize delay calculation */
-+ pcm->delay = 0;
-+
-+ if (pcm->transport && bluealsa_pause_transport(pcm->fd, pcm->transport, false) == -1) {
-+ debug("Couldn't start PCM: %s\n", strerror(errno));
-+ return -errno;
-+ }
-+
-+ /* State has to be updated before the IO thread is created - if the state
-+ * does not indicate "running", the IO thread will be suspended until the
-+ * "resume" signal is delivered. This requirement is only (?) theoretical,
-+ * anyhow forewarned is forearmed. */
-+ snd_pcm_state_t prev_state = io->state;
-+ io->state = SND_PCM_STATE_RUNNING;
-+
-+ pcm->io_started = true;
-+
-+ return 0;
-+}
-+
-+static int bluealsa_proxy_stop(snd_pcm_ioplug_t *io) {
-+ struct bluealsa_pcm *pcm = io->private_data;
-+
-+ debug("Stopping");
-+ if (pcm->io_started) {
-+ pcm->io_started = false;
-+ pthread_cancel(pcm->io_thread);
-+ pthread_join(pcm->io_thread, NULL);
-+ }
-+ return 0;
-+}
-+
-+static snd_pcm_sframes_t bluealsa_proxy_pointer(snd_pcm_ioplug_t *io) {
-+ struct bluealsa_pcm *pcm = io->private_data;
-+ if (pcm->pcm_fd == -1)
-+ return -ENODEV;
-+
-+ return pcm->io_ptr;
-+}
-+
-+static int bluealsa_proxy_close(snd_pcm_ioplug_t *io) {
-+ struct bluealsa_pcm *pcm = io->private_data;
-+
-+ debug("Closing plugin\n");
-+ close(pcm->fd);
-+ close(pcm->event_fd);
-+ free(pcm);
-+
-+ the_pcm = NULL;
-+
-+ return 0;
-+}
-+
-+static int bluealsa_proxy_hw_params(snd_pcm_ioplug_t *io, snd_pcm_hw_params_t *params) {
-+ struct bluealsa_pcm *pcm = io->private_data;
-+ (void)params;
-+
-+ pcm->frame_size = (snd_pcm_format_physical_width(io->format) * io->channels) / 8;
-+
-+ /* Indicate that our PCM is ready for writing, even though is is not 100%
-+ * true - IO thread is not running yet. Some weird implementations might
-+ * require PCM to be writable before the snd_pcm_start() call. */
-+ if (io->stream == SND_PCM_STREAM_PLAYBACK)
-+ eventfd_write(pcm->event_fd, 1);
-+
-+ if (pcm->io.stream == SND_PCM_STREAM_PLAYBACK) {
-+ /* By default, the size of the pipe buffer is set to a too large value for
-+ * our purpose. On modern Linux system it is 65536 bytes. Large buffer in
-+ * the playback mode might contribute to an unnecessary audio delay. Since
-+ * it is possible to modify the size of this buffer we will set is to some
-+ * low value, but big enough to prevent audio tearing. Note, that the size
-+ * will be rounded up to the page size (typically 4096 bytes). */
-+ pcm->pcm_buffer_size = fcntl(pcm->pcm_fd, F_SETPIPE_SZ, 2048);
-+ debug("FIFO buffer size: %zd", pcm->pcm_buffer_size);
-+ }
-+
-+ debug("Selected HW buffer: %zd periods x %zd bytes %c= %zd bytes\n",
-+ io->buffer_size / io->period_size, pcm->frame_size * io->period_size,
-+ io->period_size * (io->buffer_size / io->period_size) == io->buffer_size ? '=' : '<',
-+ io->buffer_size * pcm->frame_size);
-+
-+ return 0;
-+}
-+
-+static int bluealsa_proxy_hw_free(snd_pcm_ioplug_t *io) {
-+ struct bluealsa_pcm *pcm = io->private_data;
-+ debug("Freeing HW");
-+
-+ if (close_transport(pcm) == -1)
-+ return -errno;
-+ return 0;
-+}
-+
-+static int bluealsa_proxy_sw_params(snd_pcm_ioplug_t *io, snd_pcm_sw_params_t *params) {
-+ struct bluealsa_pcm *pcm = io->private_data;
-+ snd_pcm_sw_params_get_boundary(params, &pcm->io_hw_boundary);
-+ return 0;
-+}
-+
-+static int bluealsa_proxy_prepare(snd_pcm_ioplug_t *io) {
-+ struct bluealsa_pcm *pcm = io->private_data;
-+
-+ /* initialize ring buffer */
-+ pcm->io_hw_ptr = 0;
-+ pcm->io_ptr = 0;
-+
-+ return 0;
-+}
-+
-+static int bluealsa_proxy_drain(snd_pcm_ioplug_t *io) {
-+ struct bluealsa_pcm *pcm = io->private_data;
-+
-+ if (bluealsa_drain_transport(pcm->fd, pcm->transport) == -1)
-+ return -errno;
-+ return 0;
-+}
-+
-+static int bluealsa_proxy_pause(snd_pcm_ioplug_t *io, int enable) {
-+ struct bluealsa_pcm *pcm = io->private_data;
-+
-+ if (bluealsa_pause_transport(pcm->fd, pcm->transport, enable) == -1)
-+ return -errno;
-+
-+ if (enable == 0) {
-+ io->state = SND_PCM_STATE_RUNNING;
-+ pthread_kill(pcm->io_thread, SIGIO);
-+ }
-+
-+ /* Even though PCM transport is paused, our IO thread is still running. If
-+ * the implementer relies on the PCM file descriptor readiness, we have to
-+ * bump our internal event trigger. Otherwise, client might stuck forever
-+ * in the poll/select system call. */
-+ eventfd_write(pcm->event_fd, 1);
-+
-+ return 0;
-+}
-+
-+static void bluealsa_proxy_dump(snd_pcm_ioplug_t *io, snd_output_t *out) {
-+ struct bluealsa_pcm *pcm = io->private_data;
-+ char addr[18];
-+
-+ if (pcm->transport == NULL) {
-+ snd_output_printf(out, "Bluetooth Proxy: no transport yet\n");
-+ return;
-+ }
-+
-+ ba2str(&pcm->transport->addr, addr);
-+ snd_output_printf(out, "Bluetooth Proxy device: %s\n", addr);
-+ snd_output_printf(out, "Bluetooth Proxy profile: %d\n", pcm->transport->type);
-+ snd_output_printf(out, "Bluetooth Proxy codec: %d\n", pcm->transport->codec);
-+
-+}
-+
-+static int bluealsa_proxy_delay(snd_pcm_ioplug_t *io, snd_pcm_sframes_t *delayp) {
-+ struct bluealsa_pcm *pcm = io->private_data;
-+
-+ if (pcm->pcm_fd == -1)
-+ return -ENODEV;
-+
-+ /* Exact calculation of the PCM delay is very hard, if not impossible. For
-+ * the sake of simplicity we will make few assumptions and approximations.
-+ * In general, the delay is proportional to the number of bytes queued in
-+ * the FIFO buffer, the time required to encode data, Bluetooth transfer
-+ * latency and the time required by the device to decode and play audio. */
-+
-+ static int counter = 0;
-+ snd_pcm_sframes_t delay = 0;
-+ unsigned int size;
-+
-+ /* bytes queued in the PCM ring buffer */
-+ delay += io->appl_ptr - io->hw_ptr;
-+
-+ /* bytes queued in the FIFO buffer */
-+ if (ioctl(pcm->pcm_fd, FIONREAD, &size) != -1)
-+ delay += size / pcm->frame_size;
-+
-+ /* On the server side, the delay stat will not be available until the PCM
-+ * data transfer is started. Do not make an unnecessary call then. */
-+ if ((io->state == SND_PCM_STATE_RUNNING || io->state == SND_PCM_STATE_DRAINING)) {
-+
-+ /* data transfer (communication) and encoding/decoding */
-+ if (io->stream == SND_PCM_STREAM_PLAYBACK &&
-+ (pcm->delay == 0 || ++counter % (io->rate / 10) == 0)) {
-+
-+ unsigned int tmp;
-+ if ((tmp = bluealsa_get_transport_delay(pcm->fd, pcm->transport)) != -1) {
-+ pcm->delay = (io->rate / 100) * tmp / 100;
-+ debug("BlueALSA delay: %.1f ms (%ld frames)", (float)tmp / 10, pcm->delay);
-+ }
-+
-+ }
-+
-+ }
-+
-+ *delayp = delay + pcm->delay + pcm->delay_ex;
-+ return 0;
-+}
-+
-+static int bluealsa_proxy_poll_descriptors_count(snd_pcm_ioplug_t *io) {
-+ (void)io;
-+ return 1;
-+}
-+
-+static int bluealsa_proxy_poll_descriptors(snd_pcm_ioplug_t *io, struct pollfd *pfd,
-+ unsigned int space) {
-+ struct bluealsa_pcm *pcm = io->private_data;
-+
-+ if (space != 1)
-+ return -EINVAL;
-+
-+ pfd[0].fd = pcm->event_fd;
-+ pfd[0].events = POLLIN;
-+
-+ return 1;
-+}
-+
-+static int bluealsa_proxy_poll_revents(snd_pcm_ioplug_t *io, struct pollfd *pfd,
-+ unsigned int nfds, unsigned short *revents) {
-+ struct bluealsa_pcm *pcm = io->private_data;
-+
-+ if (nfds != 1) {
-+ return -EINVAL;
-+ }
-+
-+ if (pcm->pcm_fd == -1) {
-+ return -ENODEV;
-+ }
-+
-+ if (pfd[0].revents & POLLIN) {
-+
-+ eventfd_t event;
-+ eventfd_read(pcm->event_fd, &event);
-+
-+ if (event & 0xDEAD0000) {
-+ goto fail;
-+ }
-+
-+ /* If the event was triggered prematurely, wait for another one.
-+ * This causes a deadlock if */
-+ if (!snd_pcm_avail_update(io->pcm)) {
-+ return *revents = 0;
-+ }
-+
-+ /* ALSA expects that the event will match stream direction, e.g.
-+ * playback will not start if the event is for reading. */
-+ *revents = io->stream == SND_PCM_STREAM_CAPTURE ? POLLIN : POLLOUT;
-+
-+ }
-+ else
-+ *revents = 0;
-+
-+ return 0;
-+
-+fail:
-+ *revents = POLLERR | POLLHUP;
-+ return -ENODEV;
-+}
-+
-+static const snd_pcm_ioplug_callback_t bluealsa_proxy_callback = {
-+ .start = bluealsa_proxy_start,
-+ .stop = bluealsa_proxy_stop,
-+ .pointer = bluealsa_proxy_pointer,
-+ .close = bluealsa_proxy_close,
-+ .hw_params = bluealsa_proxy_hw_params,
-+ .hw_free = bluealsa_proxy_hw_free,
-+ .sw_params = bluealsa_proxy_sw_params,
-+ .prepare = bluealsa_proxy_prepare,
-+ .drain = bluealsa_proxy_drain,
-+ .pause = bluealsa_proxy_pause,
-+ .dump = bluealsa_proxy_dump,
-+ .delay = bluealsa_proxy_delay,
-+ .poll_descriptors_count = bluealsa_proxy_poll_descriptors_count,
-+ .poll_descriptors = bluealsa_proxy_poll_descriptors,
-+ .poll_revents = bluealsa_proxy_poll_revents,
-+};
-+
-+static enum pcm_type bluealsa_proxy_parse_profile(const char *profile) {
-+
-+ if (profile == NULL)
-+ return PCM_TYPE_NULL;
-+
-+ if (strcasecmp(profile, "a2dp") == 0)
-+ return PCM_TYPE_A2DP;
-+ else if (strcasecmp(profile, "sco") == 0)
-+ return PCM_TYPE_SCO;
-+
-+ return PCM_TYPE_NULL;
-+}
-+
-+/* This must be called when a transport is available */
-+
-+static int bluealsa_proxy_set_hw_constraint(struct bluealsa_pcm *pcm) {
-+ snd_pcm_ioplug_t *io = &pcm->io;
-+
-+ static const snd_pcm_access_t accesses[] = {
-+ SND_PCM_ACCESS_MMAP_INTERLEAVED,
-+ SND_PCM_ACCESS_RW_INTERLEAVED,
-+ };
-+ static const unsigned int formats[] = {
-+ SND_PCM_FORMAT_S16_LE,
-+ };
-+
-+ int err;
-+
-+ debug("Setting constraints\n");
-+
-+ if ((err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_ACCESS,
-+ sizeof(accesses) / sizeof(*accesses), accesses)) < 0)
-+ return err;
-+
-+ if ((err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_FORMAT,
-+ sizeof(formats) / sizeof(*formats), formats)) < 0)
-+ return err;
-+
-+ if ((err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIODS,
-+ 2, 1024)) < 0)
-+ return err;
-+
-+ /* In order to prevent audio tearing and minimize CPU utilization, we're
-+ * going to setup buffer size constraints. These limits are derived from
-+ * the transport sampling rate and the number of channels, so the buffer
-+ * "time" size will be constant. The minimal period size and buffer size
-+ * are respectively 10 ms and 200 ms. Upper limits are not constraint. */
-+ unsigned int min_p = pcm->transport->sampling * 10 / 1000 * pcm->transport->channels * 2;
-+ unsigned int min_b = pcm->transport->sampling * 200 / 1000 * pcm->transport->channels * 2;
-+
-+ if ((err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIOD_BYTES,
-+ min_p, 1024 * 16)) < 0)
-+ return err;
-+
-+ if ((err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_BUFFER_BYTES,
-+ min_b, 1024 * 1024 * 16)) < 0)
-+ return err;
-+
-+ if ((err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_CHANNELS,
-+ pcm->transport->channels, pcm->transport->channels)) < 0)
-+ return err;
-+
-+ if ((err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_RATE,
-+ pcm->transport->sampling, pcm->transport->sampling)) < 0)
-+ return err;
-+
-+ return 0;
-+}
-+
-+
-+SND_PCM_PLUGIN_DEFINE_FUNC(bluealsa_proxy) {
-+ (void)root;
-+
-+ struct bluealsa_pcm *pcm;
-+ long delay = 0;
-+ int ret;
-+
-+ if ((pcm = calloc(1, sizeof(*pcm))) == NULL)
-+ return -ENOMEM;
-+
-+ pcm->fd = -1;
-+ pcm->event_fd = -1;
-+ pcm->pcm_fd = -1;
-+ pcm->delay_ex = delay;
-+
-+ if ((pcm->event_fd = eventfd(0, EFD_CLOEXEC)) == -1) {
-+ ret = -errno;
-+ goto fail;
-+ }
-+
-+ pcm->io.version = SND_PCM_IOPLUG_VERSION;
-+ pcm->io.name = "BlueALSA";
-+ pcm->io.flags = SND_PCM_IOPLUG_FLAG_LISTED;
-+ pcm->io.mmap_rw = 1;
-+ pcm->io.callback = &bluealsa_proxy_callback;
-+ pcm->io.private_data = pcm;
-+ pcm->transport = NULL;
-+
-+ enum pcm_stream _stream = stream == SND_PCM_STREAM_PLAYBACK ?
-+ PCM_STREAM_PLAYBACK : PCM_STREAM_CAPTURE;
-+
-+ pcm->stream = stream;
-+
-+ if ((ret = snd_pcm_ioplug_create(&pcm->io, name, stream, mode)) < 0)
-+ goto fail;
-+
-+ *pcmp = pcm->io.pcm;
-+
-+ // Remember PCM
-+ the_pcm = pcm;
-+
-+ if ((errno = pthread_create(&pcm->io_thread, NULL, io_thread, &pcm->io)) != 0) {
-+ debug("Couldn't create IO thread: %s", strerror(errno));
-+ pcm->io_started = false;
-+
-+ ret = -errno;
-+ goto fail;
-+ }
-+
-+ pthread_setname_np(pcm->io_thread, "pcm-io");
-+ return 0;
-+
-+fail:
-+ if (pcm->fd != -1)
-+ close(pcm->fd);
-+ if (pcm->event_fd != -1)
-+ close(pcm->event_fd);
-+ free(pcm);
-+ return ret;
-+}
-+
-+SND_PCM_PLUGIN_SYMBOL(bluealsa_proxy);
-+
-+static int open_bluez_connection() {
-+ int ret = 0;
-+ bluealsa_pcm_t * pcm = the_pcm;
-+
-+ char addr[256];
-+ ba2str(&pcm->addr, addr);
-+
-+ debug("%s interface %s addr %s type %d\n", __func__, pcm->interface, addr, pcm->type);
-+
-+ if ((pcm->fd = bluealsa_open(pcm->interface)) == -1) {
-+ SNDERR("BlueALSA connection failed: %s", strerror(errno));
-+ ret = -errno;
-+ goto fail;
-+ }
-+
-+ if ((pcm->transport = bluealsa_get_transport(pcm->fd, pcm->addr, pcm->type, pcm->stream)) == NULL) {
-+ SNDERR("Couldn't get BlueALSA transport: %s", strerror(errno));
-+ ret = -errno;
-+ goto fail;
-+ }
-+
-+ if ((ret = bluealsa_proxy_set_hw_constraint(pcm)) < 0) {
-+ snd_pcm_ioplug_delete(&pcm->io);
-+ goto fail;
-+ }
-+
-+ pcm->transport->stream = pcm->stream;
-+
-+ if ((pcm->pcm_fd = bluealsa_open_transport(pcm->fd, pcm->transport)) == -1) {
-+ debug("Couldn't open PCM FIFO: %s", strerror(errno));
-+ return -errno;
-+ }
-+
-+ debug("PLUGIN: starting transport\n");
-+
-+ if (bluealsa_pause_transport(pcm->fd, pcm->transport, false) == -1) {
-+ debug("Couldn't start PCM: %s", strerror(errno));
-+ return -errno;
-+ }
-+
-+ debug("PLUGIN: connection ready !\n");
-+
-+ return 0;
-+
-+fail:
-+ return ret;
-+}
-+
-+static int close_bluez_connection() {
-+ int ret = 0;
-+ bluealsa_pcm_t * pcm = the_pcm;
-+ close_transport(pcm);
-+ close(pcm->fd);
-+ return ret;
-+}
-+
-+/* This is an exported function, to be loaded by clients via dlsym */
-+
-+int bluealsa_proxy_set_remote_device(const char * interface, const char * device, const char * profile) {
-+ int ret = -1;
-+
-+ debug("PLUGIN: %s: interface %s device %s profile %s\n", __func__, interface, device, profile);
-+
-+ if (the_pcm == NULL) {
-+ SNDERR("No current opened connection to bluezalsa server\n");
-+ goto failed;
-+ }
-+
-+ enum pcm_type type;
-+
-+ if (device == NULL || str2ba(device, &the_pcm->addr) != 0) {
-+ SNDERR("Invalid BT device address: %s", device);
-+ ret = -EINVAL;
-+ goto failed;
-+ }
-+
-+ if ((type = bluealsa_proxy_parse_profile(profile)) == PCM_TYPE_NULL) {
-+ SNDERR("Invalid BT profile [a2dp, sco]: %s", profile);
-+ ret = -EINVAL;
-+ goto failed;
-+ }
-+
-+ strncpy(the_pcm->profile, profile, PROFILE_STR_MAXLEN);
-+ strncpy(the_pcm->interface, interface, INTERFACE_STR_MAXLEN);
-+
-+ the_pcm->type = type;
-+
-+ close_bluez_connection();
-+ ret = open_bluez_connection();
-+
-+failed:
-+ return ret;
-+}
-+
-+SND_DLSYM_BUILD_VERSION(bluealsa_proxy_set_remote_device, SND_PCM_DLSYM_VERSION);
---
-2.1.4
-