summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Lockwood <lockwood@android.com>2009-05-13 09:51:32 -0400
committerMike Lockwood <lockwood@android.com>2009-05-14 16:46:03 -0400
commit5c4a7a53067d815f515ba8b079244c17a2fc4f4c (patch)
tree3a7d1b072d1d42b586db65c2f5c8bd2b64a765bd
parent3576a490b7bf2d583bf67a6a596ecb2425f55716 (diff)
downloadbluez-5c4a7a53067d815f515ba8b079244c17a2fc4f4c.tar.gz
liba2dp: Move control logic to a separate thread to avoid blocking audio writes.
Signed-off-by: Mike Lockwood <lockwood@android.com>
-rw-r--r--utils/audio/liba2dp.c264
1 files changed, 204 insertions, 60 deletions
diff --git a/utils/audio/liba2dp.c b/utils/audio/liba2dp.c
index 7960b12c..943f542f 100644
--- a/utils/audio/liba2dp.c
+++ b/utils/audio/liba2dp.c
@@ -33,9 +33,11 @@
#include <limits.h>
#include <fcntl.h>
#include <unistd.h>
+#include <pthread.h>
#include <netinet/in.h>
#include <sys/poll.h>
+#include <sys/prctl.h>
#include "ipc.h"
#include "sbc.h"
@@ -83,15 +85,41 @@
/* timeout in milliseconds to prevent poll() from hanging indefinitely */
#define POLL_TIMEOUT 1000
+/* timeout in milliseconds for a2dp_write */
+#define WRITE_TIMEOUT 100
+
+
+typedef enum {
+ A2DP_STATE_NONE = 0,
+ A2DP_STATE_INITIALIZED,
+ A2DP_STATE_CONFIGURING,
+ A2DP_STATE_CONFIGURED,
+ A2DP_STATE_STARTING,
+ A2DP_STATE_STARTED,
+ A2DP_STATE_STOPPING,
+} a2dp_state_t;
+
+typedef enum {
+ A2DP_CMD_NONE = 0,
+ A2DP_CMD_CONFIGURE,
+ A2DP_CMD_START,
+ A2DP_CMD_STOP,
+ A2DP_CMD_QUIT,
+} a2dp_command_t;
+
struct bluetooth_data {
- int link_mtu; /* MTU for selected transport channel */
+ int link_mtu; /* MTU for transport channel */
struct pollfd stream; /* Audio stream filedescriptor */
struct pollfd server; /* Audio daemon filedescriptor */
- int configured; /* true if we have a configured sink */
+ a2dp_state_t state; /* Current A2DP state */
+ a2dp_command_t command; /* Current command for a2dp_thread */
+ pthread_t thread;
+ pthread_mutex_t mutex;
+ pthread_cond_t thread_wait;
+ pthread_cond_t client_wait;
sbc_capabilities_t sbc_capabilities;
sbc_t sbc; /* Codec data */
- int sbc_initialized; /* Keep track if the encoder is initialized */
int frame_duration; /* length of an SBC frame in microseconds */
int codesize; /* SBC codesize */
int samples; /* Number of encoded samples */
@@ -128,6 +156,7 @@ static int audioservice_send(struct bluetooth_data *data, const bt_audio_msg_hea
static int audioservice_expect(struct bluetooth_data *data, bt_audio_msg_header_t *outmsg,
int expected_type);
static int bluetooth_a2dp_hw_params(struct bluetooth_data *data);
+static void set_state(struct bluetooth_data *data, a2dp_state_t state);
static void bluetooth_close(struct bluetooth_data *data)
@@ -143,11 +172,7 @@ static void bluetooth_close(struct bluetooth_data *data)
data->stream.fd = -1;
}
- if (data->sbc_initialized)
- sbc_finish(&data->sbc);
-
- data->sbc_initialized = 0;
- data->configured = 0;
+ data->state = A2DP_STATE_NONE;
}
static int bluetooth_start(struct bluetooth_data *data)
@@ -159,33 +184,37 @@ static int bluetooth_start(struct bluetooth_data *data)
struct bt_streamfd_ind *streamfd_ind = (void*) buf;
int opt_name, err, bytes;
+ DBG("bluetooth_start");
+ data->state = A2DP_STATE_STARTING;
/* send start */
memset(start_req, 0, BT_AUDIO_IPC_PACKET_SIZE);
start_req->h.msg_type = BT_STREAMSTART_REQ;
err = audioservice_send(data, &start_req->h);
if (err < 0)
- return err;
+ goto error;
err = audioservice_expect(data, &rsp_hdr->msg_h, BT_STREAMSTART_RSP);
if (err < 0)
- return err;
+ goto error;
if (rsp_hdr->posix_errno != 0) {
ERR("BT_START failed : %s(%d)",
strerror(rsp_hdr->posix_errno),
rsp_hdr->posix_errno);
- return -rsp_hdr->posix_errno;
+ err = -rsp_hdr->posix_errno;
+ goto error;
}
err = audioservice_expect(data, &streamfd_ind->h, BT_STREAMFD_IND);
if (err < 0)
- return err;
+ goto error;
data->stream.fd = bt_audio_service_get_data_fd(data->server.fd);
if (data->stream.fd < 0) {
ERR("bt_audio_service_get_data_fd failed, errno: %d", errno);
- return -errno;
+ err = -errno;
+ goto error;
}
data->stream.events = POLLOUT;
@@ -202,7 +231,14 @@ static int bluetooth_start(struct bluetooth_data *data)
data->frame_count = 0;
data->next_write = 0;
+ set_state(data, A2DP_STATE_STARTED);
return 0;
+
+error:
+ /* set state to A2DP_STATE_INITIALIZED to force reconfiguration */
+ if (data->state == A2DP_STATE_STARTING)
+ set_state(data, A2DP_STATE_INITIALIZED);
+ return err;
}
static int bluetooth_stop(struct bluetooth_data *data)
@@ -212,6 +248,9 @@ static int bluetooth_stop(struct bluetooth_data *data)
bt_audio_rsp_msg_header_t *rsp_hdr = (void*) buf;
int err;
+ DBG("bluetooth_stop");
+
+ data->state = A2DP_STATE_STOPPING;
if (data->stream.fd >= 0) {
close(data->stream.fd);
data->stream.fd = -1;
@@ -223,20 +262,24 @@ static int bluetooth_stop(struct bluetooth_data *data)
err = audioservice_send(data, &stop_req->h);
if (err < 0)
- return err;
+ goto error;
err = audioservice_expect(data, &rsp_hdr->msg_h, BT_STREAMSTOP_RSP);
if (err < 0)
- return err;
+ goto error;
if (rsp_hdr->posix_errno != 0) {
ERR("BT_STREAMSTOP failed : %s(%d)",
strerror(rsp_hdr->posix_errno),
rsp_hdr->posix_errno);
- return -rsp_hdr->posix_errno;
+ err = -rsp_hdr->posix_errno;
+ goto error;
}
- return 0;
+error:
+ if (data->state == A2DP_STATE_STOPPING)
+ set_state(data, A2DP_STATE_CONFIGURED);
+ return err;
}
static uint8_t default_bitpool(uint8_t freq, uint8_t mode)
@@ -358,11 +401,7 @@ static void bluetooth_a2dp_setup(struct bluetooth_data *data)
{
sbc_capabilities_t active_capabilities = data->sbc_capabilities;
- if (data->sbc_initialized)
- sbc_reinit(&data->sbc, 0);
- else
- sbc_init(&data->sbc, 0);
- data->sbc_initialized = 1;
+ sbc_reinit(&data->sbc, 0);
if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_16000)
data->sbc.frequency = SBC_FREQ_16000;
@@ -714,6 +753,7 @@ static int bluetooth_init(struct bluetooth_data *data)
data->server.fd = sk;
data->server.events = POLLIN;
+ data->state = A2DP_STATE_INITIALIZED;
return 0;
}
@@ -728,6 +768,8 @@ static int bluetooth_configure(struct bluetooth_data *data)
DBG("bluetooth_configure");
+ data->state = A2DP_STATE_CONFIGURING;
+
memset(getcaps_req, 0, BT_AUDIO_IPC_PACKET_SIZE);
getcaps_req->h.msg_type = BT_GETCAPABILITIES_REQ;
getcaps_req->flags = 0;
@@ -738,19 +780,20 @@ static int bluetooth_configure(struct bluetooth_data *data)
err = audioservice_send(data, &getcaps_req->h);
if (err < 0) {
ERR("audioservice_send failed for BT_GETCAPABILITIES_REQ\n");
- return err;
+ goto error;
}
err = audioservice_expect(data, &rsp_hdr->msg_h, BT_GETCAPABILITIES_RSP);
if (err < 0) {
ERR("audioservice_expect failed for BT_GETCAPABILITIES_RSP\n");
- return err;
+ goto error;
}
if (rsp_hdr->posix_errno != 0) {
ERR("BT_GETCAPABILITIES failed : %s(%d)",
strerror(rsp_hdr->posix_errno),
rsp_hdr->posix_errno);
- return -rsp_hdr->posix_errno;
+ err = -rsp_hdr->posix_errno;
+ goto error;
}
if (getcaps_rsp->transport == BT_CAPABILITIES_TRANSPORT_A2DP)
@@ -759,34 +802,155 @@ static int bluetooth_configure(struct bluetooth_data *data)
err = bluetooth_a2dp_hw_params(data);
if (err < 0) {
ERR("bluetooth_a2dp_hw_params failed err: %d", err);
- return err;
+ goto error;
}
- data->configured = 1;
+ set_state(data, A2DP_STATE_CONFIGURED);
return 0;
+
+error:
+ if (data->state == A2DP_STATE_CONFIGURING)
+ set_state(data, A2DP_STATE_INITIALIZED);
+ return err;
+}
+
+static void set_state(struct bluetooth_data *data, a2dp_state_t state)
+{
+ pthread_mutex_lock(&data->mutex);
+ data->state = state;
+ pthread_cond_signal(&data->client_wait);
+ pthread_mutex_unlock(&data->mutex);
+}
+
+static void set_command(struct bluetooth_data *data, a2dp_command_t command)
+{
+ VDBG("set_command %d\n", command);
+ pthread_mutex_lock(&data->mutex);
+ data->command = command;
+ pthread_cond_signal(&data->thread_wait);
+ pthread_mutex_unlock(&data->mutex);
+}
+
+static int wait_for_start(struct bluetooth_data *data, int timeout)
+{
+ a2dp_state_t state = data->state;
+ struct timespec ts;
+ uint64_t begin, end;
+ int err = 0;
+
+ begin = get_microseconds();
+ end = begin + (timeout * 1000);
+ ts.tv_sec = end / (1000 * 1000);
+ ts.tv_nsec = (end % (1000 * 1000)) * 1000;
+
+ while (state != A2DP_STATE_STARTED && !err) {
+ if (state == A2DP_STATE_NONE)
+ return -ENODEV;
+ else if (state == A2DP_STATE_INITIALIZED)
+ set_command(data, A2DP_CMD_CONFIGURE);
+ else if (state == A2DP_STATE_CONFIGURED)
+ set_command(data, A2DP_CMD_START);
+
+ pthread_mutex_lock(&data->mutex);
+ while ((err = pthread_cond_timedwait(&data->client_wait, &data->mutex, &ts))
+ == EINTR) ;
+ state = data->state;
+ pthread_mutex_unlock(&data->mutex);
+ }
+
+#ifdef ENABLE_TIMING
+ end = get_microseconds();
+ print_time("wait_for_start", begin, end);
+#endif
+
+ /* pthread_cond_timedwait returns positive errors */
+ return -err;
+}
+
+static void* a2dp_thread(void *d)
+{
+ struct bluetooth_data* data = (struct bluetooth_data*)d;
+
+ DBG("a2dp_thread started");
+ prctl(PR_SET_NAME, "a2dp_thread", 0, 0, 0);
+
+ while (1)
+ {
+ a2dp_command_t command;
+
+ pthread_mutex_lock(&data->mutex);
+ pthread_cond_wait(&data->thread_wait, &data->mutex);
+ command = data->command;
+ pthread_mutex_unlock(&data->mutex);
+
+ if (data->state == A2DP_STATE_NONE && command != A2DP_CMD_QUIT)
+ bluetooth_init(data);
+
+ switch (command) {
+ case A2DP_CMD_CONFIGURE:
+ if (data->state != A2DP_STATE_INITIALIZED)
+ break;
+ bluetooth_configure(data);
+ break;
+
+ case A2DP_CMD_START:
+ if (data->state != A2DP_STATE_CONFIGURED)
+ break;
+ bluetooth_start(data);
+ break;
+
+ case A2DP_CMD_STOP:
+ if (data->state != A2DP_STATE_STARTED)
+ break;
+ bluetooth_stop(data);
+ break;
+
+ case A2DP_CMD_QUIT:
+ bluetooth_close(data);
+ sbc_finish(&data->sbc);
+ free(data);
+ goto done;
+
+ default:
+ break;
+ }
+ }
+
+done:
+ DBG("a2dp_thread finished");
+ return NULL;
}
int a2dp_init(int rate, int channels, a2dpData* dataPtr)
{
+ struct bluetooth_data* data;
+ pthread_attr_t attr;
int err;
DBG("a2dp_init rate: %d channels: %d", rate, channels);
*dataPtr = NULL;
- struct bluetooth_data* data = malloc(sizeof(struct bluetooth_data));
+ data = malloc(sizeof(struct bluetooth_data));
if (!data)
return -1;
memset(data, 0, sizeof(struct bluetooth_data));
data->server.fd = -1;
data->stream.fd = -1;
+ data->state = A2DP_STATE_NONE;
strncpy(data->address, "00:00:00:00:00:00", 18);
data->rate = rate;
data->channels = channels;
- err = bluetooth_init(data);
- if (err < 0)
- goto error;
+ sbc_init(&data->sbc, 0);
+
+ pthread_mutex_init(&data->mutex, NULL);
+ pthread_cond_init(&data->thread_wait, NULL);
+ pthread_cond_init(&data->client_wait, NULL);
+
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ pthread_create(&data->thread, &attr, a2dp_thread, data);
*dataPtr = data;
return 0;
@@ -804,7 +968,7 @@ void a2dp_set_sink(a2dpData d, const char* address)
if (strncmp(data->address, address, 18)) {
strncpy(data->address, address, 18);
// force reconfiguration
- data->configured = 0;
+ set_command(data, A2DP_CMD_CONFIGURE);
}
}
@@ -824,30 +988,9 @@ int a2dp_write(a2dpData d, const void* buffer, int count)
begin = get_microseconds();
#endif
- if (data->server.fd == -1) {
- err = bluetooth_init(data);
- if (err < 0)
- return err;
- }
-
-configure:
- if (!data->configured) {
- err = bluetooth_configure(data);
- if (err < 0)
- return err;
- did_configure = 1;
- }
-
- if (data->stream.fd == -1) {
- err = bluetooth_start(data);
- if (err < 0) {
- ERR("bluetooth_start failed err: %d", err);
- data->configured = 0;
- if (!did_configure)
- goto configure;
- return err;
- }
- }
+ err = wait_for_start(data, WRITE_TIMEOUT);
+ if (err < 0)
+ return err;
codesize = data->codesize;
@@ -901,13 +1044,14 @@ int a2dp_stop(a2dpData d)
DBG("a2dp_stop\n");
if (!data)
return 0;
-
- return bluetooth_stop(data);
+
+ set_command(data, A2DP_CMD_STOP);
+ return 0;
}
void a2dp_cleanup(a2dpData d)
{
struct bluetooth_data* data = (struct bluetooth_data*)d;
- bluetooth_close(data);
- free(data);
+ DBG("a2dp_cleanup\n");
+ set_command(data, A2DP_CMD_QUIT);
}