diff options
Diffstat (limited to 'audio')
-rw-r--r-- | audio/ipc.h | 10 | ||||
-rw-r--r-- | audio/pcm_bluetooth.c | 317 | ||||
-rw-r--r-- | audio/unix.c | 34 |
3 files changed, 307 insertions, 54 deletions
diff --git a/audio/ipc.h b/audio/ipc.h index e6859a50..0cd9e620 100644 --- a/audio/ipc.h +++ b/audio/ipc.h @@ -40,10 +40,10 @@ /* Packet types */ #define PKT_TYPE_CFG_REQ 0 #define PKT_TYPE_CFG_RSP 1 -#define PKT_TYPE_STATUS_REQ 3 -#define PKT_TYPE_STATUS_RSP 4 -#define PKT_TYPE_CTL_REQ 5 -#define PKT_TYPE_CTL_RSP 6 +#define PKT_TYPE_STATUS_REQ 2 +#define PKT_TYPE_STATUS_RSP 3 +#define PKT_TYPE_CTL_REQ 4 +#define PKT_TYPE_CTL_RSP 5 /* Errors codes */ #define PKT_ERROR_NONE 0 @@ -68,6 +68,8 @@ struct ipc_data_cfg { uint8_t encoding; /* Stream encoding */ uint8_t bitpool; /* Encoding bitpool */ uint8_t channels; /* Number of audio channel */ + uint8_t pkt_len; /* Stream packet length */ + uint8_t sample_size; /* Sample size in bytes */ uint16_t rate; /* Stream sample rate */ } __attribute__ ((packed)); diff --git a/audio/pcm_bluetooth.c b/audio/pcm_bluetooth.c index 838dcd39..29583940 100644 --- a/audio/pcm_bluetooth.c +++ b/audio/pcm_bluetooth.c @@ -31,14 +31,28 @@ #include <alsa/asoundlib.h> #include <alsa/pcm_external.h> +#include <bluetooth/bluetooth.h> +#include <bluetooth/sco.h> + #include "ipc.h" #define DBG(fmt, arg...) printf("DEBUG: %s: " fmt "\n" , __FUNCTION__ , ## arg) +#ifndef SCO_TXBUFS +#define SCO_TXBUFS 0x03 +#endif + +#ifndef SCO_RXBUFS +#define SCO_RXBUFS 0x04 +#endif + struct bluetooth_data { snd_pcm_ioplug_t io; snd_pcm_sframes_t hw_ptr; - int sock; + struct ipc_data_cfg cfg; /* Bluetooth device config */ + int sock; /* Daemon unix socket */ + uint8_t *buffer; /* Transfer buffer */ + uint8_t count; /* Transfer buffer counter */ }; static int bluetooth_start(snd_pcm_ioplug_t *io) @@ -59,9 +73,9 @@ static snd_pcm_sframes_t bluetooth_pointer(snd_pcm_ioplug_t *io) { struct bluetooth_data *data = io->private_data; - //DBG("io %p", io); + DBG("io %p", io); - //DBG("hw_ptr=%lu", data->hw_ptr); + DBG("hw_ptr=%lu", data->hw_ptr); return data->hw_ptr; } @@ -77,16 +91,171 @@ static int bluetooth_close(snd_pcm_ioplug_t *io) return 0; } + +static int bluetooth_prepare(snd_pcm_ioplug_t *io) +{ + struct bluetooth_data *data = io->private_data; + + DBG("Preparing with io->period_size = %lu, io->buffer_size = %lu", io->period_size, io->buffer_size); + + 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; + } + return 0; +} + +static int bluetooth_hw_params(snd_pcm_ioplug_t *io, snd_pcm_hw_params_t *params) +{ + struct bluetooth_data *data = io->private_data; + struct ipc_data_cfg cfg = data->cfg; + uint32_t period_count = io->buffer_size / io->period_size; + + DBG("period_count = %d", period_count); + + if(setsockopt(cfg.fd, SOL_SCO, + io->stream == SND_PCM_STREAM_PLAYBACK ? SCO_TXBUFS : SCO_RXBUFS, + &period_count, + sizeof(period_count)) == 0) { + return 0; + } else if(setsockopt(cfg.fd, SOL_SCO, + io->stream == SND_PCM_STREAM_PLAYBACK ? SO_SNDBUF : SO_RCVBUF, + &period_count, + sizeof(period_count)) == 0) { + return 0; + } else { + SNDERR("Unable to set number of SCO buffers : please upgrade your Kernel !"); + return -EINVAL; + } +} + +static snd_pcm_sframes_t bluetooth_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; + struct ipc_data_cfg cfg = data->cfg; + + snd_pcm_sframes_t ret = 0; + + DBG("areas->step=%u, areas->first=%u, offset=%lu, size=%lu, io->nonblock=%u", + areas->step, areas->first, offset, size, io->nonblock); + + if (data->count == 0) { + int nrecv; + + nrecv = recv(cfg.fd, data->buffer, cfg.pkt_len, + MSG_WAITALL | (io->nonblock ? MSG_DONTWAIT : 0 )); + + if (nrecv == cfg.pkt_len) { + ret = 0; + /* Increment hardware transmition pointer */ + data->hw_ptr = (data->hw_ptr + cfg.pkt_len / cfg.sample_size) % io->buffer_size; + } + else if (nrecv > 0) { + ret = -EIO; + SNDERR(strerror(-ret)); + } + else if (nrecv == -1 && errno == EAGAIN) { + ret = -EAGAIN; + } + else { /* nrecv < 0 */ + /* EPIPE means device underrun in ALSA world. But we mean we lost contact + with server, so we have to find another error code */ + ret = (errno == EPIPE ? -EIO : -errno); + SYSERR("Lost contact with headsetd"); + } + } + if(ret == 0) { /* Still ok, proceed */ + snd_pcm_uframes_t frames_to_write; + unsigned char *buff; + + buff = (unsigned char *) areas->addr + (areas->first + areas->step * offset) / 8; + + if((data->count + cfg.sample_size * size) <= cfg.pkt_len) + frames_to_write = size; + else + frames_to_write = (cfg.pkt_len - data->count) / cfg.sample_size; + + memcpy(buff, data->buffer + data->count, areas->step / 8 * frames_to_write); + data->count += (areas->step / 8 * frames_to_write); + data->count %= cfg.pkt_len; + /* Return written frames count */ + ret = frames_to_write; + } + + DBG("returning %d", (int)ret); + return ret; +} + +static snd_pcm_sframes_t bluetooth_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 ipc_data_cfg cfg = data->cfg; + snd_pcm_sframes_t ret = 0; + snd_pcm_uframes_t frames_to_read; + unsigned char *buff; + + DBG("areas->step=%u, areas->first=%u, offset=%lu, size=%lu, io->nonblock=%u", + areas->step, areas->first, offset, size, io->nonblock); + + if ((data->count + cfg.sample_size * size) <= cfg.pkt_len) + frames_to_read = size; + else + frames_to_read = (cfg.pkt_len - data->count) / cfg.sample_size; + + /* Ready for more data */ + buff = (unsigned char *) areas->addr + (areas->first + areas->step * offset) / 8; + memcpy(data->buffer + data->count, buff, areas->step / 8 * frames_to_read); + + if ((data->count + areas->step / 8 * frames_to_read) == cfg.pkt_len) { + int rsend; + /* Actually send packet */ + rsend = send(cfg.fd, data->buffer, cfg.pkt_len, io->nonblock ? MSG_DONTWAIT : 0); + if (rsend > 0) { + /* Reset count pointer */ + data->count = 0; + + /* Increment hardware transmition pointer */ + data->hw_ptr = (data->hw_ptr + cfg.pkt_len / cfg.sample_size) % io->buffer_size; + + ret = frames_to_read; + } + else { + /* EPIPE means device underrun in ALSA world. But we mean we lost contact + with server, so we have to find another error code */ + ret = (errno == EPIPE ? -EIO : -errno); + if(errno == EPIPE) + SYSERR("Lost contact with headsetd"); + } + } + else { + /* Remember we have some frame in the pipe now */ + data->count += areas->step / 8 * frames_to_read; + /* Ask for more */ + ret = frames_to_read; + } + + DBG("returning %d", (int)ret); + return ret; +} + static snd_pcm_ioplug_callback_t bluetooth_playback_callback = { .start = bluetooth_start, .stop = bluetooth_stop, .pointer = bluetooth_pointer, .close = bluetooth_close, -#if 0 .hw_params = bluetooth_hw_params, .prepare = bluetooth_prepare, .transfer = bluetooth_write, -#endif }; static snd_pcm_ioplug_callback_t bluetooth_capture_callback = { @@ -94,11 +263,9 @@ static snd_pcm_ioplug_callback_t bluetooth_capture_callback = { .stop = bluetooth_stop, .pointer = bluetooth_pointer, .close = bluetooth_close, -#if 0 .hw_params = bluetooth_hw_params, .prepare = bluetooth_prepare, .transfer = bluetooth_read, -#endif }; #define ARRAY_NELEMS(a) (sizeof((a)) / sizeof((a)[0])) @@ -146,42 +313,66 @@ static int bluetooth_hw_constraint(snd_pcm_ioplug_t *io) return 0; } -SND_PCM_PLUGIN_DEFINE_FUNC(bluetooth) +static int bluetooth_cfg(struct bluetooth_data *data) { - snd_config_iterator_t iter, next; - struct bluetooth_data *data; - struct sockaddr_un addr; - unsigned int id; - int sk, err; - - DBG("Bluetooth PCM plugin (%s)", - stream == SND_PCM_STREAM_PLAYBACK ? "Playback" : "Capture"); - - snd_config_for_each(iter, next, conf) { - snd_config_t *n = snd_config_iterator_entry(iter); - const char *id; + struct ipc_packet pkt; + int res; + + DBG("Sending PKT_TYPE_CFG_REQ..."); + pkt.type = PKT_TYPE_CFG_REQ; + pkt.role = PKT_ROLE_NONE; + res = send(data->sock, &pkt, sizeof(struct ipc_packet), 0); + if (res < 0) + return errno; + DBG("OK - %d bytes sent", res); + + DBG("Waiting for response..."); + do { + int len = sizeof(struct ipc_packet) + sizeof(struct ipc_data_cfg); + struct ipc_packet *pkt_ptr; + + pkt_ptr = malloc(sizeof(struct ipc_packet) + sizeof(struct ipc_data_cfg)); + res = recv(data->sock, pkt_ptr, len, MSG_WAITALL | (data->io.nonblock ? MSG_DONTWAIT : 0 )); + } while ((res < 0) && (errno == EINTR)); + if (res < 0) + return -errno; + DBG("OK - %d bytes received", res); - if (snd_config_get_id(n, &id) < 0) - continue; + if (pkt.type != PKT_TYPE_CFG_RSP) { + SNDERR("Unexpected packet type received: type = %d", pkt.type); + return -EINVAL; + } - if (strcmp(id, "comment") == 0 || strcmp(id, "type") == 0) - continue; + if (pkt.error != PKT_ERROR_NONE) { + SNDERR("Error while configuring device: error = %d", pkt.error); + return pkt.error; + } - if (strcmp(id, "bdaddr") == 0) { - const char *str; - if (snd_config_get_string(n, &str) < 0) { - SNDERR("Invalid type for %s", id); - return -EINVAL; - } - printf("bdaddr %s\n", str); - continue; - } + if (pkt.length != sizeof(struct ipc_data_cfg)) { + SNDERR("Error while configuring device: packet size doesn't match"); + return -EINVAL; + } - SNDERR("Unknown field %s", id); + memcpy(&data->cfg, &pkt.data, pkt.length); + if (data->cfg.fd == -1) { + SNDERR("Error while configuring device: could not acquire audio socket"); return -EINVAL; } + DBG("Device configuration:"); + DBG("fd=%d, fd_opt=%u, channels=%u, pkt_len=%u, sample_size=%u, rate=%u", + data->cfg.fd, data->cfg.fd_opt, data->cfg.channels, + data->cfg.pkt_len, data->cfg.sample_size, data->cfg.rate); + + return 0; +} + +static int bluetooth_init(struct bluetooth_data *data) +{ + int sk, err, id; + struct sockaddr_un addr; + id = abs(getpid() * rand()); sk = socket(PF_LOCAL, SOCK_DGRAM, 0); @@ -195,6 +386,7 @@ SND_PCM_PLUGIN_DEFINE_FUNC(bluetooth) snprintf(addr.sun_path + 1, UNIX_PATH_MAX - 2, "%s/%d", IPC_SOCKET_NAME, id); + DBG("Binding address: %s", addr.sun_path + 1); if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { SNDERR("Can't bind socket"); close(sk); @@ -205,6 +397,7 @@ SND_PCM_PLUGIN_DEFINE_FUNC(bluetooth) addr.sun_family = AF_UNIX; snprintf(addr.sun_path + 1, UNIX_PATH_MAX - 2, "%s", IPC_SOCKET_NAME); + DBG("Connecting to address: %s", addr.sun_path + 1); if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { SNDERR("Can't connect socket"); close(sk); @@ -221,34 +414,62 @@ SND_PCM_PLUGIN_DEFINE_FUNC(bluetooth) data->sock = sk; - data->io.version = SND_PCM_IOPLUG_VERSION; - data->io.name = "Bluetooth Audio"; - data->io.mmap_rw = 0; /* No direct mmap communication */ + if ((err = bluetooth_cfg(data)) < 0) + return err; + + data->buffer = malloc(data->cfg.pkt_len); + + memset(data->buffer, 0, data->cfg.pkt_len); - data->io.callback = stream == SND_PCM_STREAM_PLAYBACK ? + return 0; +} + +SND_PCM_PLUGIN_DEFINE_FUNC(bluetooth) +{ +// snd_config_iterator_t iter, next; + struct bluetooth_data data; + int err; + + DBG("Bluetooth PCM plugin blablabla (%s)", + stream == SND_PCM_STREAM_PLAYBACK ? "Playback" : "Capture"); + +// snd_config_for_each(iter, next, conf) { +// } + + DBG("Initing Bluetooth..."); + err = bluetooth_init(&data); + if (err < 0) + goto error; + DBG("Done"); + + data.io.version = SND_PCM_IOPLUG_VERSION; + data.io.name = "Bluetooth Audio Device"; + data.io.mmap_rw = 0; /* No direct mmap communication */ + + data.io.callback = stream == SND_PCM_STREAM_PLAYBACK ? &bluetooth_playback_callback : &bluetooth_capture_callback; - data->io.poll_fd = sk; - data->io.poll_events = POLLIN; - data->io.private_data = data; + data.io.poll_fd = data.cfg.fd; + data.io.poll_events = POLLIN; + data.io.private_data = &data; - err = snd_pcm_ioplug_create(&data->io, name, stream, mode); + err = snd_pcm_ioplug_create(&data.io, name, stream, mode); if (err < 0) goto error; - err = bluetooth_hw_constraint(&data->io); + err = bluetooth_hw_constraint(&data.io); if (err < 0) { - snd_pcm_ioplug_delete(&data->io); - goto error; + snd_pcm_ioplug_delete(&data.io); + goto error; } - *pcmp = data->io.pcm; + *pcmp = data.io.pcm; return 0; error: - close(sk); + close(data.sock); - free(data); + free(&data); return err; } diff --git a/audio/unix.c b/audio/unix.c index ce2041b2..8cd1affc 100644 --- a/audio/unix.c +++ b/audio/unix.c @@ -48,7 +48,8 @@ static gboolean unix_event(GIOChannel *chan, GIOCondition cond, gpointer data) { struct sockaddr_un addr; socklen_t addrlen; - unsigned char buf[128]; + struct ipc_packet pkt; + struct ipc_data_cfg cfg; int sk, len; debug("chan %p cond %td data %p", chan, cond, data); @@ -66,10 +67,37 @@ static gboolean unix_event(GIOChannel *chan, GIOCondition cond, gpointer data) memset(&addr, 0, sizeof(addr)); addrlen = sizeof(addr); - len = recvfrom(sk, buf, sizeof(buf), 0, (struct sockaddr *) &addr, &addrlen); + len = recvfrom(sk, &pkt, sizeof(pkt), 0, (struct sockaddr *) &addr, &addrlen); debug("path %s len %d", addr.sun_path + 1, len); + switch (pkt.type) { + case PKT_TYPE_CFG_REQ: + info("Package PKT_TYPE_CFG_REQ:%u", pkt.role); + struct ipc_data_cfg *cfg_ptr; + + cfg.fd = -1; + cfg.fd_opt = CFG_FD_OPT_READWRITE; + cfg.encoding = 0; + cfg.bitpool = 0; + cfg.channels = 1; + cfg.pkt_len = 48; + cfg.sample_size = 2; + cfg.rate = 8000; + + cfg_ptr = (struct ipc_data_cfg *) &pkt.data; + pkt.type = PKT_TYPE_CFG_RSP; + cfg_ptr[0] = cfg; + pkt.length = sizeof(struct ipc_data_cfg); + len = send(sk, &pkt, sizeof(struct ipc_packet) + sizeof(struct ipc_data_cfg), 0); + break; + case PKT_TYPE_STATUS_REQ: + info("Package PKT_TYPE_STATUS_REQ"); + break; + case PKT_TYPE_CTL_REQ: + info("Package PKT_TYPE_CTL_REQ"); + break; + } return TRUE; } @@ -106,6 +134,8 @@ int unix_init(void) g_io_channel_unref(io); + info("Unix socket created: %d", sk); + return 0; } |