summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames Bottomley <James.Bottomley@HansenPartnership.com>2021-02-17 09:44:34 +0300
committerPulseAudio Marge Bot <pulseaudio-maintainers@lists.freedesktop.org>2021-04-05 15:43:32 +0000
commitf22cfa8f811f4f67a5aee38a610da72ad28fe083 (patch)
treec50a4c24743f6ef0d7642363f8982a1811c71b54
parenta7b21fb5554cc84c3c5e3aeb8a120607bd078f1e (diff)
bluetooth: add support for mSBC codec
Adding processing support for the mSBC codec is somewhat problematic, because, although it is a SBC codec, the a2dp handling can't simply be reused because the codec is used on an eSCO link with transparent data, meaning the transmission unit has to be 48 bytes (fragmenting the codec packets) and reassembly and boundary detection is required to be done by the implementation. Therefore we have to implement separate render and push routines for msbc that do this fragmentation. Fragmentation is done by emulating circular buffers. The receive (push) buffer is easy, since the mSBC packet size is 60, simply have a buffer of this size in the sbc_info area where the fragments are reassembled. Once we have a full 60 bytes, decode and restart from zero. The send (render) buffer is more problematic, since the transmit must be done from contiguous memory. This means that the buffer must be the lowest common multiple of the transmission unit and the packet size. This value is 240 since 240/48 == 5 and 240/60 == 4. So the buffer pointers are reset at 240 which is a whole number of both rendered packets and eSCO transmission units. Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com> Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/507>
-rw-r--r--meson.build1
-rw-r--r--src/modules/bluetooth/a2dp-codec-util.c2
-rw-r--r--src/modules/bluetooth/bt-codec-msbc.c290
-rw-r--r--src/modules/bluetooth/bt-codec-msbc.h35
-rw-r--r--src/modules/bluetooth/meson.build1
5 files changed, 329 insertions, 0 deletions
diff --git a/meson.build b/meson.build
index 6b86ba2bc..fd146a7e7 100644
--- a/meson.build
+++ b/meson.build
@@ -696,6 +696,7 @@ if avahi_dep.found()
endif
sbc_dep = dependency('sbc', version : '>= 1.0', required : false)
+
if get_option('bluez5')
assert(dbus_dep.found(), 'BlueZ requires D-Bus support')
assert(sbc_dep.found(), 'BlueZ requires SBC support')
diff --git a/src/modules/bluetooth/a2dp-codec-util.c b/src/modules/bluetooth/a2dp-codec-util.c
index 23860eafb..355e19ca7 100644
--- a/src/modules/bluetooth/a2dp-codec-util.c
+++ b/src/modules/bluetooth/a2dp-codec-util.c
@@ -29,12 +29,14 @@
#include "a2dp-codec-util.h"
+extern const pa_a2dp_codec pa_bt_codec_msbc;
extern const pa_a2dp_codec pa_bt_codec_cvsd;
/* List of HSP/HFP codecs.
*/
static const pa_a2dp_codec *pa_hf_codecs[] = {
&pa_bt_codec_cvsd,
+ &pa_bt_codec_msbc,
};
extern const pa_a2dp_codec pa_a2dp_codec_sbc;
diff --git a/src/modules/bluetooth/bt-codec-msbc.c b/src/modules/bluetooth/bt-codec-msbc.c
new file mode 100644
index 000000000..af99a4774
--- /dev/null
+++ b/src/modules/bluetooth/bt-codec-msbc.c
@@ -0,0 +1,290 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "a2dp-codec-api.h"
+
+#include "bt-codec-msbc.h"
+#include <sbc/sbc.h>
+
+typedef struct sbc_info {
+ sbc_t sbc; /* Codec data */
+ size_t codesize, frame_length; /* SBC Codesize, frame_length. We simply cache those values here */
+ uint8_t msbc_seq:2; /* mSBC packet sequence number, 2 bits only */
+
+ uint16_t msbc_push_offset;
+ uint8_t input_buffer[MSBC_PACKET_SIZE]; /* Codec transfer buffer */
+
+ pa_sample_spec sample_spec;
+} sbc_info_t;
+
+static void *init(bool for_encoding, bool for_backchannel, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *sample_spec, pa_core *core) {
+ struct sbc_info *info;
+ int ret;
+
+ info = pa_xnew0(struct sbc_info, 1);
+
+ ret = sbc_init_msbc(&info->sbc, 0);
+ if (ret != 0) {
+ pa_xfree(info);
+ pa_log_error("mSBC initialization failed: %d", ret);
+ return NULL;
+ }
+
+ info->sbc.endian = SBC_LE;
+
+ info->codesize = sbc_get_codesize(&info->sbc);
+ info->frame_length = sbc_get_frame_length(&info->sbc);
+ pa_log_info("mSBC codesize=%d, frame_length=%d",
+ (int)info->codesize,
+ (int)info->frame_length);
+
+ info->sample_spec.format = PA_SAMPLE_S16LE;
+ info->sample_spec.channels = 1;
+ info->sample_spec.rate = 16000;
+
+ pa_assert(pa_frame_aligned(info->codesize, &info->sample_spec));
+
+ *sample_spec = info->sample_spec;
+
+ return info;
+}
+
+static void deinit(void *codec_info) {
+ struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
+
+ sbc_finish(&sbc_info->sbc);
+ pa_xfree(sbc_info);
+}
+
+static int reset(void *codec_info) {
+ struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
+ int ret;
+
+ /* SBC library release 1.5 has a bug in sbc_reinit_msbc:
+ * it forgets to restore priv->msbc flag after clearing priv content.
+ * This causes decoder assertion on first call since codesize would be
+ * different from expected for mSBC configuration.
+ *
+ * Do not use sbc_reinit_msbc until it is fixed.
+ */
+
+ sbc_finish(&sbc_info->sbc);
+ ret = sbc_init_msbc(&sbc_info->sbc, 0);
+ if (ret != 0) {
+ pa_xfree(sbc_info);
+ pa_log_error("mSBC initialization failed: %d", ret);
+ return -1;
+ }
+
+ sbc_info->sbc.endian = SBC_LE;
+
+ sbc_info->msbc_seq = 0;
+ sbc_info->msbc_push_offset = 0;
+
+ return 0;
+}
+
+static size_t get_read_block_size(void *codec_info, size_t link_mtu) {
+ struct sbc_info *info = (struct sbc_info *) codec_info;
+ size_t block_size = info->codesize;
+
+ /* this never happens as sbc_info->codesize is always frame-aligned */
+ if (!pa_frame_aligned(block_size, &info->sample_spec)) {
+ pa_log_debug("Got invalid block size: %lu, rounding down", block_size);
+ block_size = pa_frame_align(block_size, &info->sample_spec);
+ }
+
+ return block_size;
+}
+
+static size_t get_write_block_size(void *codec_info, size_t link_mtu) {
+ struct sbc_info *info = (struct sbc_info *) codec_info;
+ return info->codesize;
+}
+
+static size_t get_encoded_block_size(void *codec_info, size_t input_size) {
+ struct sbc_info *info = (struct sbc_info *) codec_info;
+ size_t encoded_size = MSBC_PACKET_SIZE;
+
+ /* input size should be aligned to write block size */
+ pa_assert_fp(input_size % info->codesize == 0);
+
+ return encoded_size * (input_size / info->codesize);
+}
+
+static size_t reduce_encoder_bitrate(void *codec_info, size_t write_link_mtu) {
+ return 0;
+}
+
+static size_t increase_encoder_bitrate(void *codec_info, size_t write_link_mtu) {
+ return 0;
+}
+
+static size_t encode_buffer(void *codec_info, uint32_t timestamp, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size, size_t *processed) {
+ struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
+ struct msbc_frame *frame;
+ uint8_t seq;
+ ssize_t encoded;
+ ssize_t written;
+
+ pa_assert(input_size == sbc_info->codesize);
+
+ /* must be room to render packet */
+ pa_assert(output_size >= MSBC_PACKET_SIZE);
+
+ frame = (struct msbc_frame *)output_buffer;
+ seq = sbc_info->msbc_seq++;
+ frame->hdr.id0 = MSBC_H2_ID0;
+ frame->hdr.id1.s.id1 = MSBC_H2_ID1;
+ if (seq & 0x02)
+ frame->hdr.id1.s.sn1 = 3;
+ else
+ frame->hdr.id1.s.sn1 = 0;
+ if (seq & 0x01)
+ frame->hdr.id1.s.sn0 = 3;
+ else
+ frame->hdr.id1.s.sn0 = 0;
+
+ encoded = sbc_encode(&sbc_info->sbc,
+ input_buffer, input_size,
+ frame->payload, MSBC_FRAME_SIZE,
+ &written);
+
+ frame->padding = 0x00;
+
+ if (PA_UNLIKELY(encoded <= 0)) {
+ pa_log_error("SBC encoding error (%li)", (long) encoded);
+ return -1;
+ }
+
+ pa_assert_fp((size_t) encoded == sbc_info->codesize);
+ pa_assert_fp((size_t) written == sbc_info->frame_length);
+
+ *processed = encoded;
+
+ return MSBC_PACKET_SIZE;
+}
+
+/*
+ * We build a msbc frame up in the sbc_info buffer until we have a whole one
+ */
+static struct msbc_frame *msbc_find_frame(struct sbc_info *si, ssize_t *len,
+ const uint8_t *buf, int *pseq)
+{
+ int i;
+ uint8_t *p = si->input_buffer;
+
+ for (i = 0; i < *len; i++) {
+ union msbc_h2_id1 id1;
+
+ if (si->msbc_push_offset == 0) {
+ if (buf[i] != MSBC_H2_ID0)
+ continue;
+ } else if (si->msbc_push_offset == 1) {
+ id1.b = buf[i];
+
+ if (id1.s.id1 != MSBC_H2_ID1)
+ goto error;
+ if (id1.s.sn0 != 3 && id1.s.sn0 != 0)
+ goto error;
+ if (id1.s.sn1 != 3 && id1.s.sn1 != 0)
+ goto error;
+ } else if (si->msbc_push_offset == 2) {
+ if (buf[i] != MSBC_SYNC_BYTE)
+ goto error;
+ }
+ p[si->msbc_push_offset++] = buf[i];
+
+ if (si->msbc_push_offset == MSBC_PACKET_SIZE) {
+ id1.b = p[1];
+ *pseq = (id1.s.sn0 & 0x1) | (id1.s.sn1 & 0x2);
+ si->msbc_push_offset = 0;
+ *len = *len - i;
+ return (struct msbc_frame *)p;
+ }
+ continue;
+
+ error:
+ si->msbc_push_offset = 0;
+ }
+ *len = 0;
+ return NULL;
+}
+
+static size_t decode_buffer(void *codec_info, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size, size_t *processed) {
+ struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
+ ssize_t remaining;
+ ssize_t decoded;
+ size_t written = 0;
+ struct msbc_frame *frame;
+ int seq;
+
+ remaining = input_size;
+ frame = msbc_find_frame(sbc_info, &remaining, input_buffer, &seq);
+
+ /* only process when we have a full frame */
+ if (!frame) {
+ *processed = input_size - remaining;
+ return 0;
+ }
+
+ uint8_t lost_packets = (4 + seq - sbc_info->msbc_seq++) % 4;
+
+ if (lost_packets) {
+ pa_log_error("Lost %d input audio packet(s)", lost_packets);
+ sbc_info->msbc_seq = seq + 1;
+ }
+
+ decoded = sbc_decode(&sbc_info->sbc, frame->payload, MSBC_FRAME_SIZE, output_buffer, output_size, &written);
+
+ /* now we've consumed the sbc_info buffer, start a new one with
+ * the partial frame we have */
+ if (remaining > 0)
+ msbc_find_frame(sbc_info, &remaining, input_buffer + input_size - remaining, &seq);
+
+ pa_assert_fp(remaining == 0);
+
+ if (PA_UNLIKELY(decoded <= 0)) {
+ pa_log_error("mSBC decoding error (%li)", (long) decoded);
+ *processed = 0;
+ return 0;
+ }
+ pa_assert_fp((size_t)decoded == sbc_info->frame_length);
+ pa_assert_fp((size_t)written == sbc_info->codesize);
+
+ *processed = input_size - remaining;
+ return written;
+}
+
+/* Modified SBC codec for HFP Wideband Speech*/
+const pa_a2dp_codec pa_bt_codec_msbc = {
+ .name = "mSBC",
+ .description = "mSBC",
+ .init = init,
+ .deinit = deinit,
+ .reset = reset,
+ .get_read_block_size = get_read_block_size,
+ .get_write_block_size = get_write_block_size,
+ .get_encoded_block_size = get_encoded_block_size,
+ .reduce_encoder_bitrate = reduce_encoder_bitrate,
+ .increase_encoder_bitrate = increase_encoder_bitrate,
+ .encode_buffer = encode_buffer,
+ .decode_buffer = decode_buffer,
+};
diff --git a/src/modules/bluetooth/bt-codec-msbc.h b/src/modules/bluetooth/bt-codec-msbc.h
new file mode 100644
index 000000000..3236ca1cc
--- /dev/null
+++ b/src/modules/bluetooth/bt-codec-msbc.h
@@ -0,0 +1,35 @@
+#pragma once
+
+/*
+ * Parameters for use with mSBC over eSCO link
+ */
+
+#define MSBC_H2_ID0 0x01
+#define MSBC_H2_ID1 0x08
+#define MSBC_FRAME_SIZE 57
+
+#define MSBC_SYNC_BYTE 0xad
+
+struct msbc_h2_id1_s {
+ uint8_t id1:4;
+ uint8_t sn0:2;
+ uint8_t sn1:2;
+} __attribute__ ((packed));
+
+union msbc_h2_id1 {
+ struct msbc_h2_id1_s s;
+ uint8_t b;
+};
+
+struct msbc_h2_header {
+ uint8_t id0;
+ union msbc_h2_id1 id1;
+} __attribute__ ((packed));
+
+struct msbc_frame {
+ struct msbc_h2_header hdr;
+ uint8_t payload[MSBC_FRAME_SIZE];
+ uint8_t padding; /* must be zero */
+} __attribute__ ((packed));
+
+#define MSBC_PACKET_SIZE sizeof(struct msbc_frame)
diff --git a/src/modules/bluetooth/meson.build b/src/modules/bluetooth/meson.build
index 7f2e6db7a..99263bb5a 100644
--- a/src/modules/bluetooth/meson.build
+++ b/src/modules/bluetooth/meson.build
@@ -3,6 +3,7 @@ libbluez5_util_sources = [
'a2dp-codec-util.c',
'bluez5-util.c',
'bt-codec-cvsd.c',
+ 'bt-codec-msbc.c',
]
libbluez5_util_headers = [