diff options
-rw-r--r-- | audio/pcm_bluetooth.c | 411 | ||||
-rw-r--r-- | audio/unix.c | 7 |
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: |