From: Luiz Augusto von Dentz <luiz.von.dentz@xxxxxxxxx> ALSA support depend on unix support that is now removed. --- Makefile.am | 29 - acinclude.m4 | 16 +- audio/bluetooth.conf | 36 - audio/ctl_bluetooth.c | 383 ----------- audio/pcm_bluetooth.c | 1785 ------------------------------------------------- configure.ac | 1 - 6 files changed, 1 insertion(+), 2249 deletions(-) delete mode 100644 audio/bluetooth.conf delete mode 100644 audio/ctl_bluetooth.c delete mode 100644 audio/pcm_bluetooth.c diff --git a/Makefile.am b/Makefile.am index aaae42e..51156fb 100644 --- a/Makefile.am +++ b/Makefile.am @@ -335,35 +335,6 @@ EXTRA_DIST += src/genbuiltin src/bluetooth.conf src/org.bluez.service \ audio/telephony-maemo6.c sap/sap-dummy.c sap/sap-u8500.c \ proximity/proximity.conf -if ALSA -alsadir = $(libdir)/alsa-lib - -alsa_LTLIBRARIES = audio/libasound_module_pcm_bluetooth.la \ - audio/libasound_module_ctl_bluetooth.la - -audio_libasound_module_pcm_bluetooth_la_SOURCES = audio/pcm_bluetooth.c \ - audio/rtp.h audio/ipc.h audio/ipc.c -audio_libasound_module_pcm_bluetooth_la_LDFLAGS = $(AM_LDFLAGS) -module \ - -avoid-version -audio_libasound_module_pcm_bluetooth_la_LIBADD = sbc/libsbc.la \ - lib/libbluetooth-private.la @ALSA_LIBS@ -audio_libasound_module_pcm_bluetooth_la_CFLAGS = $(AM_CFLAGS) @ALSA_CFLAGS@ - -audio_libasound_module_ctl_bluetooth_la_SOURCES = audio/ctl_bluetooth.c \ - audio/rtp.h audio/ipc.h audio/ipc.c -audio_libasound_module_ctl_bluetooth_la_LDFLAGS = $(AM_LDFLAGS) -module \ - -avoid-version -audio_libasound_module_ctl_bluetooth_la_LIBADD = \ - lib/libbluetooth-private.la @ALSA_LIBS@ -audio_libasound_module_ctl_bluetooth_la_CFLAGS = $(AM_CFLAGS) @ALSA_CFLAGS@ - -if DATAFILES -alsaconfdir = $(datadir)/alsa - -alsaconf_DATA = audio/bluetooth.conf -endif -endif - if AUDIOPLUGIN if GSTREAMER gstreamerdir = $(libdir)/gstreamer-0.10 diff --git a/acinclude.m4 b/acinclude.m4 index 1d6d736..ab0ebba 100644 --- a/acinclude.m4 +++ b/acinclude.m4 @@ -114,13 +114,6 @@ AC_DEFUN([AC_PATH_GSTREAMER], [ AC_SUBST(GSTREAMER_PLUGINSDIR) ]) -AC_DEFUN([AC_PATH_ALSA], [ - PKG_CHECK_MODULES(ALSA, alsa, alsa_found=yes, alsa_found=no) - AC_CHECK_LIB(rt, clock_gettime, ALSA_LIBS="$ALSA_LIBS -lrt", alsa_found=no) - AC_SUBST(ALSA_CFLAGS) - AC_SUBST(ALSA_LIBS) -]) - AC_DEFUN([AC_PATH_USB], [ PKG_CHECK_MODULES(USB, libusb, usb_found=yes, usb_found=no) AC_SUBST(USB_CFLAGS) @@ -176,7 +169,6 @@ AC_DEFUN([AC_ARG_BLUEZ], [ sndfile_enable=${sndfile_found} hal_enable=no usb_enable=${usb_found} - alsa_enable=${alsa_found} gstreamer_enable=${gstreamer_found} audio_enable=yes input_enable=yes @@ -257,10 +249,6 @@ AC_DEFUN([AC_ARG_BLUEZ], [ gstreamer_enable=${enableval} ]) - AC_ARG_ENABLE(alsa, AC_HELP_STRING([--enable-alsa], [enable ALSA support]), [ - alsa_enable=${enableval} - ]) - AC_ARG_ENABLE(usb, AC_HELP_STRING([--enable-usb], [enable USB support]), [ usb_enable=${enableval} ]) @@ -368,9 +356,7 @@ AC_DEFUN([AC_ARG_BLUEZ], [ AM_CONDITIONAL(SNDFILE, test "${sndfile_enable}" = "yes" && test "${sndfile_found}" = "yes") AM_CONDITIONAL(USB, test "${usb_enable}" = "yes" && test "${usb_found}" = "yes") - AM_CONDITIONAL(SBC, test "${alsa_enable}" = "yes" || test "${gstreamer_enable}" = "yes" || - test "${test_enable}" = "yes") - AM_CONDITIONAL(ALSA, test "${alsa_enable}" = "yes" && test "${alsa_found}" = "yes") + AM_CONDITIONAL(SBC, test "${gstreamer_enable}" = "yes" || test "${test_enable}" = "yes") AM_CONDITIONAL(GSTREAMER, test "${gstreamer_enable}" = "yes" && test "${gstreamer_found}" = "yes") AM_CONDITIONAL(AUDIOPLUGIN, test "${audio_enable}" = "yes") AM_CONDITIONAL(INPUTPLUGIN, test "${input_enable}" = "yes") diff --git a/audio/bluetooth.conf b/audio/bluetooth.conf deleted file mode 100644 index 55b51e4..0000000 --- a/audio/bluetooth.conf +++ /dev/null @@ -1,36 +0,0 @@ -# Please note that this ALSA configuration file fragment needs be enabled in -# /etc/asound.conf or a similar configuration file with directives similar to -# the following: -# -#@hooks [ -# { -# func load -# files [ -# "/etc/alsa/bluetooth.conf" -# ] -# errors false -# } -#] - -pcm.rawbluetooth { - @args [ ADDRESS ] - @args.ADDRESS { - type string - } - type bluetooth - device $ADDRESS -} - -pcm.bluetooth { - @args [ ADDRESS ] - @args.ADDRESS { - type string - } - type plug - slave { - pcm { - type bluetooth - device $ADDRESS - } - } -} diff --git a/audio/ctl_bluetooth.c b/audio/ctl_bluetooth.c deleted file mode 100644 index a16f476..0000000 --- a/audio/ctl_bluetooth.c +++ /dev/null @@ -1,383 +0,0 @@ -/* - * - * BlueZ - Bluetooth protocol stack for Linux - * - * Copyright (C) 2004-2010 Marcel Holtmann <marcel@xxxxxxxxxxxx> - * - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <sys/socket.h> -#include <sys/un.h> - -#include <alsa/asoundlib.h> -#include <alsa/control_external.h> - -#include <bluetooth/bluetooth.h> - -#include "ipc.h" - -#ifdef ENABLE_DEBUG -#define DBG(fmt, arg...) printf("DEBUG: %s: " fmt "\n" , __FUNCTION__ , ## arg) -#else -#define DBG(fmt, arg...) -#endif - -#define BLUETOOTH_MINVOL 0 -#define BLUETOOTH_MAXVOL 15 - -struct bluetooth_data { - snd_ctl_ext_t ext; - int sock; -}; - -enum { - BLUETOOTH_PLAYBACK, - BLUETOOTH_CAPTURE, -}; - -static const char *vol_devices[2] = { - [BLUETOOTH_PLAYBACK] = "Playback volume", - [BLUETOOTH_CAPTURE] = "Capture volume", -}; - -static void bluetooth_exit(struct bluetooth_data *data) -{ - if (data == NULL) - return; - - if (data->sock >= 0) - bt_audio_service_close(data->sock); - - free(data); -} - -static void bluetooth_close(snd_ctl_ext_t *ext) -{ - struct bluetooth_data *data = ext->private_data; - - DBG("ext %p", ext); - - bluetooth_exit(data); -} - -static int bluetooth_elem_count(snd_ctl_ext_t *ext) -{ - DBG("ext %p", ext); - - return 2; -} - -static int bluetooth_elem_list(snd_ctl_ext_t *ext, - unsigned int offset, snd_ctl_elem_id_t *id) -{ - DBG("ext %p offset %d id %p", ext, offset, id); - - snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER); - - if (offset > 1) - return -EINVAL; - - snd_ctl_elem_id_set_name(id, vol_devices[offset]); - - return 0; -} - -static snd_ctl_ext_key_t bluetooth_find_elem(snd_ctl_ext_t *ext, - const snd_ctl_elem_id_t *id) -{ - const char *name = snd_ctl_elem_id_get_name(id); - int i; - - DBG("ext %p id %p name '%s'", ext, id, name); - - for (i = 0; i < 2; i++) - if (strcmp(name, vol_devices[i]) == 0) - return i; - - return SND_CTL_EXT_KEY_NOT_FOUND; -} - -static int bluetooth_get_attribute(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, - int *type, unsigned int *acc, unsigned int *count) -{ - DBG("ext %p key %ld", ext, key); - - *type = SND_CTL_ELEM_TYPE_INTEGER; - *acc = SND_CTL_EXT_ACCESS_READWRITE; - *count = 1; - - return 0; -} - -static int bluetooth_get_integer_info(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, - long *imin, long *imax, long *istep) -{ - DBG("ext %p key %ld", ext, key); - - *istep = 1; - *imin = BLUETOOTH_MINVOL; - *imax = BLUETOOTH_MAXVOL; - - return 0; -} - -static int bluetooth_send_ctl(struct bluetooth_data *data, - uint8_t mode, uint8_t key, struct bt_control_rsp *rsp) -{ - int ret; - struct bt_control_req *req = (void *) rsp; - bt_audio_error_t *err = (void *) rsp; - const char *type, *name; - - memset(req, 0, BT_SUGGESTED_BUFFER_SIZE); - req->h.type = BT_REQUEST; - req->h.name = BT_CONTROL; - req->h.length = sizeof(*req); - - req->mode = mode; - req->key = key; - - ret = send(data->sock, req, BT_SUGGESTED_BUFFER_SIZE, MSG_NOSIGNAL); - if (ret <= 0) { - SYSERR("Unable to request new volume value to server"); - return -errno; - } - - ret = recv(data->sock, rsp, BT_SUGGESTED_BUFFER_SIZE, 0); - if (ret <= 0) { - SNDERR("Unable to receive new volume value from server"); - return -errno; - } - - if (rsp->h.type == BT_ERROR) { - SNDERR("BT_CONTROL failed : %s (%d)", - strerror(err->posix_errno), - err->posix_errno); - return -err->posix_errno; - } - - type = bt_audio_strtype(rsp->h.type); - if (!type) { - SNDERR("Bogus message type %d " - "received from audio service", - rsp->h.type); - return -EINVAL; - } - - name = bt_audio_strname(rsp->h.name); - if (!name) { - SNDERR("Bogus message name %d " - "received from audio service", - rsp->h.name); - return -EINVAL; - } - - if (rsp->h.name != BT_CONTROL) { - SNDERR("Unexpected message %s received", type); - return -EINVAL; - } - - return 0; -} - -static int bluetooth_read_integer(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, - long *value) -{ - struct bluetooth_data *data = ext->private_data; - int ret; - char buf[BT_SUGGESTED_BUFFER_SIZE]; - struct bt_control_rsp *rsp = (void *) buf; - - DBG("ext %p key %ld", ext, key); - - memset(buf, 0, sizeof(buf)); - *value = 0; - - ret = bluetooth_send_ctl(data, key, 0, rsp); - if (ret == 0) - *value = rsp->key; - - return ret; -} - -static int bluetooth_write_integer(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, - long *value) -{ - struct bluetooth_data *data = ext->private_data; - char buf[BT_SUGGESTED_BUFFER_SIZE]; - struct bt_control_rsp *rsp = (void *) buf; - long current; - int ret, keyvalue; - - DBG("ext %p key %ld", ext, key); - - ret = bluetooth_read_integer(ext, key, ¤t); - if (ret < 0) - return ret; - - if (*value == current) - return 0; - - while (*value != current) { - keyvalue = (*value > current) ? BT_CONTROL_KEY_VOL_UP : - BT_CONTROL_KEY_VOL_DOWN; - - ret = bluetooth_send_ctl(data, key, keyvalue, rsp); - if (ret < 0) - break; - - current = keyvalue; - } - - return ret; -} - -static int bluetooth_read_event(snd_ctl_ext_t *ext, snd_ctl_elem_id_t *id, - unsigned int *event_mask) -{ - struct bluetooth_data *data = ext->private_data; - char buf[BT_SUGGESTED_BUFFER_SIZE]; - struct bt_control_ind *ind = (void *) buf; - int err; - const char *type, *name; - - DBG("ext %p id %p", ext, id); - - memset(buf, 0, sizeof(buf)); - - err = recv(data->sock, ind, BT_SUGGESTED_BUFFER_SIZE, MSG_DONTWAIT); - if (err < 0) { - err = -errno; - SNDERR("Failed while receiving data: %s (%d)", strerror(-err), - -err); - return err; - } - - type = bt_audio_strtype(ind->h.type); - if (!type) { - SNDERR("Bogus message type %d " - "received from audio service", - ind->h.type); - return -EAGAIN; - } - - name = bt_audio_strname(ind->h.name); - if (!name) { - SNDERR("Bogus message name %d " - "received from audio service", - ind->h.name); - return -EAGAIN; - } - - if (ind->h.name != BT_CONTROL) { - SNDERR("Unexpected message %s received", name); - return -EAGAIN; - } - - snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER); - snd_ctl_elem_id_set_name(id, ind->mode == BLUETOOTH_PLAYBACK ? - vol_devices[BLUETOOTH_PLAYBACK] : - vol_devices[BLUETOOTH_CAPTURE]); - *event_mask = SND_CTL_EVENT_MASK_VALUE; - - return 1; -} - -static snd_ctl_ext_callback_t bluetooth_callback = { - .close = bluetooth_close, - .elem_count = bluetooth_elem_count, - .elem_list = bluetooth_elem_list, - .find_elem = bluetooth_find_elem, - .get_attribute = bluetooth_get_attribute, - .get_integer_info = bluetooth_get_integer_info, - .read_integer = bluetooth_read_integer, - .write_integer = bluetooth_write_integer, - .read_event = bluetooth_read_event, -}; - -static int bluetooth_init(struct bluetooth_data *data) -{ - int sk; - - if (!data) - return -EINVAL; - - memset(data, 0, sizeof(struct bluetooth_data)); - - data->sock = -1; - - sk = bt_audio_service_open(); - if (sk < 0) - return -errno; - - data->sock = sk; - - return 0; -} - -SND_CTL_PLUGIN_DEFINE_FUNC(bluetooth); - -SND_CTL_PLUGIN_DEFINE_FUNC(bluetooth) -{ - struct bluetooth_data *data; - int err; - - DBG("Bluetooth Control plugin"); - - data = malloc(sizeof(struct bluetooth_data)); - if (!data) { - err = -ENOMEM; - goto error; - } - - err = bluetooth_init(data); - if (err < 0) - goto error; - - data->ext.version = SND_CTL_EXT_VERSION; - data->ext.card_idx = -1; - - strncpy(data->ext.id, "bluetooth", sizeof(data->ext.id) - 1); - strncpy(data->ext.driver, "Bluetooth-Audio", sizeof(data->ext.driver) - 1); - strncpy(data->ext.name, "Bluetooth Audio", sizeof(data->ext.name) - 1); - strncpy(data->ext.longname, "Bluetooth Audio", sizeof(data->ext.longname) - 1); - strncpy(data->ext.mixername, "Bluetooth Audio", sizeof(data->ext.mixername) - 1); - - data->ext.callback = &bluetooth_callback; - data->ext.poll_fd = data->sock; - data->ext.private_data = data; - - err = snd_ctl_ext_create(&data->ext, name, mode); - if (err < 0) - goto error; - - *handlep = data->ext.handle; - - return 0; - -error: - bluetooth_exit(data); - - return err; -} - -SND_CTL_PLUGIN_SYMBOL(bluetooth); diff --git a/audio/pcm_bluetooth.c b/audio/pcm_bluetooth.c deleted file mode 100644 index b9da805..0000000 --- a/audio/pcm_bluetooth.c +++ /dev/null @@ -1,1785 +0,0 @@ -/* - * - * BlueZ - Bluetooth protocol stack for Linux - * - * Copyright (C) 2006-2010 Nokia Corporation - * Copyright (C) 2004-2010 Marcel Holtmann <marcel@xxxxxxxxxxxx> - * - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <stdint.h> -#include <sys/socket.h> -#include <sys/un.h> -#include <time.h> -#include <sys/time.h> -#include <pthread.h> -#include <signal.h> -#include <limits.h> - -#include <netinet/in.h> - -#include <alsa/asoundlib.h> -#include <alsa/pcm_external.h> - -#include "ipc.h" -#include "sbc.h" -#include "rtp.h" - -/* #define ENABLE_DEBUG */ - -#define UINT_SECS_MAX (UINT_MAX / 1000000 - 1) - -#define MIN_PERIOD_TIME 1 - -#define BUFFER_SIZE 2048 - -#ifdef ENABLE_DEBUG -#define DBG(fmt, arg...) printf("DEBUG: %s: " fmt "\n" , __FUNCTION__ , ## arg) -#else -#define DBG(fmt, arg...) -#endif - -#ifndef SOL_SCO -#define SOL_SCO 17 -#endif - -#ifndef SCO_TXBUFS -#define SCO_TXBUFS 0x03 -#endif - -#ifndef SCO_RXBUFS -#define SCO_RXBUFS 0x04 -#endif - -#ifndef MIN -# define MIN(x, y) ((x) < (y) ? (x) : (y)) -#endif - -#ifndef MAX -# define MAX(x, y) ((x) > (y) ? (x) : (y)) -#endif - -#define MAX_BITPOOL 64 -#define MIN_BITPOOL 2 - -/* adapted from glibc sys/time.h timersub() macro */ -#define priv_timespecsub(a, b, result) \ - do { \ - (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ - (result)->tv_nsec = (a)->tv_nsec - (b)->tv_nsec; \ - if ((result)->tv_nsec < 0) { \ - --(result)->tv_sec; \ - (result)->tv_nsec += 1000000000; \ - } \ - } while (0) - -struct bluetooth_a2dp { - sbc_capabilities_t sbc_capabilities; - sbc_t sbc; /* Codec data */ - int sbc_initialized; /* Keep track if the encoder is initialized */ - unsigned int codesize; /* SBC codesize */ - int samples; /* Number of encoded samples */ - uint8_t buffer[BUFFER_SIZE]; /* Codec transfer buffer */ - unsigned int count; /* Codec transfer buffer counter */ - - int nsamples; /* Cumulative number of codec samples */ - uint16_t seq_num; /* Cumulative packet sequence */ - int frame_count; /* Current frames in buffer*/ -}; - -struct bluetooth_alsa_config { - char device[18]; /* Address of the remote Device */ - int has_device; - uint8_t transport; /* Requested transport */ - int has_transport; - uint16_t rate; - int has_rate; - uint8_t channel_mode; /* A2DP only */ - int has_channel_mode; - uint8_t allocation_method; /* A2DP only */ - int has_allocation_method; - uint8_t subbands; /* A2DP only */ - int has_subbands; - uint8_t block_length; /* A2DP only */ - int has_block_length; - uint8_t bitpool; /* A2DP only */ - int has_bitpool; - int autoconnect; -}; - -struct bluetooth_data { - snd_pcm_ioplug_t io; - struct bluetooth_alsa_config alsa_config; /* ALSA resource file parameters */ - volatile snd_pcm_sframes_t hw_ptr; - int transport; /* chosen transport SCO or AD2P */ - unsigned int link_mtu; /* MTU for selected transport channel */ - volatile struct pollfd stream; /* Audio stream filedescriptor */ - struct pollfd server; /* Audio daemon filedescriptor */ - uint8_t buffer[BUFFER_SIZE]; /* Encoded transfer buffer */ - unsigned int count; /* Transfer buffer counter */ - struct bluetooth_a2dp a2dp; /* A2DP data */ - - pthread_t hw_thread; /* Makes virtual hw pointer move */ - int pipefd[2]; /* Inter thread communication */ - int stopped; - sig_atomic_t reset; /* Request XRUN handling */ -}; - -static int audioservice_send(int sk, const bt_audio_msg_header_t *msg); -static int audioservice_expect(int sk, bt_audio_msg_header_t *outmsg, - int expected_type); - -static int bluetooth_start(snd_pcm_ioplug_t *io) -{ - DBG("bluetooth_start %p", io); - - return 0; -} - -static int bluetooth_stop(snd_pcm_ioplug_t *io) -{ - DBG("bluetooth_stop %p", io); - - return 0; -} - -static void *playback_hw_thread(void *param) -{ - struct bluetooth_data *data = param; - unsigned int prev_periods; - double period_time; - struct timespec start; - struct pollfd fds[2]; - int poll_timeout; - - data->server.events = POLLIN; - /* note: only errors for data->stream.events */ - - fds[0] = data->server; - fds[1] = data->stream; - - prev_periods = 0; - period_time = 1000000.0 * data->io.period_size / data->io.rate; - if (period_time > (int) (MIN_PERIOD_TIME * 1000)) - poll_timeout = (int) (period_time / 1000.0f); - else - poll_timeout = MIN_PERIOD_TIME; - - clock_gettime(CLOCK_MONOTONIC, &start); - - while (1) { - unsigned int dtime, periods; - struct timespec cur, delta; - int ret; - - if (data->stopped) - goto iter_sleep; - - if (data->reset) { - DBG("Handle XRUN in hw-thread."); - data->reset = 0; - clock_gettime(CLOCK_MONOTONIC, &start); - prev_periods = 0; - } - - clock_gettime(CLOCK_MONOTONIC, &cur); - - priv_timespecsub(&cur, &start, &delta); - - dtime = delta.tv_sec * 1000000 + delta.tv_nsec / 1000; - periods = 1.0 * dtime / period_time; - - if (periods > prev_periods) { - char c = 'w'; - int frags = periods - prev_periods, n; - - data->hw_ptr += frags * data->io.period_size; - data->hw_ptr %= data->io.buffer_size; - - for (n = 0; n < frags; n++) { - /* Notify user that hardware pointer - * has moved * */ - if (write(data->pipefd[1], &c, 1) < 0) - pthread_testcancel(); - } - - /* Reset point of reference to avoid too big values - * that wont fit an unsigned int */ - if ((unsigned int) delta.tv_sec < UINT_SECS_MAX) - prev_periods = periods; - else { - prev_periods = 0; - clock_gettime(CLOCK_MONOTONIC, &start); - } - } - -iter_sleep: - /* sleep up to one period interval */ - ret = poll(fds, 2, poll_timeout); - - if (ret < 0) { - if (errno != EINTR) { - SNDERR("poll error: %s (%d)", strerror(errno), - errno); - break; - } - } else if (ret > 0) { - ret = (fds[0].revents) ? 0 : 1; - SNDERR("poll fd %d revents %d", ret, fds[ret].revents); - if (fds[ret].revents & (POLLERR | POLLHUP | POLLNVAL)) - break; - } - - /* Offer opportunity to be canceled by main thread */ - pthread_testcancel(); - } - - data->hw_thread = 0; - pthread_exit(NULL); -} - -static int bluetooth_playback_start(snd_pcm_ioplug_t *io) -{ - struct bluetooth_data *data = io->private_data; - int err; - - DBG("%p", io); - - data->stopped = 0; - - if (data->hw_thread) - return 0; - - err = pthread_create(&data->hw_thread, 0, playback_hw_thread, data); - - return -err; -} - -static int bluetooth_playback_stop(snd_pcm_ioplug_t *io) -{ - struct bluetooth_data *data = io->private_data; - - DBG("%p", io); - - data->stopped = 1; - - return 0; -} - -static snd_pcm_sframes_t bluetooth_pointer(snd_pcm_ioplug_t *io) -{ - struct bluetooth_data *data = io->private_data; - - return data->hw_ptr; -} - -static void bluetooth_exit(struct bluetooth_data *data) -{ - struct bluetooth_a2dp *a2dp = &data->a2dp; - - if (data->server.fd >= 0) - bt_audio_service_close(data->server.fd); - - if (data->stream.fd >= 0) - close(data->stream.fd); - - if (data->hw_thread) { - pthread_cancel(data->hw_thread); - pthread_join(data->hw_thread, 0); - } - - if (a2dp->sbc_initialized) - sbc_finish(&a2dp->sbc); - - if (data->pipefd[0] > 0) - close(data->pipefd[0]); - - if (data->pipefd[1] > 0) - close(data->pipefd[1]); - - free(data); -} - -static int bluetooth_close(snd_pcm_ioplug_t *io) -{ - struct bluetooth_data *data = io->private_data; - - DBG("%p", io); - - bluetooth_exit(data); - - return 0; -} - -static int bluetooth_prepare(snd_pcm_ioplug_t *io) -{ - struct bluetooth_data *data = io->private_data; - char c = 'w'; - char buf[BT_SUGGESTED_BUFFER_SIZE]; - struct bt_start_stream_req *req = (void *) buf; - struct bt_start_stream_rsp *rsp = (void *) buf; - struct bt_new_stream_ind *ind = (void *) buf; - uint32_t period_count = io->buffer_size / io->period_size; - int opt_name, err; - struct timeval t = { 0, period_count }; - - DBG("Preparing with io->period_size=%lu io->buffer_size=%lu", - io->period_size, io->buffer_size); - - data->reset = 0; - - /* As we're gonna receive messages on the server socket, we have to stop the - hw thread that is polling on it, if any */ - if (data->hw_thread) { - pthread_cancel(data->hw_thread); - pthread_join(data->hw_thread, 0); - data->hw_thread = 0; - } - - if (io->stream == SND_PCM_STREAM_PLAYBACK) - /* If not null for playback, xmms doesn't display time - * correctly */ - data->hw_ptr = 0; - else - /* ALSA library is really picky on the fact hw_ptr is not null. - * If it is, capture won't start */ - data->hw_ptr = io->period_size; - - /* send start */ - memset(req, 0, BT_SUGGESTED_BUFFER_SIZE); - req->h.type = BT_REQUEST; - req->h.name = BT_START_STREAM; - req->h.length = sizeof(*req); - - err = audioservice_send(data->server.fd, &req->h); - if (err < 0) - return err; - - rsp->h.length = sizeof(*rsp); - err = audioservice_expect(data->server.fd, &rsp->h, - BT_START_STREAM); - if (err < 0) - return err; - - ind->h.length = sizeof(*ind); - err = audioservice_expect(data->server.fd, &ind->h, - BT_NEW_STREAM); - if (err < 0) - return err; - - if (data->stream.fd >= 0) - close(data->stream.fd); - - data->stream.fd = bt_audio_service_get_data_fd(data->server.fd); - if (data->stream.fd < 0) { - return -errno; - } - - if (data->transport == BT_CAPABILITIES_TRANSPORT_A2DP) { - opt_name = (io->stream == SND_PCM_STREAM_PLAYBACK) ? - SO_SNDTIMEO : SO_RCVTIMEO; - - if (setsockopt(data->stream.fd, SOL_SOCKET, opt_name, &t, - sizeof(t)) < 0) - return -errno; - } else { - opt_name = (io->stream == SND_PCM_STREAM_PLAYBACK) ? - SCO_TXBUFS : SCO_RXBUFS; - - if (setsockopt(data->stream.fd, SOL_SCO, opt_name, &period_count, - sizeof(period_count)) == 0) - return 0; - - opt_name = (io->stream == SND_PCM_STREAM_PLAYBACK) ? - SO_SNDBUF : SO_RCVBUF; - - if (setsockopt(data->stream.fd, SOL_SCO, opt_name, &period_count, - sizeof(period_count)) == 0) - return 0; - - /* FIXME : handle error codes */ - } - - /* wake up any client polling at us */ - if (write(data->pipefd[1], &c, 1) < 0) { - err = -errno; - return err; - } - - return 0; -} - -static int bluetooth_hsp_hw_params(snd_pcm_ioplug_t *io, - snd_pcm_hw_params_t *params) -{ - struct bluetooth_data *data = io->private_data; - char buf[BT_SUGGESTED_BUFFER_SIZE]; - struct bt_open_req *open_req = (void *) buf; - struct bt_open_rsp *open_rsp = (void *) buf; - struct bt_set_configuration_req *req = (void *) buf; - struct bt_set_configuration_rsp *rsp = (void *) buf; - int err; - - DBG("Preparing with io->period_size=%lu io->buffer_size=%lu", - io->period_size, io->buffer_size); - - memset(req, 0, BT_SUGGESTED_BUFFER_SIZE); - open_req->h.type = BT_REQUEST; - open_req->h.name = BT_OPEN; - open_req->h.length = sizeof(*open_req); - - strncpy(open_req->destination, data->alsa_config.device, 18); - open_req->seid = BT_A2DP_SEID_RANGE + 1; - open_req->lock = (io->stream == SND_PCM_STREAM_PLAYBACK ? - BT_WRITE_LOCK : BT_READ_LOCK); - - err = audioservice_send(data->server.fd, &open_req->h); - if (err < 0) - return err; - - open_rsp->h.length = sizeof(*open_rsp); - err = audioservice_expect(data->server.fd, &open_rsp->h, - BT_OPEN); - if (err < 0) - return err; - - memset(req, 0, BT_SUGGESTED_BUFFER_SIZE); - req->h.type = BT_REQUEST; - req->h.name = BT_SET_CONFIGURATION; - req->h.length = sizeof(*req); - - req->codec.transport = BT_CAPABILITIES_TRANSPORT_SCO; - req->codec.seid = BT_A2DP_SEID_RANGE + 1; - req->codec.length = sizeof(pcm_capabilities_t); - - req->h.length += req->codec.length - sizeof(req->codec); - err = audioservice_send(data->server.fd, &req->h); - if (err < 0) - return err; - - rsp->h.length = sizeof(*rsp); - err = audioservice_expect(data->server.fd, &rsp->h, - BT_SET_CONFIGURATION); - if (err < 0) - return err; - - data->transport = BT_CAPABILITIES_TRANSPORT_SCO; - data->link_mtu = rsp->link_mtu; - - return 0; -} - -static uint8_t default_bitpool(uint8_t freq, uint8_t mode) -{ - switch (freq) { - case BT_SBC_SAMPLING_FREQ_16000: - case BT_SBC_SAMPLING_FREQ_32000: - return 53; - case BT_SBC_SAMPLING_FREQ_44100: - switch (mode) { - case BT_A2DP_CHANNEL_MODE_MONO: - case BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL: - return 31; - case BT_A2DP_CHANNEL_MODE_STEREO: - case BT_A2DP_CHANNEL_MODE_JOINT_STEREO: - return 53; - default: - DBG("Invalid channel mode %u", mode); - return 53; - } - case BT_SBC_SAMPLING_FREQ_48000: - switch (mode) { - case BT_A2DP_CHANNEL_MODE_MONO: - case BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL: - return 29; - case BT_A2DP_CHANNEL_MODE_STEREO: - case BT_A2DP_CHANNEL_MODE_JOINT_STEREO: - return 51; - default: - DBG("Invalid channel mode %u", mode); - return 51; - } - default: - DBG("Invalid sampling freq %u", freq); - return 53; - } -} - -static int bluetooth_a2dp_init(struct bluetooth_data *data, - snd_pcm_hw_params_t *params) -{ - struct bluetooth_alsa_config *cfg = &data->alsa_config; - sbc_capabilities_t *cap = &data->a2dp.sbc_capabilities; - unsigned int max_bitpool, min_bitpool, rate, channels; - int dir; - - snd_pcm_hw_params_get_rate(params, &rate, &dir); - snd_pcm_hw_params_get_channels(params, &channels); - - switch (rate) { - case 48000: - cap->frequency = BT_SBC_SAMPLING_FREQ_48000; - break; - case 44100: - cap->frequency = BT_SBC_SAMPLING_FREQ_44100; - break; - case 32000: - cap->frequency = BT_SBC_SAMPLING_FREQ_32000; - break; - case 16000: - cap->frequency = BT_SBC_SAMPLING_FREQ_16000; - break; - default: - DBG("Rate %d not supported", rate); - return -1; - } - - if (cfg->has_channel_mode) - cap->channel_mode = cfg->channel_mode; - else if (channels == 2) { - if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO) - cap->channel_mode = BT_A2DP_CHANNEL_MODE_JOINT_STEREO; - else if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) - cap->channel_mode = BT_A2DP_CHANNEL_MODE_STEREO; - else if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) - cap->channel_mode = BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL; - } else { - if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) - cap->channel_mode = BT_A2DP_CHANNEL_MODE_MONO; - } - - if (!cap->channel_mode) { - DBG("No supported channel modes"); - return -1; - } - - if (cfg->has_block_length) - cap->block_length = cfg->block_length; - else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_16) - cap->block_length = BT_A2DP_BLOCK_LENGTH_16; - else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_12) - cap->block_length = BT_A2DP_BLOCK_LENGTH_12; - else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_8) - cap->block_length = BT_A2DP_BLOCK_LENGTH_8; - else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_4) - cap->block_length = BT_A2DP_BLOCK_LENGTH_4; - else { - DBG("No supported block lengths"); - return -1; - } - - if (cfg->has_subbands) - cap->subbands = cfg->subbands; - if (cap->subbands & BT_A2DP_SUBBANDS_8) - cap->subbands = BT_A2DP_SUBBANDS_8; - else if (cap->subbands & BT_A2DP_SUBBANDS_4) - cap->subbands = BT_A2DP_SUBBANDS_4; - else { - DBG("No supported subbands"); - return -1; - } - - if (cfg->has_allocation_method) - cap->allocation_method = cfg->allocation_method; - if (cap->allocation_method & BT_A2DP_ALLOCATION_LOUDNESS) - cap->allocation_method = BT_A2DP_ALLOCATION_LOUDNESS; - else if (cap->allocation_method & BT_A2DP_ALLOCATION_SNR) - cap->allocation_method = BT_A2DP_ALLOCATION_SNR; - - if (cfg->has_bitpool) - min_bitpool = max_bitpool = cfg->bitpool; - else { - min_bitpool = MAX(MIN_BITPOOL, cap->min_bitpool); - max_bitpool = MIN(default_bitpool(cap->frequency, - cap->channel_mode), - cap->max_bitpool); - } - - cap->min_bitpool = min_bitpool; - cap->max_bitpool = max_bitpool; - - return 0; -} - -static void bluetooth_a2dp_setup(struct bluetooth_a2dp *a2dp) -{ - sbc_capabilities_t active_capabilities = a2dp->sbc_capabilities; - - if (a2dp->sbc_initialized) - sbc_reinit(&a2dp->sbc, 0); - else - sbc_init(&a2dp->sbc, 0); - a2dp->sbc_initialized = 1; - - if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_16000) - a2dp->sbc.frequency = SBC_FREQ_16000; - - if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_32000) - a2dp->sbc.frequency = SBC_FREQ_32000; - - if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_44100) - a2dp->sbc.frequency = SBC_FREQ_44100; - - if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_48000) - a2dp->sbc.frequency = SBC_FREQ_48000; - - if (active_capabilities.channel_mode & BT_A2DP_CHANNEL_MODE_MONO) - a2dp->sbc.mode = SBC_MODE_MONO; - - if (active_capabilities.channel_mode & BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) - a2dp->sbc.mode = SBC_MODE_DUAL_CHANNEL; - - if (active_capabilities.channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) - a2dp->sbc.mode = SBC_MODE_STEREO; - - if (active_capabilities.channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO) - a2dp->sbc.mode = SBC_MODE_JOINT_STEREO; - - a2dp->sbc.allocation = active_capabilities.allocation_method - == BT_A2DP_ALLOCATION_SNR ? SBC_AM_SNR - : SBC_AM_LOUDNESS; - - switch (active_capabilities.subbands) { - case BT_A2DP_SUBBANDS_4: - a2dp->sbc.subbands = SBC_SB_4; - break; - case BT_A2DP_SUBBANDS_8: - a2dp->sbc.subbands = SBC_SB_8; - break; - } - - switch (active_capabilities.block_length) { - case BT_A2DP_BLOCK_LENGTH_4: - a2dp->sbc.blocks = SBC_BLK_4; - break; - case BT_A2DP_BLOCK_LENGTH_8: - a2dp->sbc.blocks = SBC_BLK_8; - break; - case BT_A2DP_BLOCK_LENGTH_12: - a2dp->sbc.blocks = SBC_BLK_12; - break; - case BT_A2DP_BLOCK_LENGTH_16: - a2dp->sbc.blocks = SBC_BLK_16; - break; - } - - a2dp->sbc.bitpool = active_capabilities.max_bitpool; - a2dp->codesize = sbc_get_codesize(&a2dp->sbc); - a2dp->count = sizeof(struct rtp_header) + sizeof(struct rtp_payload); -} - -static int bluetooth_a2dp_hw_params(snd_pcm_ioplug_t *io, - snd_pcm_hw_params_t *params) -{ - struct bluetooth_data *data = io->private_data; - struct bluetooth_a2dp *a2dp = &data->a2dp; - char buf[BT_SUGGESTED_BUFFER_SIZE]; - struct bt_open_req *open_req = (void *) buf; - struct bt_open_rsp *open_rsp = (void *) buf; - struct bt_set_configuration_req *req = (void *) buf; - struct bt_set_configuration_rsp *rsp = (void *) buf; - int err; - - DBG("Preparing with io->period_size=%lu io->buffer_size=%lu", - io->period_size, io->buffer_size); - - memset(req, 0, BT_SUGGESTED_BUFFER_SIZE); - open_req->h.type = BT_REQUEST; - open_req->h.name = BT_OPEN; - open_req->h.length = sizeof(*open_req); - - strncpy(open_req->destination, data->alsa_config.device, 18); - open_req->seid = a2dp->sbc_capabilities.capability.seid; - open_req->lock = (io->stream == SND_PCM_STREAM_PLAYBACK ? - BT_WRITE_LOCK : BT_READ_LOCK); - - err = audioservice_send(data->server.fd, &open_req->h); - if (err < 0) - return err; - - open_rsp->h.length = sizeof(*open_rsp); - err = audioservice_expect(data->server.fd, &open_rsp->h, - BT_OPEN); - if (err < 0) - return err; - - err = bluetooth_a2dp_init(data, params); - if (err < 0) - return err; - - memset(req, 0, BT_SUGGESTED_BUFFER_SIZE); - req->h.type = BT_REQUEST; - req->h.name = BT_SET_CONFIGURATION; - req->h.length = sizeof(*req); - - memcpy(&req->codec, &a2dp->sbc_capabilities, - sizeof(a2dp->sbc_capabilities)); - - req->codec.transport = BT_CAPABILITIES_TRANSPORT_A2DP; - req->codec.length = sizeof(a2dp->sbc_capabilities); - req->h.length += req->codec.length - sizeof(req->codec); - - err = audioservice_send(data->server.fd, &req->h); - if (err < 0) - return err; - - rsp->h.length = sizeof(*rsp); - err = audioservice_expect(data->server.fd, &rsp->h, - BT_SET_CONFIGURATION); - if (err < 0) - return err; - - data->transport = BT_CAPABILITIES_TRANSPORT_A2DP; - data->link_mtu = rsp->link_mtu; - - /* Setup SBC encoder now we agree on parameters */ - bluetooth_a2dp_setup(a2dp); - - DBG("\tallocation=%u\n\tsubbands=%u\n\tblocks=%u\n\tbitpool=%u\n", - a2dp->sbc.allocation, a2dp->sbc.subbands, a2dp->sbc.blocks, - a2dp->sbc.bitpool); - - return 0; -} - -static int bluetooth_poll_descriptors(snd_pcm_ioplug_t *io, - struct pollfd *pfd, unsigned int space) -{ - struct bluetooth_data *data = io->private_data; - - assert(io); - - if (space < 1) - return 0; - - pfd[0].fd = data->stream.fd; - pfd[0].events = POLLIN; - pfd[0].revents = 0; - - return 1; -} - -static int bluetooth_poll_revents(snd_pcm_ioplug_t *io ATTRIBUTE_UNUSED, - struct pollfd *pfds, unsigned int nfds, - unsigned short *revents) -{ - assert(pfds && nfds == 1 && revents); - - *revents = pfds[0].revents; - - return 0; -} - -static int bluetooth_playback_poll_descriptors_count(snd_pcm_ioplug_t *io) -{ - return 2; -} - -static int bluetooth_playback_poll_descriptors(snd_pcm_ioplug_t *io, - struct pollfd *pfd, unsigned int space) -{ - struct bluetooth_data *data = io->private_data; - - DBG(""); - - assert(data->pipefd[0] >= 0); - - if (space < 2) - return 0; - - pfd[0].fd = data->pipefd[0]; - pfd[0].events = POLLIN; - pfd[0].revents = 0; - pfd[1].fd = data->stream.fd; - pfd[1].events = POLLERR | POLLHUP | POLLNVAL; - pfd[1].revents = 0; - - return 2; -} - -static int bluetooth_playback_poll_revents(snd_pcm_ioplug_t *io, - struct pollfd *pfds, unsigned int nfds, - unsigned short *revents) -{ - static char buf[1]; - - DBG(""); - - assert(pfds); - assert(nfds == 2); - assert(revents); - assert(pfds[0].fd >= 0); - assert(pfds[1].fd >= 0); - - if (io->state != SND_PCM_STATE_PREPARED) - if (read(pfds[0].fd, buf, 1) < 0) - SYSERR("read error: %s (%d)", strerror(errno), errno); - - if (pfds[1].revents & (POLLERR | POLLHUP | POLLNVAL)) - io->state = SND_PCM_STATE_DISCONNECTED; - - *revents = (pfds[0].revents & POLLIN) ? POLLOUT : 0; - - return 0; -} - - -static snd_pcm_sframes_t bluetooth_hsp_read(snd_pcm_ioplug_t *io, - const snd_pcm_channel_area_t *areas, - snd_pcm_uframes_t offset, - snd_pcm_uframes_t size) -{ - struct bluetooth_data *data = io->private_data; - snd_pcm_uframes_t frames_to_write, ret; - unsigned char *buff; - unsigned int frame_size = 0; - int nrecv; - - DBG("areas->step=%u areas->first=%u offset=%lu size=%lu io->nonblock=%u", - areas->step, areas->first, offset, size, io->nonblock); - - frame_size = areas->step / 8; - - if (data->count > 0) - goto proceed; - - nrecv = recv(data->stream.fd, data->buffer, data->link_mtu, - io->nonblock ? MSG_DONTWAIT : 0); - - if (nrecv < 0) { - ret = (errno == EPIPE) ? -EIO : -errno; - goto done; - } - - if ((unsigned int) nrecv != data->link_mtu) { - ret = -EIO; - SNDERR(strerror(-ret)); - goto done; - } - - /* Increment hardware transmition pointer */ - data->hw_ptr = (data->hw_ptr + data->link_mtu / frame_size) % - io->buffer_size; - -proceed: - buff = (unsigned char *) areas->addr + - (areas->first + areas->step * offset) / 8; - - if ((data->count + size * frame_size) <= data->link_mtu) - frames_to_write = size; - else - frames_to_write = (data->link_mtu - data->count) / frame_size; - - memcpy(buff, data->buffer + data->count, frame_size * frames_to_write); - data->count += (frame_size * frames_to_write); - data->count %= data->link_mtu; - - /* Return written frames count */ - ret = frames_to_write; - -done: - DBG("returning %lu", ret); - return ret; -} - -static snd_pcm_sframes_t bluetooth_hsp_write(snd_pcm_ioplug_t *io, - const snd_pcm_channel_area_t *areas, - snd_pcm_uframes_t offset, - snd_pcm_uframes_t size) -{ - struct bluetooth_data *data = io->private_data; - snd_pcm_sframes_t ret = 0; - snd_pcm_uframes_t frames_to_read; - uint8_t *buff; - int rsend, frame_size; - - DBG("areas->step=%u areas->first=%u offset=%lu, size=%lu io->nonblock=%u", - areas->step, areas->first, offset, size, io->nonblock); - - if (io->hw_ptr > io->appl_ptr) { - ret = bluetooth_playback_stop(io); - if (ret == 0) - ret = -EPIPE; - goto done; - } - - frame_size = areas->step / 8; - if ((data->count + size * frame_size) <= data->link_mtu) - frames_to_read = size; - else - frames_to_read = (data->link_mtu - data->count) / frame_size; - - DBG("count=%d frames_to_read=%lu", data->count, frames_to_read); - - /* Ready for more data */ - buff = (uint8_t *) areas->addr + - (areas->first + areas->step * offset) / 8; - memcpy(data->buffer + data->count, buff, frame_size * frames_to_read); - - /* Remember we have some frames in the pipe now */ - data->count += frames_to_read * frame_size; - if (data->count != data->link_mtu) { - ret = frames_to_read; - goto done; - } - - rsend = send(data->stream.fd, data->buffer, data->link_mtu, - io->nonblock ? MSG_DONTWAIT : 0); - if (rsend > 0) { - /* Reset count pointer */ - data->count = 0; - - ret = frames_to_read; - } else if (rsend < 0) - ret = (errno == EPIPE) ? -EIO : -errno; - else - ret = -EIO; - -done: - DBG("returning %ld", ret); - return ret; -} - -static snd_pcm_sframes_t bluetooth_a2dp_read(snd_pcm_ioplug_t *io, - const snd_pcm_channel_area_t *areas, - snd_pcm_uframes_t offset, snd_pcm_uframes_t size) -{ - snd_pcm_uframes_t ret = 0; - return ret; -} - -static int avdtp_write(struct bluetooth_data *data) -{ - int err; - struct rtp_header *header; - struct rtp_payload *payload; - struct bluetooth_a2dp *a2dp = &data->a2dp; - - header = (void *) a2dp->buffer; - payload = (void *) (a2dp->buffer + sizeof(*header)); - - memset(a2dp->buffer, 0, sizeof(*header) + sizeof(*payload)); - - payload->frame_count = a2dp->frame_count; - header->v = 2; - header->pt = 1; - header->sequence_number = htons(a2dp->seq_num); - header->timestamp = htonl(a2dp->nsamples); - header->ssrc = htonl(1); - - err = send(data->stream.fd, a2dp->buffer, a2dp->count, MSG_DONTWAIT); - if (err < 0) { - err = -errno; - DBG("send failed: %s (%d)", strerror(-err), -err); - } - - /* Reset buffer of data to send */ - a2dp->count = sizeof(struct rtp_header) + sizeof(struct rtp_payload); - a2dp->frame_count = 0; - a2dp->samples = 0; - a2dp->seq_num++; - - return err; -} - -static snd_pcm_sframes_t bluetooth_a2dp_write(snd_pcm_ioplug_t *io, - const snd_pcm_channel_area_t *areas, - snd_pcm_uframes_t offset, snd_pcm_uframes_t size) -{ - struct bluetooth_data *data = io->private_data; - struct bluetooth_a2dp *a2dp = &data->a2dp; - snd_pcm_sframes_t ret = 0; - unsigned int bytes_left; - int frame_size, encoded; - ssize_t written; - uint8_t *buff; - - DBG("areas->step=%u areas->first=%u offset=%lu size=%lu", - areas->step, areas->first, offset, size); - DBG("hw_ptr=%lu appl_ptr=%lu diff=%lu", io->hw_ptr, io->appl_ptr, - io->appl_ptr - io->hw_ptr); - - /* Calutate starting pointers */ - frame_size = areas->step / 8; - bytes_left = size * frame_size; - buff = (uint8_t *) areas->addr + - (areas->first + areas->step * (offset)) / 8; - - /* Check for underrun */ - if (io->hw_ptr > io->appl_ptr) { - ret = bluetooth_playback_stop(io); - if (ret == 0) - ret = -EPIPE; - data->reset = 1; - return ret; - } - - /* Check if we should autostart */ - if (io->state == SND_PCM_STATE_PREPARED) { - snd_pcm_sw_params_t *swparams; - snd_pcm_uframes_t threshold; - - snd_pcm_sw_params_malloc(&swparams); - if (!snd_pcm_sw_params_current(io->pcm, swparams) && - !snd_pcm_sw_params_get_start_threshold(swparams, - &threshold)) { - if (io->appl_ptr >= threshold) { - ret = snd_pcm_start(io->pcm); - if (ret != 0) - return ret; - } - } - - snd_pcm_sw_params_free(swparams); - } - - /* Check if we have any left over data from the last write */ - if (data->count > 0) { - unsigned int additional_bytes_needed = - a2dp->codesize - data->count; - if (additional_bytes_needed > bytes_left) - goto out; - - memcpy(data->buffer + data->count, buff, - additional_bytes_needed); - - /* Enough data to encode (sbc wants 1k blocks) */ - encoded = sbc_encode(&a2dp->sbc, data->buffer, a2dp->codesize, - a2dp->buffer + a2dp->count, - sizeof(a2dp->buffer) - a2dp->count, - &written); - if (encoded <= 0) { - DBG("Encoding error %d", encoded); - goto done; - } - - /* Increment a2dp buffers */ - a2dp->count += written; - a2dp->frame_count++; - a2dp->samples += encoded / frame_size; - a2dp->nsamples += encoded / frame_size; - - /* No space left for another frame then send */ - if (a2dp->count + written >= data->link_mtu) { - avdtp_write(data); - DBG("sending packet %d, count %d, link_mtu %u", - a2dp->seq_num, a2dp->count, - data->link_mtu); - } - - /* Increment up buff pointer to take into account - * the data processed */ - buff += additional_bytes_needed; - bytes_left -= additional_bytes_needed; - - /* Since data has been process mark it as zero */ - data->count = 0; - } - - - /* Process this buffer in full chunks */ - while (bytes_left >= a2dp->codesize) { - /* Enough data to encode (sbc wants 1k blocks) */ - encoded = sbc_encode(&a2dp->sbc, buff, a2dp->codesize, - a2dp->buffer + a2dp->count, - sizeof(a2dp->buffer) - a2dp->count, - &written); - if (encoded <= 0) { - DBG("Encoding error %d", encoded); - goto done; - } - - /* Increment up buff pointer to take into account - * the data processed */ - buff += a2dp->codesize; - bytes_left -= a2dp->codesize; - - /* Increment a2dp buffers */ - a2dp->count += written; - a2dp->frame_count++; - a2dp->samples += encoded / frame_size; - a2dp->nsamples += encoded / frame_size; - - /* No space left for another frame then send */ - if (a2dp->count + written >= data->link_mtu) { - avdtp_write(data); - DBG("sending packet %d, count %d, link_mtu %u", - a2dp->seq_num, a2dp->count, - data->link_mtu); - } - } - -out: - /* Copy the extra to our temp buffer for the next write */ - if (bytes_left > 0) { - memcpy(data->buffer + data->count, buff, bytes_left); - data->count += bytes_left; - bytes_left = 0; - } - -done: - DBG("returning %ld", size - bytes_left / frame_size); - - return size - bytes_left / frame_size; -} - -static int bluetooth_playback_delay(snd_pcm_ioplug_t *io, - snd_pcm_sframes_t *delayp) -{ - DBG(""); - - /* This updates io->hw_ptr value using pointer() function */ - snd_pcm_hwsync(io->pcm); - - *delayp = io->appl_ptr - io->hw_ptr; - if ((io->state == SND_PCM_STATE_RUNNING) && (*delayp < 0)) { - io->callback->stop(io); - io->state = SND_PCM_STATE_XRUN; - *delayp = 0; - } - - /* This should never fail, ALSA API is really not - prepared to handle a non zero return value */ - return 0; -} - -static snd_pcm_ioplug_callback_t bluetooth_hsp_playback = { - .start = bluetooth_playback_start, - .stop = bluetooth_playback_stop, - .pointer = bluetooth_pointer, - .close = bluetooth_close, - .hw_params = bluetooth_hsp_hw_params, - .prepare = bluetooth_prepare, - .transfer = bluetooth_hsp_write, - .poll_descriptors_count = bluetooth_playback_poll_descriptors_count, - .poll_descriptors = bluetooth_playback_poll_descriptors, - .poll_revents = bluetooth_playback_poll_revents, - .delay = bluetooth_playback_delay, -}; - -static snd_pcm_ioplug_callback_t bluetooth_hsp_capture = { - .start = bluetooth_start, - .stop = bluetooth_stop, - .pointer = bluetooth_pointer, - .close = bluetooth_close, - .hw_params = bluetooth_hsp_hw_params, - .prepare = bluetooth_prepare, - .transfer = bluetooth_hsp_read, - .poll_descriptors = bluetooth_poll_descriptors, - .poll_revents = bluetooth_poll_revents, -}; - -static snd_pcm_ioplug_callback_t bluetooth_a2dp_playback = { - .start = bluetooth_playback_start, - .stop = bluetooth_playback_stop, - .pointer = bluetooth_pointer, - .close = bluetooth_close, - .hw_params = bluetooth_a2dp_hw_params, - .prepare = bluetooth_prepare, - .transfer = bluetooth_a2dp_write, - .poll_descriptors_count = bluetooth_playback_poll_descriptors_count, - .poll_descriptors = bluetooth_playback_poll_descriptors, - .poll_revents = bluetooth_playback_poll_revents, - .delay = bluetooth_playback_delay, -}; - -static snd_pcm_ioplug_callback_t bluetooth_a2dp_capture = { - .start = bluetooth_start, - .stop = bluetooth_stop, - .pointer = bluetooth_pointer, - .close = bluetooth_close, - .hw_params = bluetooth_a2dp_hw_params, - .prepare = bluetooth_prepare, - .transfer = bluetooth_a2dp_read, - .poll_descriptors = bluetooth_poll_descriptors, - .poll_revents = bluetooth_poll_revents, -}; - -#define ARRAY_NELEMS(a) (sizeof((a)) / sizeof((a)[0])) - -static int bluetooth_hsp_hw_constraint(snd_pcm_ioplug_t *io) -{ - struct bluetooth_data *data = io->private_data; - snd_pcm_access_t access_list[] = { - SND_PCM_ACCESS_RW_INTERLEAVED, - /* Mmap access is really useless fo this driver, but we - * support it because some pieces of software out there - * insist on using it */ - SND_PCM_ACCESS_MMAP_INTERLEAVED - }; - unsigned int format_list[] = { - SND_PCM_FORMAT_S16 - }; - int err; - - /* access type */ - err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_ACCESS, - ARRAY_NELEMS(access_list), access_list); - if (err < 0) - return err; - - /* supported formats */ - err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_FORMAT, - ARRAY_NELEMS(format_list), format_list); - if (err < 0) - return err; - - /* supported channels */ - err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_CHANNELS, - 1, 1); - if (err < 0) - return err; - - /* supported rate */ - err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_RATE, - 8000, 8000); - if (err < 0) - return err; - - /* supported block size */ - err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIOD_BYTES, - data->link_mtu, data->link_mtu); - if (err < 0) - return err; - - err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIODS, - 2, 200); - if (err < 0) - return err; - - return 0; -} - -static int bluetooth_a2dp_hw_constraint(snd_pcm_ioplug_t *io) -{ - struct bluetooth_data *data = io->private_data; - struct bluetooth_a2dp *a2dp = &data->a2dp; - struct bluetooth_alsa_config *cfg = &data->alsa_config; - snd_pcm_access_t access_list[] = { - SND_PCM_ACCESS_RW_INTERLEAVED, - /* Mmap access is really useless fo this driver, but we - * support it because some pieces of software out there - * insist on using it */ - SND_PCM_ACCESS_MMAP_INTERLEAVED - }; - unsigned int format_list[] = { - SND_PCM_FORMAT_S16 - }; - unsigned int rate_list[4]; - unsigned int rate_count; - int err, min_channels, max_channels; - unsigned int period_list[] = { - 2048, - 4096, /* e.g. 23.2msec/period (stereo 16bit at 44.1kHz) */ - 8192 - }; - - /* access type */ - err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_ACCESS, - ARRAY_NELEMS(access_list), access_list); - if (err < 0) - return err; - - /* supported formats */ - err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_FORMAT, - ARRAY_NELEMS(format_list), format_list); - if (err < 0) - return err; - - /* supported channels */ - if (cfg->has_channel_mode) - a2dp->sbc_capabilities.channel_mode = cfg->channel_mode; - - if (a2dp->sbc_capabilities.channel_mode & - BT_A2DP_CHANNEL_MODE_MONO) - min_channels = 1; - else - min_channels = 2; - - if (a2dp->sbc_capabilities.channel_mode & - (~BT_A2DP_CHANNEL_MODE_MONO)) - max_channels = 2; - else - max_channels = 1; - - err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_CHANNELS, - min_channels, max_channels); - if (err < 0) - return err; - - /* supported buffer sizes - * (can be used as 3*8192, 6*4096, 12*2048, ...) */ - err = snd_pcm_ioplug_set_param_minmax(io, - SND_PCM_IOPLUG_HW_BUFFER_BYTES, - 8192*3, 8192*3); - if (err < 0) - return err; - - /* supported block sizes: */ - err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_PERIOD_BYTES, - ARRAY_NELEMS(period_list), period_list); - if (err < 0) - return err; - - /* supported rates */ - rate_count = 0; - if (cfg->has_rate) { - rate_list[rate_count] = cfg->rate; - rate_count++; - } else { - if (a2dp->sbc_capabilities.frequency & - BT_SBC_SAMPLING_FREQ_16000) { - rate_list[rate_count] = 16000; - rate_count++; - } - - if (a2dp->sbc_capabilities.frequency & - BT_SBC_SAMPLING_FREQ_32000) { - rate_list[rate_count] = 32000; - rate_count++; - } - - if (a2dp->sbc_capabilities.frequency & - BT_SBC_SAMPLING_FREQ_44100) { - rate_list[rate_count] = 44100; - rate_count++; - } - - if (a2dp->sbc_capabilities.frequency & - BT_SBC_SAMPLING_FREQ_48000) { - rate_list[rate_count] = 48000; - rate_count++; - } - } - - err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_RATE, - rate_count, rate_list); - if (err < 0) - return err; - - return 0; -} - -static int bluetooth_parse_config(snd_config_t *conf, - struct bluetooth_alsa_config *bt_config) -{ - snd_config_iterator_t i, next; - - memset(bt_config, 0, sizeof(struct bluetooth_alsa_config)); - - /* Set defaults */ - bt_config->autoconnect = 1; - - snd_config_for_each(i, next, conf) { - snd_config_t *n = snd_config_iterator_entry(i); - const char *id, *value; - - if (snd_config_get_id(n, &id) < 0) - continue; - - if (strcmp(id, "comment") == 0 || strcmp(id, "type") == 0) - continue; - - if (strcmp(id, "autoconnect") == 0) { - int b; - - b = snd_config_get_bool(n); - if (b < 0) { - SNDERR("Invalid type for %s", id); - return -EINVAL; - } - - bt_config->autoconnect = b; - continue; - } - - if (strcmp(id, "device") == 0 || strcmp(id, "bdaddr") == 0) { - if (snd_config_get_string(n, &value) < 0) { - SNDERR("Invalid type for %s", id); - return -EINVAL; - } - - bt_config->has_device = 1; - strncpy(bt_config->device, value, 18); - continue; - } - - if (strcmp(id, "profile") == 0) { - if (snd_config_get_string(n, &value) < 0) { - SNDERR("Invalid type for %s", id); - return -EINVAL; - } - - if (strcmp(value, "auto") == 0) { - bt_config->transport = BT_CAPABILITIES_TRANSPORT_ANY; - bt_config->has_transport = 1; - } else if (strcmp(value, "voice") == 0 || - strcmp(value, "hfp") == 0) { - bt_config->transport = BT_CAPABILITIES_TRANSPORT_SCO; - bt_config->has_transport = 1; - } else if (strcmp(value, "hifi") == 0 || - strcmp(value, "a2dp") == 0) { - bt_config->transport = BT_CAPABILITIES_TRANSPORT_A2DP; - bt_config->has_transport = 1; - } - continue; - } - - if (strcmp(id, "rate") == 0) { - if (snd_config_get_string(n, &value) < 0) { - SNDERR("Invalid type for %s", id); - return -EINVAL; - } - - bt_config->rate = atoi(value); - bt_config->has_rate = 1; - continue; - } - - if (strcmp(id, "mode") == 0) { - if (snd_config_get_string(n, &value) < 0) { - SNDERR("Invalid type for %s", id); - return -EINVAL; - } - - if (strcmp(value, "mono") == 0) { - bt_config->channel_mode = BT_A2DP_CHANNEL_MODE_MONO; - bt_config->has_channel_mode = 1; - } else if (strcmp(value, "dual") == 0) { - bt_config->channel_mode = BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL; - bt_config->has_channel_mode = 1; - } else if (strcmp(value, "stereo") == 0) { - bt_config->channel_mode = BT_A2DP_CHANNEL_MODE_STEREO; - bt_config->has_channel_mode = 1; - } else if (strcmp(value, "joint") == 0) { - bt_config->channel_mode = BT_A2DP_CHANNEL_MODE_JOINT_STEREO; - bt_config->has_channel_mode = 1; - } - continue; - } - - if (strcmp(id, "allocation") == 0) { - if (snd_config_get_string(n, &value) < 0) { - SNDERR("Invalid type for %s", id); - return -EINVAL; - } - - if (strcmp(value, "loudness") == 0) { - bt_config->allocation_method = BT_A2DP_ALLOCATION_LOUDNESS; - bt_config->has_allocation_method = 1; - } else if (strcmp(value, "snr") == 0) { - bt_config->allocation_method = BT_A2DP_ALLOCATION_SNR; - bt_config->has_allocation_method = 1; - } - continue; - } - - if (strcmp(id, "subbands") == 0) { - if (snd_config_get_string(n, &value) < 0) { - SNDERR("Invalid type for %s", id); - return -EINVAL; - } - - bt_config->subbands = atoi(value); - bt_config->has_subbands = 1; - continue; - } - - if (strcmp(id, "blocks") == 0) { - if (snd_config_get_string(n, &value) < 0) { - SNDERR("Invalid type for %s", id); - return -EINVAL; - } - - bt_config->block_length = atoi(value); - bt_config->has_block_length = 1; - continue; - } - - if (strcmp(id, "bitpool") == 0) { - if (snd_config_get_string(n, &value) < 0) { - SNDERR("Invalid type for %s", id); - return -EINVAL; - } - - bt_config->bitpool = atoi(value); - bt_config->has_bitpool = 1; - continue; - } - - SNDERR("Unknown field %s", id); - return -EINVAL; - } - - return 0; -} - -static int audioservice_send(int sk, const bt_audio_msg_header_t *msg) -{ - int err; - uint16_t length; - - length = msg->length ? msg->length : BT_SUGGESTED_BUFFER_SIZE; - - DBG("sending %s:%s", bt_audio_strtype(msg->type), - bt_audio_strname(msg->name)); - if (send(sk, msg, length, 0) > 0) - err = 0; - else { - err = -errno; - SNDERR("Error sending data to audio service: %s(%d)", - strerror(-err), -err); - } - - return err; -} - -static int audioservice_recv(int sk, bt_audio_msg_header_t *inmsg) -{ - int err; - ssize_t ret; - const char *type, *name; - uint16_t length; - - length = inmsg->length ? inmsg->length : BT_SUGGESTED_BUFFER_SIZE; - - DBG("trying to receive msg from audio service..."); - - ret = recv(sk, inmsg, length, 0); - if (ret < 0) { - err = -errno; - SNDERR("Error receiving IPC data from bluetoothd: %s (%d)", - strerror(-err), -err); - } else if ((size_t) ret < sizeof(bt_audio_msg_header_t)) { - SNDERR("Too short (%d bytes) IPC packet from bluetoothd", ret); - err = -EINVAL; - } else { - type = bt_audio_strtype(inmsg->type); - name = bt_audio_strname(inmsg->name); - if (type && name) { - DBG("Received %s - %s", type, name); - err = 0; - } else { - err = -EINVAL; - SNDERR("Bogus message type %d - name %d" - " received from audio service", - inmsg->type, inmsg->name); - } - - } - - return err; -} - -static int audioservice_expect(int sk, bt_audio_msg_header_t *rsp, - int expected_name) -{ - bt_audio_error_t *error; - int err = audioservice_recv(sk, rsp); - - if (err != 0) - return err; - - if (rsp->name != expected_name) { - err = -EINVAL; - SNDERR("Bogus message %s received while %s was expected", - bt_audio_strname(rsp->name), - bt_audio_strname(expected_name)); - } - - if (rsp->type == BT_ERROR) { - error = (void *) rsp; - SNDERR("%s failed : %s(%d)", - bt_audio_strname(rsp->name), - strerror(error->posix_errno), - error->posix_errno); - return -error->posix_errno; - } - - return err; -} - -static int bluetooth_parse_capabilities(struct bluetooth_data *data, - struct bt_get_capabilities_rsp *rsp) -{ - int bytes_left = rsp->h.length - sizeof(*rsp); - codec_capabilities_t *codec = (void *) rsp->data; - - data->transport = codec->transport; - - if (codec->transport != BT_CAPABILITIES_TRANSPORT_A2DP) - return 0; - - while (bytes_left > 0) { - if ((codec->type == BT_A2DP_SBC_SINK) && - !(codec->lock & BT_WRITE_LOCK)) - break; - - bytes_left -= codec->length; - codec = (void *) codec + codec->length; - } - - if (bytes_left <= 0 || - codec->length != sizeof(data->a2dp.sbc_capabilities)) - return -EINVAL; - - memcpy(&data->a2dp.sbc_capabilities, codec, codec->length); - - return 0; -} - -static int bluetooth_init(struct bluetooth_data *data, - snd_pcm_stream_t stream, snd_config_t *conf) -{ - int sk, err; - struct bluetooth_alsa_config *alsa_conf = &data->alsa_config; - char buf[BT_SUGGESTED_BUFFER_SIZE]; - struct bt_get_capabilities_req *req = (void *) buf; - struct bt_get_capabilities_rsp *rsp = (void *) buf; - - memset(data, 0, sizeof(struct bluetooth_data)); - - err = bluetooth_parse_config(conf, alsa_conf); - if (err < 0) - return err; - - data->server.fd = -1; - data->stream.fd = -1; - - sk = bt_audio_service_open(); - if (sk < 0) { - err = -errno; - goto failed; - } - - data->server.fd = sk; - data->server.events = POLLIN; - - data->pipefd[0] = -1; - data->pipefd[1] = -1; - - if (pipe(data->pipefd) < 0) { - err = -errno; - goto failed; - } - if (fcntl(data->pipefd[0], F_SETFL, O_NONBLOCK) < 0) { - err = -errno; - goto failed; - } - if (fcntl(data->pipefd[1], F_SETFL, O_NONBLOCK) < 0) { - err = -errno; - goto failed; - } - - memset(req, 0, BT_SUGGESTED_BUFFER_SIZE); - req->h.type = BT_REQUEST; - req->h.name = BT_GET_CAPABILITIES; - req->h.length = sizeof(*req); - - if (alsa_conf->autoconnect) - req->flags |= BT_FLAG_AUTOCONNECT; - strncpy(req->destination, alsa_conf->device, 18); - if (alsa_conf->has_transport) - req->transport = alsa_conf->transport; - else - req->transport = BT_CAPABILITIES_TRANSPORT_ANY; - - err = audioservice_send(data->server.fd, &req->h); - if (err < 0) - goto failed; - - rsp->h.length = 0; - err = audioservice_expect(data->server.fd, &rsp->h, - BT_GET_CAPABILITIES); - if (err < 0) - goto failed; - - bluetooth_parse_capabilities(data, rsp); - - return 0; - -failed: - if (sk >= 0) - bt_audio_service_close(sk); - return err; -} - -SND_PCM_PLUGIN_DEFINE_FUNC(bluetooth); - -SND_PCM_PLUGIN_DEFINE_FUNC(bluetooth) -{ - struct bluetooth_data *data; - int err; - - DBG("Bluetooth PCM plugin (%s)", - stream == SND_PCM_STREAM_PLAYBACK ? "Playback" : "Capture"); - - data = malloc(sizeof(struct bluetooth_data)); - if (!data) { - err = -ENOMEM; - goto error; - } - - err = bluetooth_init(data, stream, conf); - if (err < 0) - goto error; - - data->io.version = SND_PCM_IOPLUG_VERSION; - data->io.name = "Bluetooth Audio Device"; - data->io.mmap_rw = 0; /* No direct mmap communication */ - data->io.private_data = data; - - if (data->transport == BT_CAPABILITIES_TRANSPORT_A2DP) - data->io.callback = stream == SND_PCM_STREAM_PLAYBACK ? - &bluetooth_a2dp_playback : - &bluetooth_a2dp_capture; - else - data->io.callback = stream == SND_PCM_STREAM_PLAYBACK ? - &bluetooth_hsp_playback : - &bluetooth_hsp_capture; - - err = snd_pcm_ioplug_create(&data->io, name, stream, mode); - if (err < 0) - goto error; - - if (data->transport == BT_CAPABILITIES_TRANSPORT_A2DP) - err = bluetooth_a2dp_hw_constraint(&data->io); - else - err = bluetooth_hsp_hw_constraint(&data->io); - - if (err < 0) { - snd_pcm_ioplug_delete(&data->io); - goto error; - } - - *pcmp = data->io.pcm; - - return 0; - -error: - if (data) - bluetooth_exit(data); - - return err; -} - -SND_PCM_PLUGIN_SYMBOL(bluetooth); diff --git a/configure.ac b/configure.ac index f2db920..7f331ae 100644 --- a/configure.ac +++ b/configure.ac @@ -41,7 +41,6 @@ AC_CHECK_HEADER([sys/inotify.h], [AC_MSG_ERROR(inotify headers are required and missing)]) AC_PATH_DBUS AC_PATH_GLIB -AC_PATH_ALSA AC_PATH_GSTREAMER AC_PATH_USB AC_PATH_UDEV -- 1.7.10.2 -- To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html