diff options
Diffstat (limited to 'audio/ctl_bluetooth.c')
-rw-r--r-- | audio/ctl_bluetooth.c | 384 |
1 files changed, 384 insertions, 0 deletions
diff --git a/audio/ctl_bluetooth.c b/audio/ctl_bluetooth.c new file mode 100644 index 0000000..2981385 --- /dev/null +++ b/audio/ctl_bluetooth.c @@ -0,0 +1,384 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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) + goto done; + + *value = rsp->key; +done: + 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 ret; + const char *type, *name; + + DBG("ext %p id %p", ext, id); + + memset(buf, 0, sizeof(buf)); + + ret = recv(data->sock, ind, BT_SUGGESTED_BUFFER_SIZE, MSG_DONTWAIT); + if (ret < 0) { + SNDERR("Failed while receiving data: %s (%d)", + strerror(errno), errno); + return -errno; + } + + 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); |