summaryrefslogtreecommitdiffstats
path: root/audio
diff options
context:
space:
mode:
authorJohan Hedberg <johan.hedberg@nokia.com>2007-08-20 09:05:02 +0000
committerJohan Hedberg <johan.hedberg@nokia.com>2007-08-20 09:05:02 +0000
commit3919df0168ddf293912231e5e6477bc6f0c24115 (patch)
tree63b406c8cd26dce9cdca433ab1b586f056fbd085 /audio
parent730a4ceb9e7e986e2a327fa7e023c3dd2102a50d (diff)
Fix handling of multiple unix clients
Diffstat (limited to 'audio')
-rw-r--r--audio/pcm_bluetooth.c411
-rw-r--r--audio/unix.c7
2 files changed, 268 insertions, 150 deletions
diff --git a/audio/pcm_bluetooth.c b/audio/pcm_bluetooth.c
index c75f8af3..a7c90661 100644
--- a/audio/pcm_bluetooth.c
+++ b/audio/pcm_bluetooth.c
@@ -28,6 +28,7 @@
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/time.h>
+#include <pthread.h>
#include <netinet/in.h>
@@ -40,7 +41,7 @@
#include "ipc.h"
#include "sbc.h"
-//#define ENABLE_DEBUG
+/*#define ENABLE_DEBUG*/
#define BUFFER_SIZE 2048
@@ -58,6 +59,9 @@
#define SCO_RXBUFS 0x04
#endif
+#define PERIOD_TIME_USECS(data) (1000000.0 * \
+ ((data)->io.period_size) / \
+ (data)->io.rate)
struct rtp_header {
uint8_t cc:4;
uint8_t x:1;
@@ -93,13 +97,13 @@ struct bluetooth_a2dp {
uint16_t seq_num; /* */
int frame_count; /* */
- int bandwithcount;
- struct timeval bandwithtimestamp;
+ pthread_t hw_thread; /* Makes virtual hw pointer move */
+ int pipefd[2]; /* Inter thread communication */
};
struct bluetooth_data {
snd_pcm_ioplug_t io;
- snd_pcm_sframes_t hw_ptr;
+ volatile snd_pcm_sframes_t hw_ptr;
struct ipc_data_cfg cfg; /* Bluetooth device config */
int stream_fd; /* Audio stream filedescriptor */
int sock; /* Daemon unix socket */
@@ -113,9 +117,9 @@ void memcpy_changeendian(void *dst, const void *src, int size)
int i;
const uint16_t *ptrsrc = src;
uint16_t *ptrdst = dst;
- for (i = 0; i < size / 2; i++) {
+
+ for (i = 0; i < size / 2; i++)
*ptrdst++ = htons(*ptrsrc++);
- }
}
static int bluetooth_start(snd_pcm_ioplug_t *io)
@@ -132,6 +136,85 @@ static int bluetooth_stop(snd_pcm_ioplug_t *io)
return 0;
}
+static void *a2dp_playback_hw_thread(void *param)
+{
+ struct bluetooth_data* data = (struct bluetooth_data *)param;
+ unsigned int num_period_elapsed = 0;
+ unsigned long long starttime; /* in usecs */
+ struct timeval tv;
+ int ret;
+
+ gettimeofday(&tv, 0);
+ starttime = tv.tv_sec * 1000000 + tv.tv_usec;
+
+ for(;;) {
+ unsigned long long curtime;
+ unsigned int ntimes;
+
+ gettimeofday(&tv, 0);
+
+ /* How much time period_time has elapsed since the thread started ? */
+ curtime = tv.tv_sec * 1000000 + tv.tv_usec;
+ ntimes = (1.0 * (curtime - starttime)) / PERIOD_TIME_USECS(data);
+
+ if (ntimes > num_period_elapsed) {
+ char c = 'w';
+ data->hw_ptr = (data->hw_ptr +
+ (ntimes - num_period_elapsed)
+ * data->io.period_size)
+ % data->io.buffer_size;
+ DBG("pointer = %ld", data->hw_ptr);
+ /* Notify user that hardware pointer has moved */
+ ret = write(data->a2dp.pipefd[1], &c, 1);
+ assert(ret == 1);
+ num_period_elapsed = ntimes;
+ }
+ /* Period time is usually no shorter that 1 ms,
+ no need to sleep for a shorter amount of time */
+ usleep(1000);
+ /* Offer opportunity to be canceled by main thread */
+ pthread_testcancel();
+ }
+}
+static int bluetooth_a2dp_playback_start(snd_pcm_ioplug_t *io)
+{
+ struct bluetooth_data *data = io->private_data;
+ struct bluetooth_a2dp *a2dp_data = &data->a2dp;
+ int ret = 0;
+
+ DBG("%p", io);
+
+ assert(a2dp_data->hw_thread == 0);
+ ret = -pthread_create(&a2dp_data->hw_thread, 0, a2dp_playback_hw_thread, data);
+
+ DBG(" - return %d", ret);
+
+ return ret;
+}
+
+static int bluetooth_a2dp_playback_stop(snd_pcm_ioplug_t *io)
+{
+ struct bluetooth_data *data = io->private_data;
+ struct bluetooth_a2dp *a2dp_data = &data->a2dp;
+ int ret = 0;
+
+ DBG("%p", io);
+
+ /* Beware - We can be called more than once */
+ if (a2dp_data->hw_thread != 0) {
+ ret = -pthread_cancel(a2dp_data->hw_thread);
+ if (ret != 0)
+ goto done;
+
+ ret = -pthread_join(a2dp_data->hw_thread, 0);
+ }
+
+done:
+ a2dp_data->hw_thread = 0;
+ DBG(" - return %d", ret);
+ return ret;
+}
+
static snd_pcm_sframes_t bluetooth_pointer(snd_pcm_ioplug_t *io)
{
struct bluetooth_data *data = io->private_data;
@@ -154,6 +237,12 @@ static void bluetooth_exit(struct bluetooth_data *data)
if (data->cfg.codec == CFG_CODEC_SBC)
sbc_finish(&data->a2dp.sbc);
+ if(data->a2dp.pipefd[0] > 0)
+ close(data->a2dp.pipefd[0]);
+
+ if(data->a2dp.pipefd[1] > 0)
+ close(data->a2dp.pipefd[1]);
+
free(data);
}
@@ -171,6 +260,7 @@ static int bluetooth_close(snd_pcm_ioplug_t *io)
static int bluetooth_prepare(snd_pcm_ioplug_t *io)
{
struct bluetooth_data *data = io->private_data;
+ char c = 'w';
DBG("Preparing with io->period_size = %lu, io->buffer_size = %lu",
io->period_size, io->buffer_size);
@@ -184,7 +274,8 @@ static int bluetooth_prepare(snd_pcm_ioplug_t *io)
* If it is, capture won't start */
data->hw_ptr = io->period_size;
- return 0;
+ /* a2dp : wake up any client polling at us */
+ return write(data->a2dp.pipefd[1], &c, 1);
}
static int bluetooth_hsp_hw_params(snd_pcm_ioplug_t *io,
@@ -234,11 +325,80 @@ static int bluetooth_a2dp_hw_params(snd_pcm_ioplug_t *io,
return 0;
err = errno;
+
SNDERR("%s (%d)", strerror(err), err);
return -err;
}
+static int bluetooth_poll_descriptors(snd_pcm_ioplug_t *io,
+ struct pollfd *pfd,
+ unsigned int space)
+{
+ assert(io);
+ assert(space >= 1);
+
+ pfd[0].fd = ((struct bluetooth_data *)io->private_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_a2dp_playback_poll_descriptors(snd_pcm_ioplug_t *io,
+ struct pollfd *pfd,
+ unsigned int space)
+{
+ struct bluetooth_data *data = io->private_data;
+
+ DBG("");
+
+ assert(io);
+ assert(space >= 1);
+ assert(data->a2dp.pipefd[0] != 0);
+
+ pfd[0].fd = data->a2dp.pipefd[0];
+ pfd[0].events = POLLIN;
+ pfd[0].revents = 0;
+
+ return 1;
+}
+
+static int bluetooth_a2dp_playback_poll_revents(snd_pcm_ioplug_t *io,
+ struct pollfd *pfds,
+ unsigned int nfds,
+ unsigned short *revents)
+{
+ static char buf[1];
+ int ret = 0;
+
+ DBG("");
+
+ assert(pfds);
+ assert(nfds == 1);
+ assert(revents);
+ assert(pfds[0].fd != 0);
+
+ if (io->state != SND_PCM_STATE_PREPARED)
+ ret = read(pfds[0].fd, buf, 1);
+
+ *revents = (pfds[0].revents & ~POLLIN) | POLLOUT;
+
+ 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,
@@ -364,19 +524,14 @@ static snd_pcm_sframes_t bluetooth_a2dp_read(snd_pcm_ioplug_t *io,
return ret;
}
-static int avdtp_write(struct bluetooth_data *data, unsigned int nonblock)
+static int avdtp_write(struct bluetooth_data *data)
{
- int count = 0, written = 0, ret = 0;
+ int ret = 0;
struct rtp_header *header;
struct rtp_payload *payload;
struct bluetooth_a2dp *a2dp = &data->a2dp;
-#ifdef ENABLE_DEBUG
- static struct timeval send_date = { 0, 0 };
- static struct timeval prev_date = { 0, 0 };
- struct timeval send_delay = { 0, 0 };
- struct timeval sendz_delay = { 0, 0 };
-#endif
+ DBG("");
header = (void *) a2dp->buffer;
payload = (void *) (a2dp->buffer + sizeof(*header));
@@ -389,98 +544,24 @@ static int avdtp_write(struct bluetooth_data *data, unsigned int nonblock)
header->timestamp = htonl(a2dp->nsamples);
header->ssrc = htonl(1);
- while (count++ < 10) {
-#ifdef ENABLE_DEBUG
- gettimeofday(&send_date, NULL);
-#endif
- ret = send(data->stream_fd, a2dp->buffer, a2dp->count,
- nonblock ? MSG_DONTWAIT : 0);
- if (ret < 0) {
- ret = -errno;
- if (errno == EAGAIN)
- goto retry;
- fprintf(stderr, "send: %s (%d)\n", strerror(errno),
- errno);
- goto done;
- }
-
- written += ret;
-
-#ifdef ENABLE_DEBUG
- if ((written >= 0 || errno == EAGAIN) && prev_date.tv_sec != 0) {
- long delay, real, theo, delta;
-
- delay = (long) (send_delay.tv_sec * 1000 +
- send_delay.tv_usec / 1000),
- real = (long) (sendz_delay.tv_sec * 1000 +
- sendz_delay.tv_usec / 1000);
- theo = (long) (((float) a2dp->nsamples) /
- data->cfg.rate * 1000.0);
- delta = (long) (sendz_delay.tv_sec * 1000 +
- sendz_delay.tv_usec / 1000) -
- (long) (((float) a2dp->nsamples) /
- data->cfg.rate * 1000.0);
-
- timersub(&send_date, &prev_date, &send_delay);
- timersub(&send_date, &a2dp->ntimestamp, &sendz_delay);
-
- printf("send %d (cumul=%d) samples (delay=%ld ms,"
- " real=%ld ms, theo=%ld ms,"
- " delta=%ld ms).\n", a2dp->samples,
- a2dp->nsamples, delay, real, theo,
- delta);
- }
-#endif
- if (written == a2dp->count)
- break;
-
- a2dp->count -= written;
+ ret = send(data->stream_fd, a2dp->buffer, a2dp->count,
+ MSG_DONTWAIT);
+ if(ret == -1)
+ ret = -errno;
-retry:
- DBG("send (retry).");
- usleep(150000);
- }
-
-#ifdef ENABLE_DEBUG
- prev_date = send_date;
-#endif
-
- if (written != a2dp->count)
- printf("Wrote %d not %d bytes\n", written, a2dp->count);
-
-#ifdef ENABLE_DEBUG
- else {
- /* Measure bandwith usage */
- struct timeval now = { 0, 0 };
- struct timeval interval = { 0, 0 };
-
- if(a2dp->bandwithtimestamp.tv_sec == 0)
- gettimeofday(&a2dp->bandwithtimestamp, NULL);
-
- /* See if we must wait again */
- gettimeofday(&now, NULL);
- timersub(&now, &a2dp->bandwithtimestamp, &interval);
- if(interval.tv_sec > 0)
- printf("Bandwith: %d (%d kbps)\n", a2dp->bandwithcount,
- a2dp->bandwithcount / 128);
- a2dp->bandwithtimestamp = now;
- a2dp->bandwithcount = 0;
- }
-
- a2dp->bandwithcount += written;
+ /* Kernel side l2cap socket layer makes sure either everything
+ is buffered for sending, or nothing is buffered.
+ This assertion is to remind people of this fact (and be noticed
+ the day that changes)
+ */
+ assert(ret < 0 || ret == a2dp->count);
-#endif
-
-done:
/* 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++;
- if (written > 0)
- return written;
-
return ret;
}
@@ -497,9 +578,35 @@ static snd_pcm_sframes_t bluetooth_a2dp_write(snd_pcm_ioplug_t *io,
uint8_t *buff;
static int codesize = 0;
- DBG("areas->step=%u, areas->first=%u, offset=%lu, size=%lu,"
- "io->nonblock=%u", areas->step, areas->first,
- offset, size, io->nonblock);
+ DBG("areas->step=%u, areas->first=%u, offset=%lu, size=%lu"
+ , areas->step, areas->first, offset, size);
+ DBG("hw_ptr = %lu, appl_ptr = %lu"
+ , io->hw_ptr, io->appl_ptr);
+
+ if(io->hw_ptr > io->appl_ptr) {
+ ret = bluetooth_a2dp_playback_stop(io);
+ if(ret == 0)
+ ret = -EPIPE;
+ goto done;
+ }
+
+ /* 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)
+ goto done;
+ }
+ }
+ snd_pcm_sw_params_free(swparams);
+ }
if (codesize == 0) {
/* How much data can be encoded by sbc at a time? */
@@ -548,7 +655,7 @@ static snd_pcm_sframes_t bluetooth_a2dp_write(snd_pcm_ioplug_t *io,
DBG("encoded = %d a2dp.sbc.len= %d", encoded, a2dp->sbc.len);
if (a2dp->count + a2dp->sbc.len >= data->cfg.pkt_len) {
- ret = avdtp_write(data, io->nonblock);
+ ret = avdtp_write(data);
if (ret < 0) {
if (-ret == EPIPE)
ret = -EIO;
@@ -561,55 +668,60 @@ static snd_pcm_sframes_t bluetooth_a2dp_write(snd_pcm_ioplug_t *io,
a2dp->frame_count++;
a2dp->samples += encoded / frame_size;
a2dp->nsamples += encoded / frame_size;
- /* Increment hardware transmition pointer */
- data->hw_ptr = (data->hw_ptr + encoded / frame_size)
- % io->buffer_size;
ret = frames_to_read;
done:
- DBG("returning %lu", ret);
+ DBG("returning %ld", ret);
return ret;
}
static snd_pcm_ioplug_callback_t bluetooth_hsp_playback = {
- .start = bluetooth_start,
- .stop = bluetooth_stop,
- .pointer = bluetooth_pointer,
- .close = bluetooth_close,
- .hw_params = bluetooth_hsp_hw_params,
- .prepare = bluetooth_prepare,
- .transfer = bluetooth_hsp_write,
+ .start = bluetooth_start,
+ .stop = bluetooth_stop,
+ .pointer = bluetooth_pointer,
+ .close = bluetooth_close,
+ .hw_params = bluetooth_hsp_hw_params,
+ .prepare = bluetooth_prepare,
+ .transfer = bluetooth_hsp_write,
+ .poll_descriptors = bluetooth_poll_descriptors,
+ .poll_revents = bluetooth_poll_revents,
};
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,
+ .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_start,
- .stop = bluetooth_stop,
- .pointer = bluetooth_pointer,
- .close = bluetooth_close,
- .hw_params = bluetooth_a2dp_hw_params,
- .prepare = bluetooth_prepare,
- .transfer = bluetooth_a2dp_write,
+ .start = bluetooth_a2dp_playback_start,
+ .stop = bluetooth_a2dp_playback_stop,
+ .pointer = bluetooth_pointer,
+ .close = bluetooth_close,
+ .hw_params = bluetooth_a2dp_hw_params,
+ .prepare = bluetooth_prepare,
+ .transfer = bluetooth_a2dp_write,
+ .poll_descriptors = bluetooth_a2dp_playback_poll_descriptors,
+ .poll_revents = bluetooth_a2dp_playback_poll_revents,
};
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,
+ .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]))
@@ -661,7 +773,7 @@ static int bluetooth_hw_constraint(snd_pcm_ioplug_t *io)
return err;
err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIODS,
- 2, 200);
+ 2, 50);
if (err < 0)
return err;
@@ -678,13 +790,13 @@ static int bluetooth_recvmsg_fd(struct bluetooth_data *data)
.iov_len = sizeof(pkt)
};
struct msghdr msgh = {
- .msg_name = 0,
- .msg_namelen = 0,
- .msg_iov = &iov,
- .msg_iovlen = 1,
- .msg_control = &cmsg_b,
- .msg_controllen = CMSG_LEN(sizeof(int)),
- .msg_flags = 0
+ .msg_name = 0,
+ .msg_namelen = 0,
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = &cmsg_b,
+ .msg_controllen = CMSG_LEN(sizeof(int)),
+ .msg_flags = 0
};
ret = recvmsg(data->sock, &msgh, 0);
@@ -738,6 +850,14 @@ static int bluetooth_a2dp_init(struct bluetooth_data *data,
a2dp->sbc.blocks = sbc->blocks;
a2dp->sbc.bitpool = sbc->bitpool;
+
+ if(pipe(a2dp->pipefd) != 0)
+ return -errno;
+ if(fcntl(a2dp->pipefd[0], F_SETFL, O_NONBLOCK) != 0)
+ return -errno;
+ if(fcntl(a2dp->pipefd[1], F_SETFL, O_NONBLOCK) != 0)
+ return -errno;
+
return 0;
}
@@ -888,7 +1008,7 @@ SND_PCM_PLUGIN_DEFINE_FUNC(bluetooth)
DBG("Bluetooth PCM plugin (%s)",
stream == SND_PCM_STREAM_PLAYBACK ? "Playback" : "Capture");
- data = malloc(sizeof(struct bluetooth_data));
+ data = calloc(1, sizeof(struct bluetooth_data));
if (!data) {
err = -ENOMEM;
goto error;
@@ -901,9 +1021,6 @@ SND_PCM_PLUGIN_DEFINE_FUNC(bluetooth)
data->io.version = SND_PCM_IOPLUG_VERSION;
data->io.name = "Bluetooth Audio Device";
data->io.mmap_rw = 0; /* No direct mmap communication */
- data->io.poll_fd = data->stream_fd;
- data->io.poll_events = stream == SND_PCM_STREAM_PLAYBACK ?
- POLLOUT : POLLIN;
data->io.private_data = data;
if (data->cfg.codec == CFG_CODEC_SBC)
diff --git a/audio/unix.c b/audio/unix.c
index 9bf1a9ca..19a140c0 100644
--- a/audio/unix.c
+++ b/audio/unix.c
@@ -203,6 +203,10 @@ static void a2dp_setup_complete(struct avdtp *session, struct device *dev,
goto failed;
}
+ client->disconnect = (notify_cb_t) a2dp_source_unlock;
+ client->suspend = (notify_cb_t) a2dp_source_suspend;
+ client->play = (notify_cb_t) a2dp_source_start_stream;
+
a2dp->stream = stream;
if (!avdtp_stream_get_transport(stream, &fd, &cfg->pkt_len, &caps)) {
@@ -322,9 +326,6 @@ proceed:
}
client->req_id = id;
- client->disconnect = (notify_cb_t) a2dp_source_unlock;
- client->suspend = (notify_cb_t) a2dp_source_suspend;
- client->play = (notify_cb_t) a2dp_source_start_stream;
break;
case TYPE_HEADSET: