[pulseaudio-discuss] [RFCv2 16/17] bluetooth: Handle mSBC-encoded streams for HFP
jprvita at gmail.com
jprvita at gmail.com
Mon Apr 15 14:53:31 PDT 2013
From: João Paulo Rechi Vita <jprvita at openbossa.org>
---
src/modules/bluetooth/bluetooth-util.c | 4 +-
src/modules/bluetooth/bluetooth-util.h | 4 +
src/modules/bluetooth/module-bluetooth-device.c | 327 +++++++++++++++++++++++-
3 files changed, 328 insertions(+), 7 deletions(-)
diff --git a/src/modules/bluetooth/bluetooth-util.c b/src/modules/bluetooth/bluetooth-util.c
index d72137b..eaa18ec 100644
--- a/src/modules/bluetooth/bluetooth-util.c
+++ b/src/modules/bluetooth/bluetooth-util.c
@@ -1330,8 +1330,8 @@ static void ofono_init(pa_bluetooth_discovery *y) {
pa_assert_se(m = dbus_message_new_method_call("org.ofono", "/", "org.ofono.HandsfreeAudioManager", "Register"));
- /* TODO: check available CODECs */
- codecs[ncodecs++] = 1; /* HFP_AUDIO_CVSD */
+ codecs[ncodecs++] = HFP_AUDIO_CODEC_CVSD;
+ codecs[ncodecs++] = HFP_AUDIO_CODEC_MSBC;
pa_assert_se(dbus_message_append_args(m,
DBUS_TYPE_OBJECT_PATH, &path,
diff --git a/src/modules/bluetooth/bluetooth-util.h b/src/modules/bluetooth/bluetooth-util.h
index e627e1d..0343a59 100644
--- a/src/modules/bluetooth/bluetooth-util.h
+++ b/src/modules/bluetooth/bluetooth-util.h
@@ -38,6 +38,10 @@
#define HFP_HS_UUID "0000111e-0000-1000-8000-00805f9b34fb"
#define HFP_AG_UUID "0000111f-0000-1000-8000-00805f9b34fb"
+#define HFP_AUDIO_CODEC_CVSD 0x01
+#define HFP_AUDIO_CODEC_MSBC 0x02
+
+
#define ADVANCED_AUDIO_UUID "0000110d-0000-1000-8000-00805f9b34fb"
#define A2DP_SOURCE_UUID "0000110a-0000-1000-8000-00805f9b34fb"
diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c
index d234f15..22880c6 100644
--- a/src/modules/bluetooth/module-bluetooth-device.c
+++ b/src/modules/bluetooth/module-bluetooth-device.c
@@ -115,11 +115,30 @@ struct a2dp_info {
uint8_t max_bitpool;
};
+struct msbc_parser {
+ int len;
+ uint8_t buffer[60];
+};
+
struct hsp_info {
pa_sink *sco_sink;
void (*sco_sink_set_volume)(pa_sink *s);
pa_source *sco_source;
void (*sco_source_set_volume)(pa_source *s);
+
+ pa_bool_t sbc_initialized; /* Keep track if the encoder is initialized */
+ sbc_t sbcenc; /* Encoder data */
+ uint8_t *ebuffer; /* Codec transfer buffer */
+ size_t ebuffer_size; /* Size of the buffer */
+ size_t ebuffer_start; /* start of encoding data */
+ size_t ebuffer_end; /* end of encoding data */
+
+ struct msbc_parser parser; /* mSBC parser for concatenating frames */
+ sbc_t sbcdec; /* Decoder data */
+
+ size_t msbc_frame_size;
+ size_t decoded_frame_size;
+
};
struct bluetooth_msg {
@@ -557,6 +576,97 @@ static int device_process_msg(pa_msgobject *obj, int code, void *data, int64_t o
return 0;
}
+static void msbc_parser_reset(struct msbc_parser *p) {
+ p->len = 0;
+}
+
+static int msbc_state_machine(struct msbc_parser *p, uint8_t byte) {
+ pa_assert(p->len < 60);
+
+ switch (p->len) {
+ case 0:
+ if (byte == 0x01)
+ goto copy;
+ return 0;
+ case 1:
+ if (byte == 0x08 || byte == 0x38 || byte == 0xC8 || byte == 0xF8)
+ goto copy;
+ break;
+ case 2:
+ if (byte == 0xAD)
+ goto copy;
+ break;
+ case 3:
+ if (byte == 0x00)
+ goto copy;
+ break;
+ case 4:
+ if (byte == 0x00)
+ goto copy;
+ break;
+ default:
+ goto copy;
+ }
+
+ p->len = 0;
+ return 0;
+
+copy:
+ p->buffer[p->len] = byte;
+ p->len ++;
+
+ return p->len;
+}
+
+static size_t msbc_parse(sbc_t *sbcdec, struct msbc_parser *p, uint8_t *data, int len, uint8_t *out, int outlen, int *bytes) {
+ size_t totalwritten = 0;
+ size_t written = 0;
+ int i;
+ *bytes = 0;
+
+ for (i = 0; i < len; i++) {
+ if (msbc_state_machine(p, data[i]) == 60) {
+ int decoded = 0;
+ decoded = sbc_decode(sbcdec, p->buffer + 2, p->len - 2 - 1, out, outlen, &written);
+ if (decoded > 0) {
+ totalwritten += written;
+ *bytes += decoded;
+ } else {
+ pa_log_debug("Error while decoding: %d\n", decoded);
+ }
+ msbc_parser_reset(p);
+ }
+ }
+ return totalwritten;
+}
+
+/* Run from IO thread */
+static void hsp_prepare_buffer(struct userdata *u) {
+
+ pa_assert(u);
+
+ /* Initialize sbc codec if not already done */
+ if (!u->hsp.sbc_initialized) {
+ sbc_init_msbc(&u->hsp.sbcenc, 0);
+ sbc_init_msbc(&u->hsp.sbcdec, 0);
+ u->hsp.msbc_frame_size = 2 + sbc_get_frame_length(&u->hsp.sbcenc) + 1;
+ u->hsp.decoded_frame_size = sbc_get_codesize(&u->hsp.sbcenc);
+ u->hsp.sbc_initialized = TRUE;
+ msbc_parser_reset(&u->hsp.parser);
+ }
+
+ /* Allocate a buffer for encoding, and a tmp buffer for sending */
+ if (u->hsp.ebuffer_size < u->hsp.msbc_frame_size) {
+ pa_xfree(u->hsp.ebuffer);
+ u->hsp.ebuffer_size = u->hsp.msbc_frame_size * 4; /* 5 * 48 = 10 * 24 = 4 * 60 */
+ u->hsp.ebuffer = pa_xmalloc(u->hsp.ebuffer_size);
+ u->hsp.ebuffer_start = 0;
+ u->hsp.ebuffer_end = 0;
+ }
+}
+
+static const char sntable[4] = { 0x08, 0x38, 0xC8, 0xF8 };
+
/* Run from IO thread */
static int hsp_process_render(struct userdata *u) {
int ret = 0;
@@ -622,6 +732,94 @@ static int hsp_process_render(struct userdata *u) {
}
/* Run from IO thread */
+static int hsp_process_render_msbc(struct userdata *u) {
+ int ret = 0;
+
+ pa_assert(u);
+ pa_assert(u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW);
+ pa_assert(u->sink);
+
+ hsp_prepare_buffer(u);
+
+ /* First, render some data */
+ if (!u->write_memchunk.memblock)
+ pa_sink_render_full(u->sink, u->hsp.decoded_frame_size, &u->write_memchunk);
+
+ for (;;) {
+ int l = 0;
+ pa_bool_t wrote = false;
+ const uint8_t *p;
+ size_t written = 0;
+ uint8_t *h2 = u->hsp.ebuffer + u->hsp.ebuffer_end;
+ static int sn = 0;
+
+ /* Now write that data to the socket. The socket is of type
+ * SEQPACKET, and we generated the data of the MTU size, so this
+ * should just work. */
+
+ p = (const uint8_t *) pa_memblock_acquire_chunk(&u->write_memchunk);
+ h2[0] = 0x01;
+ h2[1] = sntable[sn];
+ h2[59] = 0xff;
+ sn = (sn + 1) % 4;
+
+ pa_assert(u->hsp.ebuffer_end + u->hsp.msbc_frame_size <= u->hsp.ebuffer_size);
+ sbc_encode(&u->hsp.sbcenc, p, u->write_memchunk.length, ((uint8_t *)u->hsp.ebuffer)+u->hsp.ebuffer_end+2, u->hsp.ebuffer_size-u->hsp.ebuffer_end-2, (ssize_t *)&written);
+
+ written += 2 /* H2 */ + 1 /* 0xff */;
+ pa_assert(written == u->hsp.msbc_frame_size);
+ u->hsp.ebuffer_end += written;
+
+ /* Send MTU bytes of it, if there is more it will send later */
+ while (u->hsp.ebuffer_start + u->write_link_mtu <= u->hsp.ebuffer_end) {
+ l = pa_write(u->stream_fd, u->hsp.ebuffer + u->hsp.ebuffer_start, u->write_link_mtu, &u->stream_write_type);
+ wrote = true;
+ if (l <= 0) {
+ pa_log_debug("Error while writing: l %d, errno %d", l, errno);
+ break;
+ }
+ u->hsp.ebuffer_start += l;
+ if (u->hsp.ebuffer_start >= u->hsp.ebuffer_end)
+ u->hsp.ebuffer_start = u->hsp.ebuffer_end = 0;
+ }
+
+ pa_memblock_release(u->write_memchunk.memblock);
+
+ if (wrote && l < 0) {
+
+ if (errno == EINTR)
+ /* Retry right away if we got interrupted */
+ continue;
+
+ else if (errno == EAGAIN)
+ /* Hmm, apparently the socket was not writable, give up for now */
+ break;
+
+ pa_log_error("Failed to write data to SCO socket: %s", pa_cstrerror(errno));
+ ret = -1;
+ break;
+ }
+
+ if ((size_t) l != (size_t)u->write_link_mtu) {
+ pa_log_error("Wrote memory block to socket only partially! %llu written, wanted to write %llu.",
+ (unsigned long long) l,
+ (unsigned long long) u->write_link_mtu);
+ ret = -1;
+ break;
+ }
+
+ u->write_index += (uint64_t) u->write_memchunk.length;
+ pa_memblock_unref(u->write_memchunk.memblock);
+ pa_memchunk_reset(&u->write_memchunk);
+
+ ret = 1;
+ break;
+ }
+
+ return ret;
+}
+
+/* Run from IO thread */
static int hsp_process_push(struct userdata *u) {
int ret = 0;
pa_memchunk memchunk;
@@ -719,6 +917,102 @@ static int hsp_process_push(struct userdata *u) {
}
/* Run from IO thread */
+static int hsp_process_push_msbc(struct userdata *u) {
+ int ret = 0;
+ pa_memchunk memchunk;
+
+ pa_assert(u);
+ pa_assert(u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW);
+ pa_assert(u->source);
+ pa_assert(u->read_smoother);
+
+ u->read_block_size = u->hsp.decoded_frame_size;
+ memchunk.memblock = pa_memblock_new(u->core->mempool, u->read_block_size);
+ memchunk.index = memchunk.length = 0;
+
+ hsp_prepare_buffer(u);
+
+ for (;;) {
+ ssize_t l;
+ void *p;
+ struct msghdr m;
+ struct cmsghdr *cm;
+ uint8_t aux[1024];
+ struct iovec iov;
+ bool found_tstamp = false;
+ pa_usec_t tstamp;
+ int decoded = 0;
+ size_t written;
+ uint8_t *tmpbuf = pa_xmalloc(u->read_link_mtu);
+
+ memset(&m, 0, sizeof(m));
+ memset(&aux, 0, sizeof(aux));
+ memset(&iov, 0, sizeof(iov));
+
+ m.msg_iov = &iov;
+ m.msg_iovlen = 1;
+ m.msg_control = aux;
+ m.msg_controllen = sizeof(aux);
+
+ iov.iov_base = tmpbuf;
+ iov.iov_len = u->read_link_mtu;
+ l = recvmsg(u->stream_fd, &m, 0);
+ if (l <= 0) {
+
+ if (l < 0 && errno == EINTR)
+ /* Retry right away if we got interrupted */
+ continue;
+
+ else if (l < 0 && errno == EAGAIN)
+ /* Hmm, apparently the socket was not readable, give up for now. */
+ break;
+
+ pa_log_error("Failed to read data from SCO socket: %s", l < 0 ? pa_cstrerror(errno) : "EOF");
+ ret = -1;
+ break;
+ }
+
+ p = pa_memblock_acquire(memchunk.memblock);
+ written = msbc_parse(&u->hsp.sbcdec, &u->hsp.parser, tmpbuf, l, p, pa_memblock_get_length(memchunk.memblock), &decoded);
+ pa_memblock_release(memchunk.memblock);
+
+ pa_xfree(tmpbuf);
+
+ memchunk.length = (size_t) written;
+
+ u->read_index += (uint64_t) decoded;
+
+ for (cm = CMSG_FIRSTHDR(&m); cm; cm = CMSG_NXTHDR(&m, cm)) {
+ if (cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SO_TIMESTAMP) {
+ struct timeval *tv = (struct timeval*) CMSG_DATA(cm);
+ pa_rtclock_from_wallclock(tv);
+ tstamp = pa_timeval_load(tv);
+ found_tstamp = true;
+ break;
+ }
+ }
+
+ if (!found_tstamp) {
+ pa_log_warn("Couldn't find SO_TIMESTAMP data in auxiliary recvmsg() data!");
+ tstamp = pa_rtclock_now();
+ }
+
+ pa_smoother_put(u->read_smoother, tstamp, pa_bytes_to_usec(u->read_index, &u->sample_spec));
+ pa_smoother_resume(u->read_smoother, tstamp, true);
+
+ if (memchunk.length > 0)
+ pa_source_post(u->source, &memchunk);
+
+ ret = decoded;
+ break;
+ }
+
+ pa_memblock_unref(memchunk.memblock);
+
+ return ret;
+}
+
+/* Run from IO thread */
static void a2dp_prepare_buffer(struct userdata *u) {
size_t min_buffer_size = PA_MAX(u->read_link_mtu, u->write_link_mtu);
@@ -1040,7 +1334,18 @@ static void thread_func(void *userdata) {
int n_read;
if (u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW)
- n_read = hsp_process_push(u);
+ switch(u->transport->codec) {
+ case HFP_AUDIO_CODEC_CVSD:
+ n_read = hsp_process_push(u);
+ break;
+ case HFP_AUDIO_CODEC_MSBC:
+ n_read = hsp_process_push_msbc(u);
+ break;
+ default:
+ pa_log_error("Invalid codec for encoding %d", u->transport->codec);
+ n_read = -1;
+ }
+
else
n_read = a2dp_process_push(u);
@@ -1063,7 +1368,11 @@ static void thread_func(void *userdata) {
if (pollfd->revents & POLLOUT)
writable = true;
- if ((!u->source || !PA_SOURCE_IS_LINKED(u->source->thread_info.state)) && do_write <= 0 && writable) {
+ /* Force time based scheduling for outgoing packets */
+ /* This is necessary for mSBC because one pushed PCM frame != one sent mSBC frame */
+ if (u->transport->codec == 2 ||
+ ((!u->source || !PA_SOURCE_IS_LINKED(u->source->thread_info.state)) && do_write <= 0 && writable)
+ ) {
pa_usec_t time_passed;
pa_usec_t audio_sent;
@@ -1115,8 +1424,16 @@ static void thread_func(void *userdata) {
if ((n_written = a2dp_process_render(u)) < 0)
goto io_fail;
} else {
- if ((n_written = hsp_process_render(u)) < 0)
- goto io_fail;
+ if (u->transport->codec == 1) {
+ if ((n_written = hsp_process_render(u)) < 0)
+ goto io_fail;
+ } else if (u->transport->codec == 2) {
+ if ((n_written = hsp_process_render_msbc(u)) < 0)
+ goto io_fail;
+ } else {
+ n_written = -1;
+ pa_log_debug("Invalid codec for encoding %d", u->transport->codec);
+ }
}
if (n_written == 0)
@@ -1818,7 +2135,7 @@ static void bt_transport_config(struct userdata *u) {
if (u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW) {
u->sample_spec.format = PA_SAMPLE_S16LE;
u->sample_spec.channels = 1;
- u->sample_spec.rate = 8000;
+ u->sample_spec.rate = (u->transport->codec == 2) ? 16000 : 8000;
} else
bt_transport_config_a2dp(u);
}
--
1.7.11.7
More information about the pulseaudio-discuss
mailing list