diff --git a/.github/workflows/build-and-test.yaml b/.github/workflows/build-and-test.yaml index 0ea2f9418..eb7f65bc1 100644 --- a/.github/workflows/build-and-test.yaml +++ b/.github/workflows/build-and-test.yaml @@ -33,6 +33,7 @@ jobs: libncurses5-dev \ libreadline-dev \ libsbc-dev \ + libspandsp-dev \ python-docutils - uses: actions/checkout@v2 - name: Create Build Environment diff --git a/.github/workflows/codeql-analysis.yaml b/.github/workflows/codeql-analysis.yaml index 4e48aa100..9e94bccab 100644 --- a/.github/workflows/codeql-analysis.yaml +++ b/.github/workflows/codeql-analysis.yaml @@ -29,7 +29,8 @@ jobs: libmpg123-dev \ libncurses5-dev \ libreadline-dev \ - libsbc-dev + libsbc-dev \ + libspandsp-dev - uses: actions/checkout@v2 - name: Initialize CodeQL uses: github/codeql-action/init@v1 diff --git a/NEWS b/NEWS index 9856343dc..262bb2bb6 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,7 @@ unreleased - optional support for A2DP FastStream codec (music & voice) - optional support for A2DP LC3plus codec (music & voice) +- packet loss concealment (PLC) for HFP with mSBC codec - enable/disable BT profiles/codecs via command line options - allow to select BT transport codec with ALSA configuration - allow to set PCM volume properties with ALSA configuration diff --git a/README.md b/README.md index 7c4e2840c..bdbd8270d 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ Dependencies: - [mpg123](https://www.mpg123.org/) (when MPEG decoding support is enabled with `--enable-mpg123`) - [openaptx](https://github.com/Arkq/openaptx) (when apt-X support is enabled with `--enable-aptx` and/or `--enable-aptx-hd`) +- [spandsp](https://www.soft-switch.org) (when mSBC support is enabled with `--enable-msbc`) Dependencies for client applications (e.g. `bluealsa-aplay` or `bluealsa-cli`): diff --git a/configure.ac b/configure.ac index b46bdfc05..ba3db3491 100644 --- a/configure.ac +++ b/configure.ac @@ -201,6 +201,7 @@ 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], [ + PKG_CHECK_MODULES([SPANDSP], [spandsp >= 0.0.6]) AC_DEFINE([ENABLE_MSBC], [1], [Define to 1 if mSBC is enabled.]) ]) diff --git a/src/Makefile.am b/src/Makefile.am index d2819772d..94465e5da 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -107,7 +107,8 @@ AM_CFLAGS = \ @LIBBSD_CFLAGS@ \ @LIBUNWIND_CFLAGS@ \ @MPG123_CFLAGS@ \ - @SBC_CFLAGS@ + @SBC_CFLAGS@ \ + @SPANDSP_CFLAGS@ LDADD = \ @AAC_LIBS@ \ @@ -123,4 +124,5 @@ LDADD = \ @LIBUNWIND_LIBS@ \ @MP3LAME_LIBS@ \ @MPG123_LIBS@ \ - @SBC_LIBS@ + @SBC_LIBS@ \ + @SPANDSP_LIBS@ diff --git a/src/codec-msbc.c b/src/codec-msbc.c index adc4331de..7895454ba 100644 --- a/src/codec-msbc.c +++ b/src/codec-msbc.c @@ -1,6 +1,6 @@ /* * BlueALSA - codec-msbc.c - * Copyright (c) 2016-2021 Arkadiusz Bokowy + * Copyright (c) 2016-2022 Arkadiusz Bokowy * 2017 Juha Kuikka * * This file is a part of bluez-alsa. @@ -14,11 +14,23 @@ #include #include #include +#include #include +#include + #include "codec-sbc.h" #include "shared/log.h" +/** + * Use PLC in case of SBC decoding error. + * + * If defined to 1, in case of SBC frame decoding error the msbc_decode() + * function will not return error code, but will use PLC to conceal missing + * PCM samples. Such behavior should ensure that a PCM client will receive + * correct number of PCM samples - matching sampling frequency. */ +#define MSBC_DECODE_ERROR_PLC 1 + /* Code protected 2-bit sequence numbers (SN0 and SN1) used * by the msbc_encode() function. */ static const uint8_t sn[][2] = { @@ -72,7 +84,9 @@ int msbc_init(struct esco_msbc *msbc) { goto fail; if (ffb_init_uint8_t(&msbc->data, sizeof(esco_msbc_frame_t) * 3) == -1) goto fail; - if (ffb_init_int16_t(&msbc->pcm, MSBC_CODESAMPLES * 2) == -1) + /* Allocate buffer for 1 decoded frame, optional 3 PLC frames and + * some extra frames to account for async PCM samples reading. */ + if (ffb_init_int16_t(&msbc->pcm, MSBC_CODESAMPLES * 6) == -1) goto fail; } @@ -100,6 +114,11 @@ int msbc_init(struct esco_msbc *msbc) { msbc->seq_number = 0; msbc->frames = 0; + /* Initialize PLC context. When calling with non-NULL parameter, + * this function does not allocate anything - there is no need + * to call plc_free(). */ + plc_init(&msbc->plc); + msbc->initialized = true; return 0; @@ -131,7 +150,6 @@ ssize_t msbc_decode(struct esco_msbc *msbc) { const uint8_t *input = msbc->data.data; size_t input_len = ffb_blen_out(&msbc->data); - int16_t *output = msbc->pcm.tail; size_t output_len = ffb_blen_in(&msbc->pcm); ssize_t rv = 0; @@ -140,9 +158,10 @@ ssize_t msbc_decode(struct esco_msbc *msbc) { input += tmp - input_len; /* Skip decoding if there is not enough input data or the output - * buffer is not big enough to hold decoded PCM samples.*/ + * buffer is not big enough to hold decoded PCM samples and PCM + * samples reconstructed with PLC (up to 3 mSBC frames). */ if (input_len < sizeof(*frame) || - output_len < MSBC_CODESIZE) + output_len < MSBC_CODESIZE * (1 + 3)) goto final; esco_h2_header_t h2; @@ -155,22 +174,49 @@ ssize_t msbc_decode(struct esco_msbc *msbc) { msbc->seq_number = _seq; } else if (_seq != ++msbc->seq_number) { - warn("Missing mSBC packet: %u != %u", _seq, msbc->seq_number); + + /* In case of missing mSBC frames (we can detect up to 3 consecutive + * missing frames) use PLC for PCM samples reconstruction. */ + + uint8_t missing = (_seq + ESCO_H2_SN_MAX - msbc->seq_number) % ESCO_H2_SN_MAX; + warn("Missing mSBC packets (%u != %u): %u", _seq, msbc->seq_number, missing); + msbc->seq_number = _seq; - /* TODO: Implement PLC. */ + + plc_fillin(&msbc->plc, msbc->pcm.tail, missing * MSBC_CODESAMPLES); + ffb_seek(&msbc->pcm, missing * MSBC_CODESAMPLES); + rv += missing * MSBC_CODESAMPLES; + } ssize_t len; if ((len = sbc_decode(&msbc->sbc, frame->payload, sizeof(frame->payload), - output, output_len, NULL)) < 0) { + msbc->pcm.tail, output_len, NULL)) < 0) { + + /* Move forward one byte to avoid getting stuck in + * decoding the same mSBC packet all over again. */ input += 1; + +#if MSBC_DECODE_ERROR_PLC + + warn("Couldn't decode mSBC frame: %s", sbc_strerror(len)); + plc_fillin(&msbc->plc, msbc->pcm.tail, MSBC_CODESAMPLES); + ffb_seek(&msbc->pcm, MSBC_CODESAMPLES); + rv += MSBC_CODESAMPLES; + +#else rv = len; +#endif + goto final; } + /* record PCM history and blend new data after PLC */ + plc_rx(&msbc->plc, msbc->pcm.tail, MSBC_CODESAMPLES); + ffb_seek(&msbc->pcm, MSBC_CODESAMPLES); input += sizeof(*frame); - rv = MSBC_CODESAMPLES; + rv += MSBC_CODESAMPLES; final: /* Reshuffle remaining data to the beginning of the buffer. */ diff --git a/src/codec-msbc.h b/src/codec-msbc.h index 7b7e7e4de..b793f6ad2 100644 --- a/src/codec-msbc.h +++ b/src/codec-msbc.h @@ -1,6 +1,6 @@ /* * BlueALSA - codec-msbc.h - * Copyright (c) 2016-2021 Arkadiusz Bokowy + * Copyright (c) 2016-2022 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * @@ -22,6 +22,7 @@ #include #include +#include #include "shared/ffb.h" @@ -32,6 +33,7 @@ #define MSBC_FRAMELEN 57 #define ESCO_H2_SYNCWORD 0x801 +#define ESCO_H2_SN_MAX 0x4 #define ESCO_H2_GET_SYNCWORD(h2) ((h2) & 0xFFF) #define ESCO_H2_GET_SN0(h2) (((h2) >> 12) & 0x3) #define ESCO_H2_GET_SN1(h2) (((h2) >> 14) & 0x3) @@ -62,6 +64,9 @@ struct esco_msbc { /* number of processed frames */ size_t frames; + /* packet loss concealment */ + plc_state_t plc; + /* Determine whether structure has been initialized. This field is * used for reinitialization - it makes msbc_init() idempotent. */ bool initialized; diff --git a/src/sco.c b/src/sco.c index 3a584a36d..f23cb1a17 100644 --- a/src/sco.c +++ b/src/sco.c @@ -369,41 +369,42 @@ static void *sco_msbc_enc_thread(struct ba_transport_thread *th) { ffb_seek(&msbc.pcm, samples); - int err; - if ((err = msbc_encode(&msbc)) < 0) { - warn("Couldn't encode mSBC: %s", sbc_strerror(err)); - ffb_rewind(&msbc.pcm); - } + while (ffb_len_out(&msbc.pcm) >= MSBC_CODESAMPLES) { - if (msbc.frames == 0) - continue; + int err; + if ((err = msbc_encode(&msbc)) < 0) { + error("mSBC encoding error: %s", sbc_strerror(err)); + break; + } - uint8_t *data = msbc.data.data; - size_t data_len = ffb_blen_out(&msbc.data); + uint8_t *data = msbc.data.data; + size_t data_len = ffb_blen_out(&msbc.data); - while (data_len >= mtu_write) { + while (data_len >= mtu_write) { - ssize_t len; - if ((len = io_bt_write(th, data, mtu_write)) <= 0) { - if (len == -1) - error("BT write error: %s", strerror(errno)); - goto exit; - } + ssize_t len; + if ((len = io_bt_write(th, data, mtu_write)) <= 0) { + if (len == -1) + error("BT write error: %s", strerror(errno)); + goto exit; + } - data += len; - data_len -= len; + data += len; + data_len -= len; - } + } - /* keep data transfer at a constant bit rate */ - asrsync_sync(&io.asrs, msbc.frames * MSBC_CODESAMPLES); - /* update busy delay (encoding overhead) */ - pcm->delay = asrsync_get_busy_usec(&io.asrs) / 100; + /* keep data transfer at a constant bit rate */ + asrsync_sync(&io.asrs, msbc.frames * MSBC_CODESAMPLES); + /* update busy delay (encoding overhead) */ + pcm->delay = asrsync_get_busy_usec(&io.asrs) / 100; - /* Move unprocessed data to the front of our linear - * buffer and clear the mSBC frame counter. */ - ffb_shift(&msbc.data, ffb_blen_out(&msbc.data) - data_len); - msbc.frames = 0; + /* Move unprocessed data to the front of our linear + * buffer and clear the mSBC frame counter. */ + ffb_shift(&msbc.data, ffb_blen_out(&msbc.data) - data_len); + msbc.frames = 0; + + } } @@ -452,8 +453,8 @@ static void *sco_msbc_dec_thread(struct ba_transport_thread *th) { int err; if ((err = msbc_decode(&msbc)) < 0) { - warn("Couldn't decode mSBC: %s", sbc_strerror(err)); - ffb_rewind(&msbc.data); + error("mSBC decoding error: %s", sbc_strerror(err)); + continue; } ssize_t samples; diff --git a/test/Makefile.am b/test/Makefile.am index 4a1797d50..667015180 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -224,7 +224,8 @@ AM_CFLAGS = \ @LIBUNWIND_CFLAGS@ \ @MPG123_CFLAGS@ \ @SBC_CFLAGS@ \ - @SNDFILE_CFLAGS@ + @SNDFILE_CFLAGS@ \ + @SPANDSP_CFLAGS@ LDADD = \ @AAC_LIBS@ \ @@ -243,4 +244,5 @@ LDADD = \ @MP3LAME_LIBS@ \ @MPG123_LIBS@ \ @SBC_LIBS@ \ - @SNDFILE_LIBS@ + @SNDFILE_LIBS@ \ + @SPANDSP_LIBS@ diff --git a/test/test-io.c b/test/test-io.c index 03dc1de31..dddf9b182 100644 --- a/test/test-io.c +++ b/test/test-io.c @@ -1,6 +1,6 @@ /* * test-io.c - * Copyright (c) 2016-2021 Arkadiusz Bokowy + * Copyright (c) 2016-2022 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * @@ -13,7 +13,6 @@ #endif #include -#include #include #include #include @@ -27,6 +26,7 @@ #include #include +#include #include #include #if ENABLE_LDAC @@ -48,7 +48,6 @@ #include "io.h" #include "rtp.h" #include "sco.h" -#include "utils.h" #include "shared/a2dp-codecs.h" #include "shared/defs.h" #include "shared/log.h" @@ -66,6 +65,11 @@ #include "inc/btd.inc" #include "inc/sine.inc" +#define CHECK_VERSION ( \ + (CHECK_MAJOR_VERSION << 16 & 0xff0000) | \ + (CHECK_MINOR_VERSION << 8 & 0x00ff00) | \ + (CHECK_MICRO_VERSION << 0 & 0x0000ff)) + int bluealsa_dbus_pcm_register(struct ba_transport_pcm *pcm) { debug("%s: %p", __func__, (void *)pcm); return 0; } void bluealsa_dbus_pcm_update(struct ba_transport_pcm *pcm, unsigned int mask) { @@ -665,6 +669,8 @@ START_TEST(test_a2dp_sbc) { #if ENABLE_MP3LAME START_TEST(test_a2dp_mp3) { + config_mp3_44100_stereo.vbr = enable_vbr_mode ? 1 : 0; + struct ba_transport_type ttype = { .profile = BA_TRANSPORT_PROFILE_A2DP_SOURCE, .codec = A2DP_CODEC_MPEG12 }; @@ -694,6 +700,10 @@ START_TEST(test_a2dp_mp3) { #if ENABLE_AAC START_TEST(test_a2dp_aac) { + config.aac_afterburner = true; + config.aac_prefer_vbr = enable_vbr_mode; + config_aac_44100_stereo.vbr = enable_vbr_mode ? 1 : 0; + struct ba_transport_type ttype = { .profile = BA_TRANSPORT_PROFILE_A2DP_SOURCE, .codec = A2DP_CODEC_MPEG24 }; @@ -852,6 +862,9 @@ START_TEST(test_a2dp_lc3plus) { #if ENABLE_LDAC START_TEST(test_a2dp_ldac) { + config.ldac_abr = true; + config.ldac_eqmid = LDACBT_EQMID_HQ; + struct ba_transport_type ttype = { .profile = BA_TRANSPORT_PROFILE_A2DP_SOURCE, .codec = A2DP_CODEC_VENDOR_LDAC }; @@ -904,6 +917,9 @@ START_TEST(test_sco_cvsd) { #if ENABLE_MSBC START_TEST(test_sco_msbc) { + adapter->hci.features[2] = LMP_TRSP_SCO; + adapter->hci.features[3] = LMP_ESCO; + struct ba_transport_type ttype = { .profile = BA_TRANSPORT_PROFILE_HFP_AG, .codec = HFP_CODEC_MSBC }; @@ -923,6 +939,42 @@ START_TEST(test_sco_msbc) { int main(int argc, char *argv[]) { + const struct { + const char *name; +#if CHECK_VERSION >= 0x000D00 /* 0.13.0 */ + const TTest *tf; +#else + TFun tf; +#endif + } codecs[] = { + { a2dp_codecs_codec_id_to_string(A2DP_CODEC_SBC), test_a2dp_sbc }, +#if ENABLE_MP3LAME + { a2dp_codecs_codec_id_to_string(A2DP_CODEC_MPEG12), test_a2dp_mp3 }, +#endif +#if ENABLE_AAC + { a2dp_codecs_codec_id_to_string(A2DP_CODEC_MPEG24), test_a2dp_aac }, +#endif +#if ENABLE_APTX + { a2dp_codecs_codec_id_to_string(A2DP_CODEC_VENDOR_APTX), test_a2dp_aptx }, +#endif +#if ENABLE_APTX_HD + { a2dp_codecs_codec_id_to_string(A2DP_CODEC_VENDOR_APTX_HD), test_a2dp_aptx_hd }, +#endif +#if ENABLE_FASTSTREAM + { a2dp_codecs_codec_id_to_string(A2DP_CODEC_VENDOR_FASTSTREAM), test_a2dp_faststream }, +#endif +#if ENABLE_LC3PLUS + { a2dp_codecs_codec_id_to_string(A2DP_CODEC_VENDOR_LC3PLUS), test_a2dp_lc3plus }, +#endif +#if ENABLE_LDAC + { a2dp_codecs_codec_id_to_string(A2DP_CODEC_VENDOR_LDAC), test_a2dp_ldac }, +#endif + { hfp_codec_id_to_string(HFP_CODEC_CVSD), test_sco_cvsd }, +#if ENABLE_MSBC + { hfp_codec_id_to_string(HFP_CODEC_MSBC), test_sco_msbc }, +#endif + }; + int opt; const char *opts = "ha:d"; struct option longopts[] = { @@ -935,32 +987,6 @@ int main(int argc, char *argv[]) { { 0, 0, 0, 0 }, }; - struct { - const char *name; - unsigned int flag; - } codecs[] = { -#define TEST_CODEC_SBC (1 << 0) - { a2dp_codecs_codec_id_to_string(A2DP_CODEC_SBC), TEST_CODEC_SBC }, -#define TEST_CODEC_MP3 (1 << 1) - { a2dp_codecs_codec_id_to_string(A2DP_CODEC_MPEG12), TEST_CODEC_MP3 }, -#define TEST_CODEC_AAC (1 << 2) - { a2dp_codecs_codec_id_to_string(A2DP_CODEC_MPEG24), TEST_CODEC_AAC }, -#define TEST_CODEC_APTX (1 << 3) - { a2dp_codecs_codec_id_to_string(A2DP_CODEC_VENDOR_APTX), TEST_CODEC_APTX }, -#define TEST_CODEC_APTX_HD (1 << 4) - { a2dp_codecs_codec_id_to_string(A2DP_CODEC_VENDOR_APTX_HD), TEST_CODEC_APTX_HD }, -#define TEST_CODEC_FASTSTREAM (1 << 5) - { a2dp_codecs_codec_id_to_string(A2DP_CODEC_VENDOR_FASTSTREAM), TEST_CODEC_FASTSTREAM }, -#define TEST_CODEC_LC3PLUS (1 << 6) - { a2dp_codecs_codec_id_to_string(A2DP_CODEC_VENDOR_LC3PLUS), TEST_CODEC_LC3PLUS }, -#define TEST_CODEC_LDAC (1 << 7) - { a2dp_codecs_codec_id_to_string(A2DP_CODEC_VENDOR_LDAC), TEST_CODEC_LDAC }, -#define TEST_CODEC_CVSD (1 << 8) - { hfp_codec_id_to_string(HFP_CODEC_CVSD), TEST_CODEC_CVSD }, -#define TEST_CODEC_MSBC (1 << 9) - { hfp_codec_id_to_string(HFP_CODEC_MSBC), TEST_CODEC_MSBC }, - }; - while ((opt = getopt_long(argc, argv, opts, longopts, NULL)) != -1) switch (opt) { case 'h' /* --help */ : @@ -996,14 +1022,14 @@ int main(int argc, char *argv[]) { } unsigned int enabled_codecs = 0xFFFF; - size_t i; if (optind != argc) enabled_codecs = 0; + for (; optind < argc; optind++) - for (i = 0; i < ARRAYSIZE(codecs); i++) + for (size_t i = 0; i < ARRAYSIZE(codecs); i++) if (strcasecmp(argv[optind], codecs[i].name) == 0) - enabled_codecs |= codecs[i].flag; + enabled_codecs |= 1 << i; if (input_bt_file != NULL) { @@ -1028,9 +1054,9 @@ int main(int argc, char *argv[]) { } enabled_codecs = 0; - for (i = 0; i < ARRAYSIZE(codecs); i++) + for (size_t i = 0; i < ARRAYSIZE(codecs); i++) if (strcmp(codec, codecs[i].name) == 0) - enabled_codecs = codecs[i].flag; + enabled_codecs = 1 << i; } @@ -1050,48 +1076,9 @@ int main(int argc, char *argv[]) { if (input_bt_file != NULL || input_pcm_file != NULL) tcase_set_timeout(tc, aging_duration + 3600); - if (enabled_codecs & TEST_CODEC_SBC) - tcase_add_test(tc, test_a2dp_sbc); -#if ENABLE_MP3LAME - config_mp3_44100_stereo.vbr = enable_vbr_mode ? 1 : 0; - if (enabled_codecs & TEST_CODEC_MP3) - tcase_add_test(tc, test_a2dp_mp3); -#endif -#if ENABLE_AAC - config.aac_afterburner = true; - config.aac_prefer_vbr = enable_vbr_mode; - config_aac_44100_stereo.vbr = enable_vbr_mode ? 1 : 0; - if (enabled_codecs & TEST_CODEC_AAC) - tcase_add_test(tc, test_a2dp_aac); -#endif -#if ENABLE_APTX - if (enabled_codecs & TEST_CODEC_APTX) - tcase_add_test(tc, test_a2dp_aptx); -#endif -#if ENABLE_APTX_HD - if (enabled_codecs & TEST_CODEC_APTX_HD) - tcase_add_test(tc, test_a2dp_aptx_hd); -#endif -#if ENABLE_FASTSTREAM - if (enabled_codecs & TEST_CODEC_FASTSTREAM) - tcase_add_test(tc, test_a2dp_faststream); -#endif -#if ENABLE_LC3PLUS - if (enabled_codecs & TEST_CODEC_LC3PLUS) - tcase_add_test(tc, test_a2dp_lc3plus); -#endif -#if ENABLE_LDAC - config.ldac_abr = true; - config.ldac_eqmid = LDACBT_EQMID_HQ; - if (enabled_codecs & TEST_CODEC_LDAC) - tcase_add_test(tc, test_a2dp_ldac); -#endif - if (enabled_codecs & TEST_CODEC_CVSD) - tcase_add_test(tc, test_sco_cvsd); -#if ENABLE_MSBC - if (enabled_codecs & TEST_CODEC_MSBC) - tcase_add_test(tc, test_sco_msbc); -#endif + for (size_t i = 0; i < ARRAYSIZE(codecs); i++) + if (enabled_codecs & (1 << i)) + tcase_add_test(tc, codecs[i].tf); srunner_run_all(sr, CK_ENV); int nf = srunner_ntests_failed(sr); diff --git a/test/test-msbc.c b/test/test-msbc.c index 07c5bb4af..904ea073b 100644 --- a/test/test-msbc.c +++ b/test/test-msbc.c @@ -1,6 +1,6 @@ /* * test-msbc.c - * Copyright (c) 2016-2021 Arkadiusz Bokowy + * Copyright (c) 2016-2022 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -18,6 +19,7 @@ #include "codec-msbc.h" #include "shared/defs.h" #include "shared/ffb.h" +#include "shared/log.h" #include "inc/sine.inc" #include "../src/codec-msbc.c" @@ -86,17 +88,17 @@ START_TEST(test_msbc_find_h2_header) { START_TEST(test_msbc_encode_decode) { - struct esco_msbc msbc = { 0 }; - int16_t sine[1024]; - size_t len; - size_t i; - int rv; - + int16_t sine[8 * MSBC_CODESAMPLES]; snd_pcm_sine_s16_2le(sine, ARRAYSIZE(sine), 1, 0, 1.0 / 128); uint8_t data[sizeof(sine)]; uint8_t *data_tail = data; + struct esco_msbc msbc = { 0 }; + size_t len; + size_t i; + int rv; + msbc.initialized = false; ck_assert_int_eq(msbc_init(&msbc), 0); for (rv = 1, i = 0; rv > 0;) { @@ -110,7 +112,7 @@ START_TEST(test_msbc_encode_decode) { len = ffb_blen_out(&msbc.data); memcpy(data_tail, msbc.data.data, len); - ffb_shift(&msbc.data, len); + ffb_rewind(&msbc.data); data_tail += len; } @@ -135,7 +137,7 @@ START_TEST(test_msbc_encode_decode) { len = ffb_len_out(&msbc.pcm); memcpy(pcm_tail, msbc.pcm.data, len * msbc.pcm.size); - ffb_shift(&msbc.pcm, len); + ffb_rewind(&msbc.pcm); pcm_tail += len; } @@ -146,6 +148,75 @@ START_TEST(test_msbc_encode_decode) { } END_TEST +START_TEST(test_msbc_decode_plc) { + + int16_t sine[18 * MSBC_CODESAMPLES]; + snd_pcm_sine_s16_2le(sine, ARRAYSIZE(sine), 1, 0, 1.0 / 128); + + struct esco_msbc msbc = { .initialized = false }; + ck_assert_int_eq(msbc_init(&msbc), 0); + + uint8_t data[sizeof(sine)]; + uint8_t *data_tail = data; + + debug("Simulating mSBC packet loss events"); + + int rv; + size_t counter, i; + for (rv = 1, counter = i = 0; rv > 0; counter++) { + + size_t len = MIN(ARRAYSIZE(sine) - i, ffb_len_in(&msbc.pcm)); + memcpy(msbc.pcm.tail, &sine[i], len * msbc.pcm.size); + ffb_seek(&msbc.pcm, len); + i += len; + + rv = msbc_encode(&msbc); + + len = ffb_blen_out(&msbc.data); + memcpy(data_tail, msbc.data.data, len); + ffb_rewind(&msbc.data); + + /* simulate packet loss */ + if (counter == 2 || + (6 <= counter && counter <= 8) || + /* 4 packets (undetectable) */ + (12 <= counter && counter <= 15)) { + fprintf(stderr, "_"); + continue; + } + + fprintf(stderr, "x"); + data_tail += len; + + } + + fprintf(stderr, "\n"); + + /* reinitialize encoder/decoder handler */ + ck_assert_int_eq(msbc_init(&msbc), 0); + + size_t samples = 0; + for (rv = 1, i = 0; rv > 0; ) { + + size_t len = MIN((data_tail - data) - i, ffb_blen_in(&msbc.data)); + memcpy(msbc.data.tail, &data[i], len); + ffb_seek(&msbc.data, len); + i += len; + + rv = msbc_decode(&msbc); + + samples += ffb_len_out(&msbc.pcm); + ffb_rewind(&msbc.pcm); + + } + + /* we should recover all except consecutive 4 frames */ + ck_assert_int_eq(samples, (18 - 4) * MSBC_CODESAMPLES); + + msbc_finish(&msbc); + +} END_TEST + int main(void) { Suite *s = suite_create(__FILE__); @@ -157,6 +228,7 @@ int main(void) { tcase_add_test(tc, test_msbc_init); tcase_add_test(tc, test_msbc_find_h2_header); tcase_add_test(tc, test_msbc_encode_decode); + tcase_add_test(tc, test_msbc_decode_plc); srunner_run_all(sr, CK_ENV); int nf = srunner_ntests_failed(sr);