#include <stdint.h>
#include <sys/socket.h>
#include <sys/un.h>
-#include <time.h>
-#include <sys/time.h>
#include <signal.h>
#include <limits.h>
#include <fcntl.h>
#define LOG_TAG "A2DP"
#include <utils/Log.h>
-// #define ENABLE_DEBUG
+/* #define ENABLE_DEBUG */
#define BUFFER_SIZE 2048
#define SNDERR LOGE
-struct bluetooth_a2dp {
+/* Number of milliseconds worth of audio to buffer in our the data->stream.fd socket */
+#define SOCK_BUFFER_MS 100
+
+struct bluetooth_data {
+ int link_mtu; /* MTU for selected transport channel */
+ struct pollfd stream; /* Audio stream filedescriptor */
+ struct pollfd server; /* Audio daemon filedescriptor */
+
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 */
uint8_t buffer[BUFFER_SIZE]; /* Codec transfer buffer */
int nsamples; /* Cumulative number of codec samples */
uint16_t seq_num; /* Cumulative packet sequence */
int frame_count; /* Current frames in buffer*/
-};
+ int started;
+ char address[20];
+ int rate;
+ int channels;
-struct bluetooth_data {
- volatile long hw_ptr;
- int transport; /* chosen transport SCO or AD2P */
- int link_mtu; /* MTU for selected transport channel */
- volatile struct pollfd stream; /* Audio stream filedescriptor */
- struct pollfd server; /* Audio daemon filedescriptor */
- uint8_t buffer[BUFFER_SIZE]; /* Encoded transfer buffer */
- int count; /* Transfer buffer counter */
- struct bluetooth_a2dp a2dp; /* A2DP data */
+ /* used for pacing our writes to the output socket */
+ struct timeval last_write;
+ unsigned long last_duration;
- sig_atomic_t reset; /* Request XRUN handling */
-
- char address[20];
+ /* true if we already set the buffer size on the data->stream.fd socket */
+ int adjusted_sock_buffer;
};
-
static int audioservice_send(int sk, const bt_audio_msg_header_t *msg);
static int audioservice_expect(int sk, bt_audio_msg_header_t *outmsg,
int expected_type);
+static int bluetooth_a2dp_hw_params(struct bluetooth_data *data);
static void bluetooth_exit(struct bluetooth_data *data)
{
- struct bluetooth_a2dp *a2dp = &data->a2dp;
-
if (data->server.fd >= 0)
bt_audio_service_close(data->server.fd);
if (data->stream.fd >= 0)
close(data->stream.fd);
- if (a2dp->sbc_initialized)
- sbc_finish(&a2dp->sbc);
+ if (data->sbc_initialized)
+ sbc_finish(&data->sbc);
}
-static int bluetooth_prepare(struct bluetooth_data *data)
+static int bluetooth_start(struct bluetooth_data *data)
{
char c = 'w';
char buf[BT_AUDIO_IPC_PACKET_SIZE];
bt_audio_rsp_msg_header_t *rsp_hdr = (void*) buf;
struct bt_streamfd_ind *streamfd_ind = (void*) buf;
int opt_name, err;
- struct timeval t = { 100, 0 };
- int buffer;
-
- data->reset = 0;
- data->hw_ptr = 0;
+ int retry = 0;
+retry:
/* send start */
memset(start_req, 0, BT_AUDIO_IPC_PACKET_SIZE);
start_req->h.msg_type = BT_STREAMSTART_REQ;
SNDERR("BT_START failed : %s(%d)",
strerror(rsp_hdr->posix_errno),
rsp_hdr->posix_errno);
+
+ /* if the connection dropped, we may need to reset the configuration */
+ if (!retry) {
+ retry = 1;
+ if (bluetooth_a2dp_hw_params(data) == 0)
+ goto retry;
+ }
+
return -rsp_hdr->posix_errno;
}
if (err < 0)
return err;
- if (data->stream.fd >= 0)
+ if (data->stream.fd >= 0) {
close(data->stream.fd);
+ data->stream.fd = -1;
+ data->adjusted_sock_buffer = 0;
+ }
data->stream.fd = bt_audio_service_get_data_fd(data->server.fd);
if (data->stream.fd < 0) {
return -errno;
}
+ data->stream.events = POLLOUT;
- if (setsockopt(data->stream.fd, SOL_SOCKET, SO_SNDTIMEO, &t, sizeof(t)) < 0)
- return -errno;
+ return 0;
+}
- /* disable buffering to reduce latency when pausing or changing volume */
- buffer = 0;
- if (setsockopt(data->stream.fd, SOL_SOCKET, SO_SNDBUF, &buffer, sizeof(buffer)) < 0)
- return -errno;
+static int bluetooth_stop(struct bluetooth_data *data)
+{
+ char buf[BT_AUDIO_IPC_PACKET_SIZE];
+ struct bt_streamstop_req *stop_req = (void*) buf;
+ bt_audio_rsp_msg_header_t *rsp_hdr = (void*) buf;
+ int err;
+
+ data->started = 0;
+
+ if (data->stream.fd >= 0) {
+ close(data->stream.fd);
+ data->stream.fd = 0;
+ }
+
+ /* send stop request */
+ memset(stop_req, 0, BT_AUDIO_IPC_PACKET_SIZE);
+ stop_req->h.msg_type = BT_STREAMSTOP_REQ;
+
+ err = audioservice_send(data->server.fd, &stop_req->h);
+ if (err < 0)
+ return err;
+
+ err = audioservice_expect(data->server.fd, &rsp_hdr->msg_h,
+ BT_STREAMSTOP_RSP);
+ if (err < 0)
+ return err;
+
+ if (rsp_hdr->posix_errno != 0) {
+ SNDERR("BT_STREAMSTOP failed : %s(%d)",
+ strerror(rsp_hdr->posix_errno),
+ rsp_hdr->posix_errno);
+ return -rsp_hdr->posix_errno;
+ }
return 0;
}
}
}
-static int bluetooth_a2dp_init(struct bluetooth_data *data, int rate, int channels)
+static int bluetooth_a2dp_init(struct bluetooth_data *data)
{
- sbc_capabilities_t *cap = &data->a2dp.sbc_capabilities;
+ sbc_capabilities_t *cap = &data->sbc_capabilities;
unsigned int max_bitpool, min_bitpool;
int dir;
- switch (rate) {
+ switch (data->rate) {
case 48000:
cap->frequency = BT_SBC_SAMPLING_FREQ_48000;
break;
cap->frequency = BT_SBC_SAMPLING_FREQ_16000;
break;
default:
- DBG("Rate %d not supported", rate);
+ DBG("Rate %d not supported", data->rate);
return -1;
}
- if (channels == 2) {
+ if (data->channels == 2) {
if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO)
cap->channel_mode = BT_A2DP_CHANNEL_MODE_JOINT_STEREO;
else if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO)
cap->min_bitpool = min_bitpool;
cap->max_bitpool = max_bitpool;
-DBG("bluetooth_a2dp_init bottom:\n channel_mode: %d\n frequency: %d\n allocation_method: %d\n subbands: %d\n block_length: %d\n min_bitpool: %d\n max_bitpool: %d\n ",
- cap->channel_mode, cap->frequency, cap->allocation_method, cap->subbands,
- cap->block_length, cap->min_bitpool, cap->max_bitpool);
+ DBG("bluetooth_a2dp_init bottom:\n channel_mode: %d\n frequency: %d\n allocation_method: %d\n subbands: %d\n block_length: %d\n min_bitpool: %d\n max_bitpool: %d\n ",
+ cap->channel_mode, cap->frequency, cap->allocation_method, cap->subbands,
+ cap->block_length, cap->min_bitpool, cap->max_bitpool);
return 0;
}
-static void bluetooth_a2dp_setup(struct bluetooth_a2dp *a2dp)
+static void bluetooth_a2dp_setup(struct bluetooth_data *data)
{
- sbc_capabilities_t active_capabilities = a2dp->sbc_capabilities;
+ sbc_capabilities_t active_capabilities = data->sbc_capabilities;
- if (a2dp->sbc_initialized)
- sbc_reinit(&a2dp->sbc, 0);
+ if (data->sbc_initialized)
+ sbc_reinit(&data->sbc, 0);
else
- sbc_init(&a2dp->sbc, 0);
- a2dp->sbc_initialized = 1;
+ sbc_init(&data->sbc, 0);
+ data->sbc_initialized = 1;
if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_16000)
- a2dp->sbc.frequency = SBC_FREQ_16000;
+ data->sbc.frequency = SBC_FREQ_16000;
if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_32000)
- a2dp->sbc.frequency = SBC_FREQ_32000;
+ data->sbc.frequency = SBC_FREQ_32000;
if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_44100)
- a2dp->sbc.frequency = SBC_FREQ_44100;
+ data->sbc.frequency = SBC_FREQ_44100;
if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_48000)
- a2dp->sbc.frequency = SBC_FREQ_48000;
+ data->sbc.frequency = SBC_FREQ_48000;
if (active_capabilities.channel_mode & BT_A2DP_CHANNEL_MODE_MONO)
- a2dp->sbc.mode = SBC_MODE_MONO;
+ data->sbc.mode = SBC_MODE_MONO;
if (active_capabilities.channel_mode & BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL)
- a2dp->sbc.mode = SBC_MODE_DUAL_CHANNEL;
+ data->sbc.mode = SBC_MODE_DUAL_CHANNEL;
if (active_capabilities.channel_mode & BT_A2DP_CHANNEL_MODE_STEREO)
- a2dp->sbc.mode = SBC_MODE_STEREO;
+ data->sbc.mode = SBC_MODE_STEREO;
if (active_capabilities.channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO)
- a2dp->sbc.mode = SBC_MODE_JOINT_STEREO;
+ data->sbc.mode = SBC_MODE_JOINT_STEREO;
- a2dp->sbc.allocation = active_capabilities.allocation_method
+ data->sbc.allocation = active_capabilities.allocation_method
== BT_A2DP_ALLOCATION_SNR ? SBC_AM_SNR
: SBC_AM_LOUDNESS;
switch (active_capabilities.subbands) {
case BT_A2DP_SUBBANDS_4:
- a2dp->sbc.subbands = SBC_SB_4;
+ data->sbc.subbands = SBC_SB_4;
break;
case BT_A2DP_SUBBANDS_8:
- a2dp->sbc.subbands = SBC_SB_8;
+ data->sbc.subbands = SBC_SB_8;
break;
}
switch (active_capabilities.block_length) {
case BT_A2DP_BLOCK_LENGTH_4:
- a2dp->sbc.blocks = SBC_BLK_4;
+ data->sbc.blocks = SBC_BLK_4;
break;
case BT_A2DP_BLOCK_LENGTH_8:
- a2dp->sbc.blocks = SBC_BLK_8;
+ data->sbc.blocks = SBC_BLK_8;
break;
case BT_A2DP_BLOCK_LENGTH_12:
- a2dp->sbc.blocks = SBC_BLK_12;
+ data->sbc.blocks = SBC_BLK_12;
break;
case BT_A2DP_BLOCK_LENGTH_16:
- a2dp->sbc.blocks = SBC_BLK_16;
+ data->sbc.blocks = SBC_BLK_16;
break;
}
- a2dp->sbc.bitpool = active_capabilities.max_bitpool;
- a2dp->codesize = sbc_get_codesize(&a2dp->sbc);
- a2dp->count = sizeof(struct rtp_header) + sizeof(struct rtp_payload);
+ data->sbc.bitpool = active_capabilities.max_bitpool;
+ data->codesize = sbc_get_codesize(&data->sbc);
+ data->frame_duration = sbc_get_frame_duration(&data->sbc);
+ data->count = sizeof(struct rtp_header) + sizeof(struct rtp_payload);
}
-static int bluetooth_a2dp_hw_params(struct bluetooth_data *data, int rate, int channels)
+static int bluetooth_a2dp_hw_params(struct bluetooth_data *data)
{
- struct bluetooth_a2dp *a2dp = &data->a2dp;
char buf[BT_AUDIO_IPC_PACKET_SIZE];
bt_audio_rsp_msg_header_t *rsp_hdr = (void*) buf;
struct bt_setconfiguration_req *setconf_req = (void*) buf;
struct bt_setconfiguration_rsp *setconf_rsp = (void*) buf;
int err;
- err = bluetooth_a2dp_init(data, rate, channels);
+ err = bluetooth_a2dp_init(data);
if (err < 0)
return err;
setconf_req->h.msg_type = BT_SETCONFIGURATION_REQ;
strncpy(setconf_req->device, data->address, 18);
setconf_req->transport = BT_CAPABILITIES_TRANSPORT_A2DP;
- setconf_req->sbc_capabilities = a2dp->sbc_capabilities;
+ setconf_req->sbc_capabilities = data->sbc_capabilities;
setconf_req->access_mode = BT_CAPABILITIES_ACCESS_MODE_WRITE;
err = audioservice_send(data->server.fd, &setconf_req->h);
return -rsp_hdr->posix_errno;
}
- data->transport = setconf_rsp->transport;
data->link_mtu = setconf_rsp->link_mtu;
/* Setup SBC encoder now we agree on parameters */
- bluetooth_a2dp_setup(a2dp);
+ bluetooth_a2dp_setup(data);
DBG("\tallocation=%u\n\tsubbands=%u\n\tblocks=%u\n\tbitpool=%u\n",
- a2dp->sbc.allocation, a2dp->sbc.subbands, a2dp->sbc.blocks,
- a2dp->sbc.bitpool);
+ data->sbc.allocation, data->sbc.subbands, data->sbc.blocks,
+ data->sbc.bitpool);
return 0;
}
-
-
-static int avdtp_write(struct bluetooth_data *data)
+static int avdtp_write(struct bluetooth_data *data, unsigned long duration)
{
int ret = 0;
struct rtp_header *header;
struct rtp_payload *payload;
- struct bluetooth_a2dp *a2dp = &data->a2dp;
+ unsigned long delta;
+ struct timeval now;
+ int microseconds, bytes;
- header = (void *) a2dp->buffer;
- payload = (void *) (a2dp->buffer + sizeof(*header));
+ header = (struct rtp_header *)data->buffer;
+ payload = (struct rtp_payload *)(data->buffer + sizeof(*header));
- memset(a2dp->buffer, 0, sizeof(*header) + sizeof(*payload));
+ memset(data->buffer, 0, sizeof(*header) + sizeof(*payload));
- payload->frame_count = a2dp->frame_count;
+ payload->frame_count = data->frame_count;
header->v = 2;
header->pt = 1;
- header->sequence_number = htons(a2dp->seq_num);
- header->timestamp = htonl(a2dp->nsamples);
+ header->sequence_number = htons(data->seq_num);
+ header->timestamp = htonl(data->nsamples);
header->ssrc = htonl(1);
-retry:
- ret = send(data->stream.fd, a2dp->buffer, a2dp->count, MSG_DONTWAIT);
- if (ret < 0) {
- DBG("send returned %d errno %s.", ret, strerror(errno));
- ret = -errno;
- if (ret == -EAGAIN) {
- struct timespec tv;
- DBG("retry");
- tv.tv_sec = 0;
- tv.tv_nsec = 100 * 1000;
- nanosleep(&tv, NULL);
- goto retry;
+ data->stream.revents = 0;
+ ret = poll(&data->stream, 1, -1);
+ if (ret == 1 && data->stream.revents == POLLOUT) {
+ gettimeofday(&now, NULL);
+ if (data->last_write.tv_sec || data->last_write.tv_usec) {
+ if (now.tv_usec > data->last_write.tv_usec)
+ delta = now.tv_usec - data->last_write.tv_usec;
+ else
+ delta = (1000000 - data->last_write.tv_usec) + now.tv_usec;
+
+ if (duration > delta) {
+ DBG("duration: %ld delta: %ld, delay %ld us", duration, delta, duration - delta);
+ usleep(duration - delta);
+ }
}
- }
-
- /* 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++;
-
- return ret;
-}
-
-static long bluetooth_a2dp_write(struct bluetooth_data *data,
- const char* buffer,
- long size)
-{
- struct bluetooth_a2dp *a2dp = &data->a2dp;
- long ret = 0;
- long frames_to_read, frames_left = size;
- int encoded, written;
- const char *buff;
-
- while (frames_left > 0) {
-
- if ((data->count + frames_left) <= a2dp->codesize)
- frames_to_read = frames_left;
- else
- frames_to_read = a2dp->codesize - data->count;
- buff = buffer + ret;
-
- memcpy(data->buffer + data->count, buff, frames_to_read);
- /* Remember we have some frames in the pipe now */
- data->count += frames_to_read;
- if (data->count != a2dp->codesize) {
- ret = frames_to_read;
- goto done;
- }
-
- /* Enough data to encode (sbc wants 1k blocks) */
- encoded = sbc_encode(&(a2dp->sbc), data->buffer, a2dp->codesize,
- a2dp->buffer + a2dp->count,
- sizeof(a2dp->buffer) - a2dp->count,
- &written);
- if (encoded <= 0) {
- DBG("Encoding error %d", encoded);
- goto done;
- }
-
- data->count -= encoded;
- a2dp->count += written;
- a2dp->frame_count++;
- a2dp->samples += encoded;
- a2dp->nsamples += encoded;
-
- /* No space left for another frame then send */
- if (a2dp->count + written >= data->link_mtu) {
- DBG("sending packet %d, count %d, link_mtu %u",
- a2dp->seq_num, a2dp->count,
- data->link_mtu);
- avdtp_write(data);
+ data->last_write = now;
+
+ ret = send(data->stream.fd, data->buffer, data->count, 0);
+ if (ret < 0) {
+ DBG("send returned %d errno %s.", ret, strerror(errno));
+ ret = -errno;
}
+ } else {
+ ret = -errno;
+ }
- ret += frames_to_read;
- frames_left -= frames_to_read;
+ if (!data->adjusted_sock_buffer) {
+ /* microseconds: number of microseconds of audio for this write */
+ microseconds = data->frame_duration * data->frame_count;
+ /* ret: number of bytes written */
+ /* bytes: number of bytes corresponding to SOCK_BUFFER_MS milliseconds of audio playback */
+ bytes = (ret * 1000 * SOCK_BUFFER_MS) / microseconds;
+
+ DBG("microseconds: %d, ret: %d, bytes: %d\n", microseconds, ret, bytes);
+ setsockopt(data->stream.fd, SOL_SOCKET, SO_SNDBUF, &bytes, sizeof(bytes));
+ data->adjusted_sock_buffer = 1;
}
- /* note: some ALSA apps will get confused otherwise */
- if (ret > size)
- ret = size;
+ /* Reset buffer of data to send */
+ data->count = sizeof(struct rtp_header) + sizeof(struct rtp_payload);
+ data->frame_count = 0;
+ data->samples = 0;
+ data->seq_num++;
-done:
- DBG("returning %ld", ret);
return ret;
}
-
static int audioservice_send(int sk, const bt_audio_msg_header_t *msg)
{
int err;
data->server.fd = -1;
data->stream.fd = -1;
+ data->adjusted_sock_buffer = 0;
sk = bt_audio_service_open();
if (sk <= 0) {
+ SNDERR("bt_audio_service_open failed\n");
err = -errno;
goto failed;
}
getcaps_req->transport = BT_CAPABILITIES_TRANSPORT_A2DP;
err = audioservice_send(data->server.fd, &getcaps_req->h);
- if (err < 0)
+ if (err < 0) {
+ SNDERR("audioservice_send failed for BT_GETCAPABILITIES_REQ\n");
goto failed;
+ }
err = audioservice_expect(data->server.fd, &rsp_hdr->msg_h, BT_GETCAPABILITIES_RSP);
- if (err < 0)
+ if (err < 0) {
+ SNDERR("audioservice_expect failed for BT_GETCAPABILITIES_RSP\n");
goto failed;
-
+ }
if (rsp_hdr->posix_errno != 0) {
SNDERR("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 failed;
}
- data->transport = getcaps_rsp->transport;
-
- if (getcaps_rsp->transport == BT_CAPABILITIES_TRANSPORT_A2DP) {
- data->a2dp.sbc_capabilities = getcaps_rsp->sbc_capabilities;
- }
+ if (getcaps_rsp->transport == BT_CAPABILITIES_TRANSPORT_A2DP)
+ data->sbc_capabilities = getcaps_rsp->sbc_capabilities;
return 0;
failed:
+ SNDERR("bluetooth_init failed, err: %d\n", err);
bt_audio_service_close(sk);
+ data->server.fd = -1;
return err;
}
{
int err;
- *dataPtr = NULL;
- struct bluetooth_data* data = malloc(sizeof(struct bluetooth_data));
- if (!data)
- return -1;
+ DBG("a2dp_init");
+ *dataPtr = NULL;
+ struct bluetooth_data* data = malloc(sizeof(struct bluetooth_data));
+ if (!data)
+ return -1;
strncpy(data->address, address, 18);
err = bluetooth_init(data);
if (err < 0)
goto error;
-
- err = bluetooth_a2dp_hw_params(data, rate, channels);
- if (err < 0) {
- printf("bluetooth_a2dp_hw_params failed");
- goto error;
- }
-
- err = bluetooth_prepare(data);
+
+ data->rate = rate;
+ data->channels = channels;
+
+ err = bluetooth_a2dp_hw_params(data);
if (err < 0) {
- printf("bluetooth_prepare failed");
+ SNDERR("bluetooth_a2dp_hw_params failed");
goto error;
}
- *dataPtr = data;
+ *dataPtr = data;
return 0;
error:
int a2dp_write(a2dpData d, const void* buffer, int count)
{
- struct bluetooth_data* data = (struct bluetooth_data*)d;
- return bluetooth_a2dp_write(data, buffer, count);
+ struct bluetooth_data* data = (struct bluetooth_data*)d;
+ const uint8_t* src = buffer;
+ int codesize = data->codesize;
+ long ret = 0;
+ long frames_left = count;
+ int encoded, written;
+ const char *buff;
+ unsigned long duration = 0;
+
+ if (!data->started) {
+ ret = bluetooth_start(data);
+ if (ret < 0) {
+ SNDERR("bluetooth_start failed");
+ return ret;
+ }
+ data->started = 1;
+ }
+
+ while (frames_left >= codesize) {
+ /* Enough data to encode (sbc wants 1k blocks) */
+ encoded = sbc_encode(&(data->sbc), src, codesize,
+ data->buffer + data->count,
+ sizeof(data->buffer) - data->count,
+ &written);
+ if (encoded <= 0) {
+ DBG("Encoding error %d", encoded);
+ goto done;
+ }
+ DBG("sbc_encode returned %d, codesize: %d, written: %d\n", encoded, codesize, written);
+
+ src += encoded;
+ data->count += written;
+ data->frame_count++;
+ data->samples += encoded;
+ data->nsamples += encoded;
+ duration += data->frame_duration;
+
+ /* No space left for another frame then send */
+ if (data->count + written >= data->link_mtu) {
+ DBG("sending packet %d, count %d, link_mtu %u",
+ data->seq_num, data->count,
+ data->link_mtu);
+ avdtp_write(data, data->last_duration);
+ data->last_duration = duration;
+ duration = 0;
+ }
+
+ ret += encoded;
+ frames_left -= encoded;
+ }
+
+ if (frames_left > 0)
+ SNDERR("%ld bytes left at end of a2dp_write\n", frames_left);
+
+done:
+ DBG("returning %ld", ret);
+ return ret;
+}
+
+int a2dp_stop(a2dpData d)
+{
+ struct bluetooth_data* data = (struct bluetooth_data*)d;
+ DBG("a2dp_stop\n");
+ if (!data)
+ return 0;
+
+ return bluetooth_stop(data);
}
void a2dp_cleanup(a2dpData d)
{
- struct bluetooth_data* data = (struct bluetooth_data*)d;
+ struct bluetooth_data* data = (struct bluetooth_data*)d;
bluetooth_exit(data);
free(data);
}