summaryrefslogtreecommitdiffstats
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, 1010 insertions, 0 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
new file mode 100644
index 00000000..fd0f4ea8
--- /dev/null
+++ b/meta-audio-4a-framework/recipes-connectivity/bluez-alsa/bluez-alsa/0001-Added-a-connection-proxy-plugin.patch
@@ -0,0 +1,1010 @@
+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
+