From a6df43b777dc6a39f16e2a7a3adec6e0bf942503 Mon Sep 17 00:00:00 2001 From: Arkadiusz Bokowy Date: Tue, 19 Sep 2017 19:26:27 +0200 Subject: [PATCH] Add mSBC support into the SCO IO thread Add support for the mSBC codec for HFP into the SCO IO thread. This support is optional, and is controlled by the --enable-msbc configuration flag. The receiving part of this mSBC support has been tested with Jabra MOVE v2.3.0 headset and seems to work flawlessly. However, playback does not work... Maybe it will work with some other BT device. Note: This commit is a rework of a pull request submitted by Juha Kuikka. Fixes #29 and closes #37 --- README.md | 4 +- configure.ac | 7 ++ src/Makefile.am | 4 + src/bluealsa.c | 24 ++++- src/io.c | 79 ++++++++++++++++- src/msbc.c | 216 +++++++++++++++++++++++++++++++++++++++++++++ src/msbc.h | 71 +++++++++++++++ src/rfcomm.c | 23 ++++- src/rfcomm.h | 9 ++ src/transport.c | 3 + test/Makefile.am | 2 + test/test-io.c | 1 + test/test-msbc.c | 124 ++++++++++++++++++++++++++ test/test-server.c | 1 + 14 files changed, 557 insertions(+), 11 deletions(-) create mode 100644 src/msbc.c create mode 100644 src/msbc.h create mode 100644 test/test-msbc.c diff --git a/README.md b/README.md index 2c316f43f..c7cfe30b3 100644 --- a/README.md +++ b/README.md @@ -22,12 +22,12 @@ Installation $ autoreconf --install $ mkdir build && cd build - $ ../configure --enable-aac --enable-debug + $ ../configure --enable-debug --enable-aac --enable-msbc or if you intend to stream audio from a Linux distribution using PulseAudio (see [this issue](https://github.com/Arkq/bluez-alsa/issues/13)) - $ ../configure --enable-aac --enable-debug --disable-payloadcheck + $ ../configure --enable-debug --enable-aac --enable-msbc --disable-payloadcheck then diff --git a/configure.ac b/configure.ac index 092752a69..3ecf9f902 100644 --- a/configure.ac +++ b/configure.ac @@ -64,6 +64,13 @@ AM_COND_IF([ENABLE_APTX], [ AC_DEFINE([ENABLE_APTX], [1], [Define to 1 if apt-X is enabled.]) ]) +AC_ARG_ENABLE([msbc], + [AS_HELP_STRING([--enable-msbc], [enable MSBC support])]) +AM_CONDITIONAL([ENABLE_MSBC], [test "x$enable_msbc" = "xyes"]) +AM_COND_IF([ENABLE_MSBC], [ + AC_DEFINE([ENABLE_MSBC], [1], [Define to 1 if MSBC is enabled.]) +]) + AC_ARG_ENABLE([payloadcheck], [AS_HELP_STRING([--disable-payloadcheck], [disable RTP payload type check (workaround for a PulseAudio bug)])]) AM_CONDITIONAL([ENABLE_PAYLOADCHECK], [test "x$enable_payloadcheck" != "xno"]) diff --git a/src/Makefile.am b/src/Makefile.am index 78ba0ed90..ae73014f2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -20,6 +20,10 @@ bluealsa_SOURCES = \ utils.c \ main.c +if ENABLE_MSBC +bluealsa_SOURCES += msbc.c +endif + AM_CFLAGS = \ @BLUEZ_CFLAGS@ \ @GLIB2_CFLAGS@ \ diff --git a/src/bluealsa.c b/src/bluealsa.c index 7753fe08c..64abf64aa 100644 --- a/src/bluealsa.c +++ b/src/bluealsa.c @@ -33,20 +33,36 @@ struct ba_config config = { .hfp.features_sdp_hf = SDP_HFP_HF_FEAT_CLI | - SDP_HFP_HF_FEAT_VOLUME, - .hfp.features_sdp_ag = 0, + SDP_HFP_HF_FEAT_VOLUME | +#if ENABLE_MSBC + SDP_HFP_HF_FEAT_WBAND | +#endif + 0, + .hfp.features_sdp_ag = +#if ENABLE_MSBC + SDP_HFP_AG_FEAT_WBAND | +#endif + 0, .hfp.features_rfcomm_hf = HFP_HF_FEAT_CLI | HFP_HF_FEAT_VOLUME | HFP_HF_FEAT_ECS | HFP_HF_FEAT_ECC | - HFP_HF_FEAT_CODEC, +#if ENABLE_MSBC + HFP_HF_FEAT_CODEC | + HFP_HF_FEAT_ESOC | +#endif + 0, .hfp.features_rfcomm_ag = HFP_AG_FEAT_REJECT | HFP_AG_FEAT_ECS | HFP_AG_FEAT_ECC | HFP_AG_FEAT_EERC | - HFP_AG_FEAT_CODEC, +#if ENABLE_MSBC + HFP_AG_FEAT_CODEC | + HFP_AG_FEAT_ESOC | +#endif + 0, #if ENABLE_AAC /* There are two issues with the afterburner: a) it uses a LOT of power, diff --git a/src/io.c b/src/io.c index e8b6b920d..54fec33a3 100644 --- a/src/io.c +++ b/src/io.c @@ -36,6 +36,7 @@ #include "a2dp-codecs.h" #include "a2dp-rtp.h" #include "bluealsa.h" +#include "msbc.h" #include "transport.h" #include "utils.h" #include "shared/ffb.h" @@ -1154,6 +1155,11 @@ void *io_thread_sco(void *arg) { pthread_cleanup_push(CANCEL_ROUTINE(ffb_free), &bt_in); pthread_cleanup_push(CANCEL_ROUTINE(ffb_free), &bt_out); +#if ENABLE_MSBC + struct esco_msbc msbc = { .init = false }; + pthread_cleanup_push(CANCEL_ROUTINE(msbc_finish), &msbc); +#endif + /* these buffers shall be bigger than the SCO MTU */ if (ffb_init(&bt_in, 128) == -1 || ffb_init(&bt_out, 128) == -1) { error("Couldn't create data buffer: %s", strerror(ENOMEM)); @@ -1182,6 +1188,20 @@ void *io_thread_sco(void *arg) { pfds[3].fd = pfds[4].fd = -1; switch (t->codec) { +#if ENABLE_MSBC + case HFP_CODEC_MSBC: + msbc_encode(&msbc); + msbc_decode(&msbc); + if (t->mtu_read > 0 && ffb_len_in(&msbc.dec_data) >= t->mtu_read) + pfds[1].fd = t->bt_fd; + if (t->mtu_write > 0 && ffb_len_out(&msbc.enc_data) >= t->mtu_write) + pfds[2].fd = t->bt_fd; + if (t->mtu_write > 0 && ffb_len_in(&msbc.enc_pcm) >= t->mtu_write) + pfds[3].fd = t->sco.spk_pcm.fd; + if (ffb_len_out(&msbc.dec_pcm) > 0) + pfds[4].fd = t->sco.mic_pcm.fd; + break; +#endif case HFP_CODEC_CVSD: default: if (t->mtu_read > 0 && ffb_len_in(&bt_in) >= t->mtu_read) @@ -1245,8 +1265,16 @@ void *io_thread_sco(void *arg) { transport_release_bt_sco(t); asrs.frames = 0; } - else + else { transport_acquire_bt_sco(t); +#if ENABLE_MSBC + /* this can be called again, make sure it is idempotent */ + if (t->codec == HFP_CODEC_MSBC && msbc_init(&msbc) != 0) { + error("Couldn't initialize mSBC codec: %s", strerror(errno)); + goto fail; + } +#endif + } continue; } @@ -1262,6 +1290,12 @@ void *io_thread_sco(void *arg) { ssize_t len; switch (t->codec) { +#if ENABLE_MSBC + case HFP_CODEC_MSBC: + buffer = msbc.dec_data.tail; + buffer_len = ffb_len_in(&msbc.dec_data); + break; +#endif case HFP_CODEC_CVSD: default: buffer = bt_in.tail; @@ -1285,6 +1319,11 @@ void *io_thread_sco(void *arg) { } switch (t->codec) { +#if ENABLE_MSBC + case HFP_CODEC_MSBC: + ffb_seek(&msbc.dec_data, len); + break; +#endif case HFP_CODEC_CVSD: default: ffb_seek(&bt_in, len); @@ -1304,6 +1343,12 @@ void *io_thread_sco(void *arg) { ssize_t len; switch (t->codec) { +#if ENABLE_MSBC + case HFP_CODEC_MSBC: + buffer = msbc.enc_data.data; + buffer_len = t->mtu_write; + break; +#endif case HFP_CODEC_CVSD: default: buffer = bt_out.data; @@ -1327,6 +1372,11 @@ void *io_thread_sco(void *arg) { } switch (t->codec) { +#if ENABLE_MSBC + case HFP_CODEC_MSBC: + ffb_rewind(&msbc.enc_data, len); + break; +#endif case HFP_CODEC_CVSD: default: ffb_rewind(&bt_out, len); @@ -1341,6 +1391,12 @@ void *io_thread_sco(void *arg) { ssize_t samples; switch (t->codec) { +#if ENABLE_MSBC + case HFP_CODEC_MSBC: + buffer = (int16_t *)msbc.enc_pcm.tail; + samples = ffb_len_in(&msbc.enc_pcm) / sizeof(int16_t); + break; +#endif case HFP_CODEC_CVSD: default: buffer = (int16_t *)bt_out.tail; @@ -1358,6 +1414,11 @@ void *io_thread_sco(void *arg) { snd_pcm_scale_s16le(buffer, samples, 1, 0, 0); switch (t->codec) { +#if ENABLE_MSBC + case HFP_CODEC_MSBC: + ffb_seek(&msbc.enc_pcm, samples * sizeof(int16_t)); + break; +#endif case HFP_CODEC_CVSD: default: ffb_seek(&bt_out, samples * sizeof(int16_t)); @@ -1377,6 +1438,12 @@ void *io_thread_sco(void *arg) { ssize_t samples; switch (t->codec) { +#if ENABLE_MSBC + case HFP_CODEC_MSBC: + buffer = (int16_t *)msbc.dec_pcm.data; + samples = ffb_len_out(&msbc.dec_pcm) / sizeof(int16_t); + break; +#endif case HFP_CODEC_CVSD: default: buffer = (int16_t *)bt_in.data; @@ -1390,6 +1457,11 @@ void *io_thread_sco(void *arg) { error("FIFO write error: %s", strerror(errno)); switch (t->codec) { +#if ENABLE_MSBC + case HFP_CODEC_MSBC: + ffb_rewind(&msbc.dec_pcm, samples * sizeof(int16_t)); + break; +#endif case HFP_CODEC_CVSD: default: ffb_rewind(&bt_in, samples * sizeof(int16_t)); @@ -1398,13 +1470,16 @@ void *io_thread_sco(void *arg) { } /* keep data transfer at a constant bit rate */ - asrsync_sync(&asrs, 48 / 2); + asrsync_sync(&asrs, t->mtu_write / 2); t->delay = asrs.ts_busy.tv_nsec / 100000; } fail: pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); +#if ENABLE_MSBC + pthread_cleanup_pop(1); +#endif pthread_cleanup_pop(1); pthread_cleanup_pop(1); pthread_cleanup_pop(1); diff --git a/src/msbc.c b/src/msbc.c new file mode 100644 index 000000000..b9e5441e9 --- /dev/null +++ b/src/msbc.c @@ -0,0 +1,216 @@ +/* + * BlueALSA - msbc.c + * Copyright (c) 2016-2017 Arkadiusz Bokowy + * 2017 Juha Kuikka + * + * This file is a part of bluez-alsa. + * + * This project is licensed under the terms of the MIT license. + * + */ + +#include "msbc.h" + +#include +#include +#include + +#include "shared/log.h" + + +/** + * Find H2 synchronization header within eSCO transparent data. + * + * @param data Memory area with the eSCO transparent data. + * @param len Address from where the length of the eSCO transparent data + * is read. Upon exit, the remaining length of the eSCO data will be + * stored in this variable (received length minus scanned length). + * @return On success this function returns the first occurrence of the H2 + * synchronization header. Otherwise, it returns NULL. */ +static esco_h2_header_t *msbc_find_h2_header(const void *data, size_t *len) { + + esco_h2_header_t *h2 = NULL; + size_t _len = *len; + + while (_len >= sizeof(esco_h2_header_t)) { + esco_h2_header_t *tmp = (esco_h2_header_t *)data; + + if (tmp->sync == ESCO_H2_SYNCWORD && + (bool)(tmp->sn0 & 0x1) == (bool)(tmp->sn0 & 0x2) && + (bool)(tmp->sn1 & 0x1) == (bool)(tmp->sn1 & 0x2)) { + h2 = tmp; + goto final; + } + + data += 1; + _len--; + } + +final: + *len = _len; + return h2; +} + +int msbc_init(struct esco_msbc *msbc) { + + int err; + + if (msbc->init) { + /* Because there is no sbc_reinit_msbc(), we have to initialize encoder + * and decoder once more. In order to prevent memory leaks, we have to + * release previously allocated resources. */ + sbc_finish(&msbc->dec_sbc); + sbc_finish(&msbc->enc_sbc); + } + + if ((errno = -sbc_init_msbc(&msbc->dec_sbc, 0)) != 0) + goto fail; + if ((errno = -sbc_init_msbc(&msbc->enc_sbc, 0)) != 0) + goto fail; + +#if DEBUG + size_t len; + if ((len = sbc_get_frame_length(&msbc->dec_sbc)) > MSBC_FRAMELEN) { + warn("Unexpected mSBC frame size: %zd > %d", len, MSBC_FRAMELEN); + errno = ENOMEM; + goto fail; + } + if ((len = sbc_get_codesize(&msbc->dec_sbc)) > MSBC_CODESIZE) { + warn("Unexpected mSBC code size: %zd > %d", len, MSBC_CODESIZE); + errno = ENOMEM; + goto fail; + } + if ((len = sbc_get_frame_length(&msbc->enc_sbc)) > MSBC_FRAMELEN) { + warn("Unexpected mSBC frame size: %zd > %d", len, MSBC_FRAMELEN); + errno = ENOMEM; + goto fail; + } + if ((len = sbc_get_codesize(&msbc->enc_sbc)) > MSBC_CODESIZE) { + warn("Unexpected mSBC code size: %zd > %d", len, MSBC_CODESIZE); + errno = ENOMEM; + goto fail; + } +#endif + + if (!msbc->init) { + if (ffb_init(&msbc->dec_data, sizeof(esco_msbc_frame_t) * 3) == -1) + goto fail; + if (ffb_init(&msbc->dec_pcm, MSBC_CODESIZE * 2) == -1) + goto fail; + if (ffb_init(&msbc->enc_data, sizeof(esco_msbc_frame_t) * 3) == -1) + goto fail; + if (ffb_init(&msbc->enc_pcm, MSBC_CODESIZE * 2) == -1) + goto fail; + } + + msbc->dec_data.tail = msbc->dec_data.data; + msbc->dec_pcm.tail = msbc->dec_pcm.data; + msbc->enc_data.tail = msbc->enc_data.data; + msbc->enc_pcm.tail = msbc->enc_pcm.data; + msbc->enc_frames = 0; + + msbc->init = true; + return 0; + +fail: + err = errno; + msbc_finish(msbc); + errno = err; + return -1; +} + +void msbc_finish(struct esco_msbc *msbc) { + + if (msbc == NULL) + return; + + sbc_finish(&msbc->dec_sbc); + sbc_finish(&msbc->enc_sbc); + + ffb_free(&msbc->dec_data); + ffb_free(&msbc->dec_pcm); + ffb_free(&msbc->enc_data); + ffb_free(&msbc->enc_pcm); + +} + +void msbc_decode(struct esco_msbc *msbc) { + + uint8_t *input = msbc->dec_data.data; + size_t input_len = ffb_len_out(&msbc->dec_data); + uint8_t *output = msbc->dec_pcm.tail; + size_t output_len = ffb_len_in(&msbc->dec_pcm); + + for (;;) { + + size_t tmp = input_len; + esco_h2_header_t *h2 = msbc_find_h2_header(input, &input_len); + esco_msbc_frame_t *frame = (esco_msbc_frame_t *)h2; + ssize_t len; + + input += tmp - input_len; + if (frame == NULL || input_len < sizeof(*frame) || output_len < MSBC_CODESIZE) + break; + + /* TODO: Check SEQ, implement PLC. */ + + if ((len = sbc_decode(&msbc->dec_sbc, frame->payload, sizeof(frame->payload), + output, output_len, NULL)) > 0) { + output += MSBC_CODESIZE; + output_len -= MSBC_CODESIZE; + ffb_seek(&msbc->dec_pcm, MSBC_CODESIZE); + } + else + warn("mSBC decoding error: %s", strerror(-len)); + + input += sizeof(*frame); + input_len -= sizeof(*frame); + + } + + /* reshuffle remaining data to the beginning of the buffer */ + ffb_rewind(&msbc->dec_data, input - msbc->dec_data.data); + +} + +void msbc_encode(struct esco_msbc *msbc) { + + /* pre-generated H2 headers */ + static const uint16_t h2[] = { + 0x0801, 0x3801, 0xc801, 0xf801 + }; + + uint8_t *input = msbc->enc_pcm.data; + size_t input_len = ffb_len_out(&msbc->enc_pcm); + esco_msbc_frame_t *frame = (esco_msbc_frame_t *)msbc->enc_data.tail; + size_t output_len = ffb_len_in(&msbc->enc_data); + + while (input_len >= MSBC_CODESIZE && + output_len >= sizeof(*frame)) { + + ssize_t len; + + if ((len = sbc_encode(&msbc->enc_sbc, input, input_len, + frame->payload, sizeof(frame->payload), NULL)) > 0) { + + *(uint16_t *)&frame->header = h2[msbc->enc_frames % 4]; + frame->padding = 0; + msbc->enc_frames++; + + frame++; + output_len -= sizeof(*frame); + ffb_seek(&msbc->enc_data, sizeof(*frame)); + + } + else + warn("mSBC encoding error: %s", strerror(-len)); + + input += MSBC_CODESIZE; + input_len -= MSBC_CODESIZE; + + } + + /* reshuffle remaining data to the beginning of the buffer */ + ffb_rewind(&msbc->enc_pcm, input - msbc->enc_pcm.data); + +} diff --git a/src/msbc.h b/src/msbc.h new file mode 100644 index 000000000..bead491db --- /dev/null +++ b/src/msbc.h @@ -0,0 +1,71 @@ +/* + * BlueALSA - msbc.h + * Copyright (c) 2016-2017 Arkadiusz Bokowy + * 2017 Juha Kuikka + * + * This file is a part of bluez-alsa. + * + * This project is licensed under the terms of the MIT license. + * + */ + +#ifndef BLUEALSA_MSBC_H_ +#define BLUEALSA_MSBC_H_ + +#include +#include +#include "shared/ffb.h" + +/* HFP uses SBC encoding with precisely defined parameters. Hence, the size + * of the input (number of PCM samples) and output is known up front. */ +#define MSBC_CODESIZE 240 +#define MSBC_FRAMELEN 57 + +#define ESCO_H2_SYNCWORD 0x801 + +/** + * Synchronization header for eSCO transparent data. */ +typedef struct esco_h2_header { + uint16_t sync:12; + uint16_t sn0:2; + uint16_t sn1:2; +} __attribute__ ((packed)) esco_h2_header_t; + +typedef struct esco_msbc_frame { + esco_h2_header_t header; + uint8_t payload[MSBC_FRAMELEN]; + uint8_t padding; +} __attribute__ ((packed)) esco_msbc_frame_t; + +struct esco_msbc { + + /* decoder */ + sbc_t dec_sbc; + /* encoder */ + sbc_t enc_sbc; + + /* buffer for incoming eSCO frames */ + struct ffb dec_data; + /* buffer for outgoing PCM samples */ + struct ffb dec_pcm; + + /* buffer for incoming PCM samples */ + struct ffb enc_pcm; + /* buffer for outgoing eSCO frames */ + struct ffb enc_data; + + size_t enc_frames; + + /* Determine whether structure has been initialized. This field is + * used for reinitialization - it makes msbc_init() idempotent. */ + bool init; + +}; + +int msbc_init(struct esco_msbc *msbc); +void msbc_finish(struct esco_msbc *msbc); + +void msbc_decode(struct esco_msbc *msbc); +void msbc_encode(struct esco_msbc *msbc); + +#endif diff --git a/src/rfcomm.c b/src/rfcomm.c index 57bff4a87..ae16596c1 100644 --- a/src/rfcomm.c +++ b/src/rfcomm.c @@ -401,11 +401,17 @@ static int rfcomm_handler_bcs_resp_cb(struct rfcomm_conn *c, const struct bt_at /** * SET: Bluetooth Available Codecs */ static int rfcomm_handler_bac_set_cb(struct rfcomm_conn *c, const struct bt_at *at) { - (void)at; + const int fd = c->t->bt_fd; + char *tmp = at->value - 1; - /* In case some headsets send BAC even if we don't advertise - * support for it. In such case, just OK and ignore. */ + do { + tmp += 1; +#if ENABLE_MSBC + if (atoi(tmp) == HFP_CODEC_MSBC) + c->msbc = true; +#endif + } while ((tmp = strchr(tmp, ',')) != NULL); if (rfcomm_write_at(fd, AT_TYPE_RESP, NULL, "OK") == -1) return -1; @@ -612,7 +618,12 @@ void *rfcomm_thread(void *arg) { break; case HFP_SLC_BRSF_SET_OK: if (t->rfcomm.hfp_features & HFP_AG_FEAT_CODEC) { +#if ENABLE_MSBC + /* advertise, that we are supporting CVSD (1) and mSBC (2) */ + if (rfcomm_write_at(pfds[1].fd, AT_TYPE_CMD_SET, "+BAC", "1,2") == -1) +#else if (rfcomm_write_at(pfds[1].fd, AT_TYPE_CMD_SET, "+BAC", "1") == -1) +#endif goto ioerror; conn.handler = &rfcomm_handler_resp_ok; break; @@ -668,9 +679,15 @@ void *rfcomm_thread(void *arg) { rfcomm_set_hfp_state(&conn, HFP_SLC_CONNECTED); case HFP_SLC_CONNECTED: if (t->rfcomm.hfp_features & HFP_HF_FEAT_CODEC) { +#if ENABLE_MSBC + if (rfcomm_write_at(pfds[1].fd, AT_TYPE_RESP, "+BCS", conn.msbc ? "2" : "1") == -1) + goto ioerror; + t->rfcomm.sco->codec = conn.msbc ? HFP_CODEC_MSBC : HFP_CODEC_CVSD; +#else if (rfcomm_write_at(pfds[1].fd, AT_TYPE_RESP, "+BCS", "1") == -1) goto ioerror; t->rfcomm.sco->codec = HFP_CODEC_CVSD; +#endif conn.handler = &rfcomm_handler_bcs_set; break; } diff --git a/src/rfcomm.h b/src/rfcomm.h index b9161c87b..f2c2ad77f 100644 --- a/src/rfcomm.h +++ b/src/rfcomm.h @@ -11,6 +11,10 @@ #ifndef BLUEALSA_RFCOMM_H_ #define BLUEALSA_RFCOMM_H_ +#if HAVE_CONFIG_H +# include "config.h" +#endif + #include "at.h" #include "hfp.h" #include "transport.h" @@ -41,6 +45,11 @@ struct rfcomm_conn { uint8_t spk_gain; uint8_t mic_gain; +#if ENABLE_MSBC + /* determine whether mSBC is available */ + bool msbc; +#endif + /* associated transport */ struct ba_transport *t; diff --git a/src/transport.c b/src/transport.c index 2bbecea03..6ba7c82bc 100644 --- a/src/transport.c +++ b/src/transport.c @@ -949,8 +949,11 @@ int transport_acquire_bt_sco(struct ba_transport *t) { /* XXX: It seems, that the MTU values returned by the HCI interface * are incorrect (or our interpretation of them is incorrect). */ + debug("HCI MTU: R:%zu W:%zu", t->mtu_read, t->mtu_write); t->mtu_read = 48; t->mtu_write = 48; + if (t->codec == HFP_CODEC_MSBC) + t->mtu_read = t->mtu_write = 24; debug("New SCO link: %d (MTU: R:%zu W:%zu)", t->bt_fd, t->mtu_read, t->mtu_write); diff --git a/test/Makefile.am b/test/Makefile.am index 1c8d489b5..84ae07065 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -4,6 +4,7 @@ TESTS = \ test-at \ test-io \ + test-msbc \ test-utils if ENABLE_PCM_TEST @@ -13,6 +14,7 @@ endif check_PROGRAMS = \ test-at \ test-io \ + test-msbc \ test-pcm \ test-server \ test-utils diff --git a/test/test-io.c b/test/test-io.c index 99592e2e4..7adb212f3 100644 --- a/test/test-io.c +++ b/test/test-io.c @@ -16,6 +16,7 @@ #include "../src/bluealsa.c" #include "../src/ctl.c" #include "../src/io.c" +#include "../src/msbc.c" #include "../src/rfcomm.c" #include "../src/transport.c" #include "../src/utils.c" diff --git a/test/test-msbc.c b/test/test-msbc.c new file mode 100644 index 000000000..10b91b6c4 --- /dev/null +++ b/test/test-msbc.c @@ -0,0 +1,124 @@ +/* + * test-msbc.c + * Copyright (c) 2016-2017 Arkadiusz Bokowy + * + * This file is a part of bluez-alsa. + * + * This project is licensed under the terms of the MIT license. + * + */ + +#include "inc/sine.inc" +#include "inc/test.inc" +#include "../src/msbc.c" +#include "../src/shared/ffb.c" + +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) + +int test_msbc_find_h2_header(void) { + + static const uint8_t raw[][10] = { + { 0 }, + /* H2 header starts at first byte */ + { 0x01, 0x08, 0xad, 0x00, 0x00, 0xd5, 0x10, 0x00, 0x11, 0x10 }, + /* H2 header starts at 5th byte */ + { 0x00, 0xd5, 0x10, 0x00, 0x01, 0x38, 0xad, 0x00, 0x11, 0x10 }, + /* first H2 header starts at 2nd byte (second at 6th byte) */ + { 0xd5, 0x01, 0xc8, 0xad, 0x00, 0x01, 0xf8, 0xad, 0x11, 0x10 }, + /* incorrect sequence number (bit not duplicated) */ + { 0x01, 0x18, 0xad, 0x00, 0x00, 0xd5, 0x10, 0x00, 0x11, 0x10 }, + { 0x01, 0x58, 0xad, 0x00, 0x00, 0xd5, 0x10, 0x00, 0x11, 0x10 }, + }; + + size_t len; + + len = sizeof(*raw); + assert(msbc_find_h2_header(raw[0], &len) == NULL); + assert(len == 1); + + len = sizeof(*raw); + assert(msbc_find_h2_header(raw[1], &len) == (esco_h2_header_t *)&raw[1][0]); + assert(len == sizeof(*raw) - 0); + + len = sizeof(*raw); + assert(msbc_find_h2_header(raw[2], &len) == (esco_h2_header_t *)&raw[2][4]); + assert(len == sizeof(*raw) - 4); + + len = sizeof(*raw); + assert(msbc_find_h2_header(raw[3], &len) == (esco_h2_header_t *)&raw[3][1]); + assert(len == sizeof(*raw) - 1); + + len = sizeof(*raw); + assert(msbc_find_h2_header(raw[4], &len) == NULL); + assert(len == 1); + + len = sizeof(*raw); + assert(msbc_find_h2_header(raw[5], &len) == NULL); + assert(len == 1); + + return 0; +} + +int test_msbc_encode_decode(void) { + + struct esco_msbc msbc = { .init = false }; + uint8_t sine[1024 * 2]; + size_t len; + size_t i; + + assert(msbc_init(&msbc) == 0); + snd_pcm_sine_s16le((int16_t *)sine, sizeof(sine) / sizeof(int16_t), 1, 0, 0.01); + + uint8_t data[sizeof(sine)]; + uint8_t *data_tail = data; + + for (i = 0; i < sizeof(sine); ) { + + len = ffb_len_in(&msbc.enc_pcm); + len = MIN(sizeof(sine) - i, len); + memcpy(msbc.enc_pcm.tail, &sine[i], len); + ffb_seek(&msbc.enc_pcm, len); + i += len; + + msbc_encode(&msbc); + + len = ffb_len_out(&msbc.enc_data); + memcpy(data_tail, msbc.enc_data.data, len); + ffb_rewind(&msbc.enc_data, len); + data_tail += len; + + } + + assert((data_tail - data) == 480); + + uint8_t pcm[sizeof(sine)]; + uint8_t *pcm_tail = pcm; + + for (i = 0; i < (size_t)(data_tail - data); ) { + + len = ffb_len_in(&msbc.dec_data); + len = MIN((data_tail - data) - i, len); + memcpy(msbc.dec_data.tail, &data[i], len); + ffb_seek(&msbc.dec_data, len); + i += len; + + msbc_decode(&msbc); + + len = ffb_len_out(&msbc.dec_pcm); + memcpy(pcm_tail, msbc.dec_pcm.data, len); + ffb_rewind(&msbc.dec_pcm, len); + pcm_tail += len; + + } + + assert((pcm_tail - pcm) == 1920); + + msbc_finish(&msbc); + return 0; +} + +int main(void) { + test_run(test_msbc_find_h2_header); + test_run(test_msbc_encode_decode); + return 0; +} diff --git a/test/test-server.c b/test/test-server.c index 6b8731267..3871c600a 100644 --- a/test/test-server.c +++ b/test/test-server.c @@ -30,6 +30,7 @@ #include "../src/io.c" #undef io_thread_a2dp_sink_sbc #undef io_thread_a2dp_source_sbc +#include "../src/msbc.c" #include "../src/rfcomm.c" #define transport_acquire_bt_a2dp _transport_acquire_bt_a2dp #include "../src/transport.c"