From 2459ed4686eaef0a19dfa3f330a960813c5f60de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pali=20Roh=C3=A1r?= Date: Sat, 11 Apr 2020 15:04:01 +0200 Subject: [PATCH] New version 0.2.0 with lot of fixes * Introduce a new library functions aptx_decode_sync() and aptx_decode_sync_finish() for synchronization and decoding partially corrupted continuous stream * Improve synchronization support in openaptxdec utility via these new library functions * Fix openaptxdec utility to not output data which are result of decoding the trailing aptx padding from the last aptX sample * Fix exit codes from utilities, returns 1 when error occurred * Make utilities to be compatible with Windows systems, set stdin and stdout to binary mode * Make source code to be compatible with ISO C90, C99, C11, C17, ISO C++ 1998, 2011, 2014, 2017, 2020 and also with MSVC 6.0 (and new), but there is one exception that stdint.h header file with basic fixed width integer types needs to be provided * Fix integer variable types, size, signedness and const qualifiers * Make Makefile to be POSIX compatible * Do not install static executables * Generate and install a new pkg-config file for library * Provide and install static library libopenaptx.a * Enable -O3 optimizations in Makefile by default * Update parameters for sox/play examples in README and help --- Makefile | 110 +++++++--- README | 18 +- openaptx.c | 553 +++++++++++++++++++++++++++++++++----------------- openaptx.h | 90 +++++--- openaptxdec.c | 166 ++++++++------- openaptxenc.c | 44 ++-- 6 files changed, 643 insertions(+), 338 deletions(-) diff --git a/Makefile b/Makefile index 42303b4..d59800f 100644 --- a/Makefile +++ b/Makefile @@ -1,47 +1,93 @@ -MAJOR := 0 -MINOR := 1 -PATCH := 0 +.POSIX: +.SUFFIXES: +.PHONY: all clean install uninstall -PREFIX := /usr/local -BINDIR := bin -LIBDIR := lib -INCDIR := include +RM = rm -f +CP = cp -a +LNS = ln -sf +MKDIR = mkdir -p +PRINTF = printf -LIBNAME := libopenaptx.so -SONAME := $(LIBNAME).$(MAJOR) -FILENAME := $(SONAME).$(MINOR).$(PATCH) +CFLAGS = -W -Wall -O3 +LDFLAGS = -s +ARFLAGS = -rcs -UTILITIES := openaptxenc openaptxdec openaptxenc-static openaptxdec-static +PREFIX = /usr/local +BINDIR = bin +LIBDIR = lib +INCDIR = include +PKGDIR = $(LIBDIR)/pkgconfig -HEADERS := openaptx.h -SOURCES := openaptx.c +NAME = openaptx +MAJOR = 0 +MINOR = 2 +PATCH = 0 -BUILD := $(FILENAME) $(SONAME) $(LIBNAME) $(UTILITIES) +LIBNAME = lib$(NAME).so +SONAME = $(LIBNAME).$(MAJOR) +SOFILENAME = $(SONAME).$(MINOR).$(PATCH) +ANAME = lib$(NAME).a +PCNAME = lib$(NAME).pc + +UTILITIES = $(NAME)enc $(NAME)dec +STATIC_UTILITIES = $(NAME)enc.static $(NAME)dec.static + +HEADERS = $(NAME).h +SOURCES = $(NAME).c +AOBJECTS = $(NAME).o +IOBJECTS = $(NAME)enc.o $(NAME)dec.o + +BUILD = $(SOFILENAME) $(SONAME) $(LIBNAME) $(ANAME) $(AOBJECTS) $(IOBJECTS) $(UTILITIES) $(STATIC_UTILITIES) all: $(BUILD) +clean: + $(RM) $(BUILD) + install: $(BUILD) - mkdir -p $(DESTDIR)/$(PREFIX)/$(LIBDIR) - cp -a $(FILENAME) $(SONAME) $(LIBNAME) $(DESTDIR)/$(PREFIX)/$(LIBDIR) - mkdir -p $(DESTDIR)/$(PREFIX)/$(BINDIR) - cp -a $(UTILITIES) $(DESTDIR)/$(PREFIX)/$(BINDIR) - mkdir -p $(DESTDIR)/$(PREFIX)/$(INCDIR) - cp -a $(HEADERS) $(DESTDIR)/$(PREFIX)/$(INCDIR) - -$(FILENAME): $(SOURCES) $(HEADERS) - $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -I. -shared -fPIC -Wl,-soname,$(SONAME) -o $@ $(SOURCES) + $(MKDIR) $(DESTDIR)$(PREFIX)/$(LIBDIR) + $(CP) $(SOFILENAME) $(SONAME) $(LIBNAME) $(ANAME) $(DESTDIR)$(PREFIX)/$(LIBDIR) + $(MKDIR) $(DESTDIR)$(PREFIX)/$(BINDIR) + $(CP) $(UTILITIES) $(DESTDIR)$(PREFIX)/$(BINDIR) + $(MKDIR) $(DESTDIR)$(PREFIX)/$(INCDIR) + $(CP) $(HEADERS) $(DESTDIR)$(PREFIX)/$(INCDIR) + $(MKDIR) $(DESTDIR)$(PREFIX)/$(PKGDIR) + $(PRINTF) 'prefix=%s\nexec_prefix=$${prefix}\nlibdir=$${exec_prefix}/%s\nincludedir=$${prefix}/%s\n\n' $(PREFIX) $(LIBDIR) $(INCDIR) > $(DESTDIR)$(PREFIX)/$(PKGDIR)/$(PCNAME) + $(PRINTF) 'Name: lib%s\nDescription: Open Source aptX codec library\nVersion: %u.%u.%u\n' $(NAME) $(MAJOR) $(MINOR) $(PATCH) >> $(DESTDIR)$(PREFIX)/$(PKGDIR)/$(PCNAME) + $(PRINTF) 'Libs: -Wl,-rpath=$${libdir} -L$${libdir} -l%s\nCflags: -I$${includedir}\n' $(NAME) >> $(DESTDIR)$(PREFIX)/$(PKGDIR)/$(PCNAME) + +uninstall: + for f in $(SOFILENAME) $(SONAME) $(LIBNAME) $(ANAME); do $(RM) $(DESTDIR)$(PREFIX)/$(LIBDIR)/$$f; done + for f in $(UTILITIES); do $(RM) $(DESTDIR)$(PREFIX)/$(BINDIR)/$$f; done + for f in $(HEADERS); do $(RM) $(DESTDIR)$(PREFIX)/$(INCDIR)/$$f; done + $(RM) $(DESTDIR)$(PREFIX)/$(PKGDIR)/$(PCNAME) + +$(UTILITIES): $(LIBNAME) -$(SONAME): $(FILENAME) - ln -sf $< $@ +$(STATIC_UTILITIES): $(ANAME) + +$(AOBJECTS) $(IOBJECTS): $(HEADERS) $(LIBNAME): $(SONAME) - ln -sf $< $@ + $(LNS) $(SONAME) $@ -%-static: %.c $(SOURCES) $(HEADERS) - $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -I. -o $@ $< $(SOURCES) +$(SONAME): $(SOFILENAME) + $(LNS) $(SOFILENAME) $@ -%: %.c $(LIBNAME) $(HEADERS) - $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -I. -o $@ $< $(LIBNAME) +$(SOFILENAME): $(SOURCES) $(HEADERS) + $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -I. -shared -fPIC -Wl,-soname,$(SONAME) -o $@ $(SOURCES) -clean: - $(RM) $(BUILD) +$(ANAME): $(AOBJECTS) + $(RM) $@ + $(AR) $(ARFLAGS) $@ $(AOBJECTS) + +.SUFFIXES: .o .c .static + +.o: + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LIBNAME) + +.o.static: + $(CC) $(CFLAGS) $(LDFLAGS) -static -o $@ $< $(ANAME) + +.c.o: + $(CC) $(CFLAGS) $(CPPFLAGS) -I. -c -o $@ $< diff --git a/README b/README index 67709ac..18d1ac7 100644 --- a/README +++ b/README @@ -4,27 +4,35 @@ mainly used in Bluetooth A2DP profile. It provides dynamic linked shared library libopenaptx.so and simple command line utilities openaptxenc and openaptxdec for encoding and decoding operations. +Documentation for shared library is provided in C header file openaptx.h. + There is support for aptX and aptX HD codec variants. Both variants operates on a raw 24 bit signed stereo audio samples. aptX provides fixed compress ratio 6:1 and aptX HD fixed compress ratio 4:1. For building and installing into system simply run: make install. For building -without installing run: LD_RUN_PATH='$ORIGIN' make +without installing run: LD_RUN_PATH='$ORIGIN' make. For producing windows builds +run: make SOFILENAME=openaptx0.dll. + +It is suggested to compile library with -O3 optimizations (enabled by default +when env variable CFLAGS is not set) and -mavx2 switch (not enabled by default, +needs CPU with AVX2: Intel Haswell or AMD Excavator) as it provides significant +boost to the performance. Usage of command line utilities together with sox for resampling or playing: To convert Wave audio file sample.wav into aptX audio file sample.aptx run: -$ sox sample.wav -t raw -r 44.1k -s -3 -c 2 - | openaptxenc > sample.aptx +$ sox sample.wav -t raw -r 44.1k -L -e s -b 24 -c 2 - | openaptxenc > sample.aptx To convert aptX audio file sample.aptx into Wave audio file sample.wav run: -$ openaptxdec < sample.aptx | sox -t raw -r 44.1k -s -3 -c 2 - sample.wav +$ openaptxdec < sample.aptx | sox -t raw -r 44.1k -L -e s -b 24 -c 2 - sample.wav To convert MP3 audio file sample.mp3 into aptX HD audio file sample.aptxhd run: -$ sox sample.mp3 -t raw -r 44.1k -s -3 -c 2 - | openaptxenc --hd > sample.aptxhd +$ sox sample.mp3 -t raw -r 44.1k -L -e s -b 24 -c 2 - | openaptxenc --hd > sample.aptxhd To play aptX HD audio file sample.aptxhd run: -$ openaptxdec --hd < sample.aptxhd | play -t raw -r 44.1k -s -3 -c 2 - +$ openaptxdec --hd < sample.aptxhd | play -t raw -r 44.1k -L -e s -b 24 -c 2 - diff --git a/openaptx.c b/openaptx.c index acf1218..2b1b16d 100644 --- a/openaptx.c +++ b/openaptx.c @@ -1,7 +1,7 @@ /* * Open Source implementation of Audio Processing Technology codec (aptX) - * Copyright (C) 2017 Aurelien Jacobs - * Copyright (C) 2018 Pali Rohár + * Copyright (C) 2017 Aurelien Jacobs + * Copyright (C) 2018-2020 Pali Rohár * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -15,7 +15,7 @@ * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include @@ -24,41 +24,46 @@ #include +#if (!defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901L) && !defined(inline) +#define inline +#endif + #define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a))) #define DIFFSIGN(x,y) (((x)>(y)) - ((x)<(y))) -/** +/* * Clip a signed integer into the -(2^p),(2^p-1) range. * @param a value to clip * @param p bit position to clip at * @return clipped value */ -static inline int clip_intp2(int a, int p) +static inline int32_t clip_intp2(int32_t a, unsigned p) { - if (((unsigned)a + (1 << p)) & ~((2 << p) - 1)) + if (((uint32_t)a + ((uint32_t)1 << p)) & ~(((uint32_t)2 << p) - 1)) return (a >> 31) ^ ((1 << p) - 1); else return a; } -/** +/* * Clip a signed integer value into the amin-amax range. * @param a value to clip * @param amin minimum value of the clip range * @param amax maximum value of the clip range * @return clipped value */ -static inline int clip(int a, int amin, int amax) +static inline int32_t clip(int32_t a, int32_t amin, int32_t amax) { if (a < amin) return amin; else if (a > amax) return amax; else return a; } -static inline int sign_extend(int val, unsigned bits) +static inline int32_t sign_extend(int32_t val, unsigned bits) { - unsigned shift = 8 * sizeof(int) - bits; - union { unsigned u; int s; } v = { (unsigned) val << shift }; + const unsigned shift = 8 * sizeof(val) - bits; + union { uint32_t u; int32_t s; } v; + v.u = (uint32_t)val << shift; return v.s >> shift; } @@ -68,21 +73,14 @@ enum channels { NB_CHANNELS }; -enum subbands { - LF, // Low Frequency (0-5.5 kHz) - MLF, // Medium-Low Frequency (5.5-11kHz) - MHF, // Medium-High Frequency (11-16.5kHz) - HF, // High Frequency (16.5-22kHz) - NB_SUBBANDS -}; - +#define NB_SUBBANDS 4 #define NB_FILTERS 2 #define FILTER_TAPS 16 #define LATENCY_SAMPLES 90 struct aptx_filter_signal { - uint8_t pos; int32_t buffer[2*FILTER_TAPS]; + uint8_t pos; }; struct aptx_QMF_analysis { @@ -122,14 +120,18 @@ struct aptx_channel { struct aptx_quantize quantize[NB_SUBBANDS]; struct aptx_invert_quantize invert_quantize[NB_SUBBANDS]; struct aptx_prediction prediction[NB_SUBBANDS]; -} ; +}; struct aptx_context { + size_t decode_sync_packets; + size_t decode_dropped; + struct aptx_channel channels[NB_CHANNELS]; uint8_t hd; uint8_t sync_idx; uint8_t encode_remaining; uint8_t decode_skip_leading; - struct aptx_channel channels[NB_CHANNELS]; + uint8_t decode_sync_buffer_len; + unsigned char decode_sync_buffer[6]; }; @@ -426,61 +428,93 @@ struct aptx_tables { const int16_t *quantize_factor_select_offset; int tables_size; int32_t factor_max; - int32_t prediction_order; + int prediction_order; }; -static const struct aptx_tables tables[2][NB_SUBBANDS] = { +static const struct aptx_tables all_tables[2][NB_SUBBANDS] = { { - [LF] = { quantize_intervals_LF, - invert_quantize_dither_factors_LF, - quantize_dither_factors_LF, - quantize_factor_select_offset_LF, - ARRAY_SIZE(quantize_intervals_LF), - 0x11FF, 24 }, - [MLF] = { quantize_intervals_MLF, - invert_quantize_dither_factors_MLF, - quantize_dither_factors_MLF, - quantize_factor_select_offset_MLF, - ARRAY_SIZE(quantize_intervals_MLF), - 0x14FF, 12 }, - [MHF] = { quantize_intervals_MHF, - invert_quantize_dither_factors_MHF, - quantize_dither_factors_MHF, - quantize_factor_select_offset_MHF, - ARRAY_SIZE(quantize_intervals_MHF), - 0x16FF, 6 }, - [HF] = { quantize_intervals_HF, - invert_quantize_dither_factors_HF, - quantize_dither_factors_HF, - quantize_factor_select_offset_HF, - ARRAY_SIZE(quantize_intervals_HF), - 0x15FF, 12 }, + { + /* Low Frequency (0-5.5 kHz) */ + quantize_intervals_LF, + invert_quantize_dither_factors_LF, + quantize_dither_factors_LF, + quantize_factor_select_offset_LF, + ARRAY_SIZE(quantize_intervals_LF), + 0x11FF, + 24 + }, + { + /* Medium-Low Frequency (5.5-11kHz) */ + quantize_intervals_MLF, + invert_quantize_dither_factors_MLF, + quantize_dither_factors_MLF, + quantize_factor_select_offset_MLF, + ARRAY_SIZE(quantize_intervals_MLF), + 0x14FF, + 12 + }, + { + /* Medium-High Frequency (11-16.5kHz) */ + quantize_intervals_MHF, + invert_quantize_dither_factors_MHF, + quantize_dither_factors_MHF, + quantize_factor_select_offset_MHF, + ARRAY_SIZE(quantize_intervals_MHF), + 0x16FF, + 6 + }, + { + /* High Frequency (16.5-22kHz) */ + quantize_intervals_HF, + invert_quantize_dither_factors_HF, + quantize_dither_factors_HF, + quantize_factor_select_offset_HF, + ARRAY_SIZE(quantize_intervals_HF), + 0x15FF, + 12 + }, }, { - [LF] = { hd_quantize_intervals_LF, - hd_invert_quantize_dither_factors_LF, - hd_quantize_dither_factors_LF, - hd_quantize_factor_select_offset_LF, - ARRAY_SIZE(hd_quantize_intervals_LF), - 0x11FF, 24 }, - [MLF] = { hd_quantize_intervals_MLF, - hd_invert_quantize_dither_factors_MLF, - hd_quantize_dither_factors_MLF, - hd_quantize_factor_select_offset_MLF, - ARRAY_SIZE(hd_quantize_intervals_MLF), - 0x14FF, 12 }, - [MHF] = { hd_quantize_intervals_MHF, - hd_invert_quantize_dither_factors_MHF, - hd_quantize_dither_factors_MHF, - hd_quantize_factor_select_offset_MHF, - ARRAY_SIZE(hd_quantize_intervals_MHF), - 0x16FF, 6 }, - [HF] = { hd_quantize_intervals_HF, - hd_invert_quantize_dither_factors_HF, - hd_quantize_dither_factors_HF, - hd_quantize_factor_select_offset_HF, - ARRAY_SIZE(hd_quantize_intervals_HF), - 0x15FF, 12 }, + { + /* Low Frequency (0-5.5 kHz) */ + hd_quantize_intervals_LF, + hd_invert_quantize_dither_factors_LF, + hd_quantize_dither_factors_LF, + hd_quantize_factor_select_offset_LF, + ARRAY_SIZE(hd_quantize_intervals_LF), + 0x11FF, + 24 + }, + { + /* Medium-Low Frequency (5.5-11kHz) */ + hd_quantize_intervals_MLF, + hd_invert_quantize_dither_factors_MLF, + hd_quantize_dither_factors_MLF, + hd_quantize_factor_select_offset_MLF, + ARRAY_SIZE(hd_quantize_intervals_MLF), + 0x14FF, + 12 + }, + { + /* Medium-High Frequency (11-16.5kHz) */ + hd_quantize_intervals_MHF, + hd_invert_quantize_dither_factors_MHF, + hd_quantize_dither_factors_MHF, + hd_quantize_factor_select_offset_MHF, + ARRAY_SIZE(hd_quantize_intervals_MHF), + 0x16FF, + 6 + }, + { + /* High Frequency (16.5-22kHz) */ + hd_quantize_intervals_HF, + hd_invert_quantize_dither_factors_HF, + hd_quantize_dither_factors_HF, + hd_quantize_factor_select_offset_HF, + ARRAY_SIZE(hd_quantize_intervals_HF), + 0x15FF, + 12 + }, } }; @@ -492,17 +526,17 @@ static const int16_t quantization_factors[32] = { }; -/* Rounded right shift with optionnal clipping */ +/* Rounded right shift with optional clipping */ #define RSHIFT_SIZE(size) \ -static inline int##size##_t rshift##size(int##size##_t value, int shift) \ +static inline int##size##_t rshift##size(int##size##_t value, unsigned shift) \ { \ - int##size##_t rounding = (int##size##_t)1 << (shift - 1); \ - int##size##_t mask = ((int##size##_t)1 << (shift + 1)) - 1; \ + const int##size##_t rounding = (int##size##_t)1 << (shift - 1); \ + const int##size##_t mask = ((int##size##_t)1 << (shift + 1)) - 1; \ return ((value + rounding) >> shift) - ((value & mask) == rounding); \ } \ -static inline int##size##_t rshift##size##_clip24(int##size##_t value, int shift) \ +static inline int32_t rshift##size##_clip24(int##size##_t value, unsigned shift) \ { \ - return clip_intp2(rshift##size(value, shift), 23); \ + return clip_intp2((int32_t)rshift##size(value, shift), 23); \ } RSHIFT_SIZE(32) RSHIFT_SIZE(64) @@ -510,24 +544,24 @@ RSHIFT_SIZE(64) static inline void aptx_update_codeword_history(struct aptx_channel *channel) { - int32_t cw = ((channel->quantize[0].quantized_sample & 3) << 0) + - ((channel->quantize[1].quantized_sample & 2) << 1) + - ((channel->quantize[2].quantized_sample & 1) << 3); - channel->codeword_history = (cw << 8) + (channel->codeword_history << 4); + const int32_t cw = ((channel->quantize[0].quantized_sample & 3) << 0) + + ((channel->quantize[1].quantized_sample & 2) << 1) + + ((channel->quantize[2].quantized_sample & 1) << 3); + channel->codeword_history = (cw << 8) + (int32_t)((uint32_t)channel->codeword_history << 4); } static void aptx_generate_dither(struct aptx_channel *channel) { - int subband; + unsigned subband; int64_t m; int32_t d; aptx_update_codeword_history(channel); m = (int64_t)5184443 * (channel->codeword_history >> 7); - d = (m << 2) + (m >> 22); + d = (int32_t)((m * 4) + (m >> 22)); for (subband = 0; subband < NB_SUBBANDS; subband++) - channel->dither[subband] = d << (23 - 5*subband); + channel->dither[subband] = (int32_t)((uint32_t)d << (23 - 5*subband)); channel->dither_parity = (d >> 25) & 1; } @@ -576,13 +610,13 @@ static inline void aptx_qmf_filter_signal_push(struct aptx_filter_signal *signal * Compute the convolution of the signal with the coefficients, and reduce * to 24 bits by applying the specified right shifting. */ -static inline int32_t aptx_qmf_convolution(struct aptx_filter_signal *signal, +static inline int32_t aptx_qmf_convolution(const struct aptx_filter_signal *signal, const int32_t coeffs[FILTER_TAPS], - int shift) + unsigned shift) { - int32_t *sig = &signal->buffer[signal->pos]; + const int32_t *sig = &signal->buffer[signal->pos]; int64_t e = 0; - int i; + unsigned i; for (i = 0; i < FILTER_TAPS; i++) e += (int64_t)sig[i] * (int64_t)coeffs[i]; @@ -598,13 +632,13 @@ static inline int32_t aptx_qmf_convolution(struct aptx_filter_signal *signal, */ static inline void aptx_qmf_polyphase_analysis(struct aptx_filter_signal signal[NB_FILTERS], const int32_t coeffs[NB_FILTERS][FILTER_TAPS], - int shift, + unsigned shift, const int32_t samples[NB_FILTERS], int32_t *low_subband_output, int32_t *high_subband_output) { int32_t subbands[NB_FILTERS]; - int i; + unsigned i; for (i = 0; i < NB_FILTERS; i++) { aptx_qmf_filter_signal_push(&signal[i], samples[NB_FILTERS-1-i]); @@ -623,10 +657,10 @@ static inline void aptx_qmf_polyphase_analysis(struct aptx_filter_signal signal[ */ static void aptx_qmf_tree_analysis(struct aptx_QMF_analysis *qmf, const int32_t samples[4], - int32_t subband_samples[4]) + int32_t subband_samples[NB_SUBBANDS]) { int32_t intermediate_samples[4]; - int i; + unsigned i; /* Split 4 input samples into 2 intermediate subbands downsampled to 2 samples */ for (i = 0; i < 2; i++) @@ -652,13 +686,13 @@ static void aptx_qmf_tree_analysis(struct aptx_QMF_analysis *qmf, */ static inline void aptx_qmf_polyphase_synthesis(struct aptx_filter_signal signal[NB_FILTERS], const int32_t coeffs[NB_FILTERS][FILTER_TAPS], - int shift, + unsigned shift, int32_t low_subband_input, int32_t high_subband_input, int32_t samples[NB_FILTERS]) { int32_t subbands[NB_FILTERS]; - int i; + unsigned i; subbands[0] = low_subband_input + high_subband_input; subbands[1] = low_subband_input - high_subband_input; @@ -675,11 +709,11 @@ static inline void aptx_qmf_polyphase_synthesis(struct aptx_filter_signal signal * So for each 4 subbands sample that goes in, a group of 4 samples goes out. */ static void aptx_qmf_tree_synthesis(struct aptx_QMF_analysis *qmf, - int32_t subband_samples[4], + const int32_t subband_samples[NB_SUBBANDS], int32_t samples[4]) { int32_t intermediate_samples[4]; - int i; + unsigned i; /* Join 4 subbands into 2 intermediate subbands upsampled to 2 samples. */ for (i = 0; i < 2; i++) @@ -700,7 +734,7 @@ static void aptx_qmf_tree_synthesis(struct aptx_QMF_analysis *qmf, static inline int32_t aptx_bin_search(int32_t value, int32_t factor, - const int32_t *intervals, int32_t nb_intervals) + const int32_t *intervals, int nb_intervals) { int32_t idx = 0; int i; @@ -726,15 +760,15 @@ static void aptx_quantize_difference(struct aptx_quantize *quantize, sample_difference_abs = sample_difference; if (sample_difference_abs < 0) sample_difference_abs = -sample_difference_abs; - if (sample_difference_abs > (1 << 23) - 1) - sample_difference_abs = (1 << 23) - 1; + if (sample_difference_abs > ((int32_t)1 << 23) - 1) + sample_difference_abs = ((int32_t)1 << 23) - 1; quantized_sample = aptx_bin_search(sample_difference_abs >> 4, quantization_factor, intervals, tables->tables_size); - d = rshift32_clip24(((int64_t)dither * (int64_t)dither) >> 32, 7) - (1 << 23); - d = rshift64((int64_t)d * (int64_t)tables->quantize_dither_factors[quantized_sample], 23); + d = rshift32_clip24((int32_t)(((int64_t)dither * (int64_t)dither) >> 32), 7) - ((int32_t)1 << 23); + d = (int32_t)rshift64((int64_t)d * (int64_t)tables->quantize_dither_factors[quantized_sample], 23); intervals += quantized_sample; mean = (intervals[1] + intervals[0]) / 2; @@ -742,7 +776,7 @@ static void aptx_quantize_difference(struct aptx_quantize *quantize, dithered_sample = rshift64_clip24((int64_t)dither * (int64_t)interval + ((int64_t)clip_intp2(mean + d, 23) << 32), 32); error = ((int64_t)sample_difference_abs << 20) - (int64_t)dithered_sample * (int64_t)quantization_factor; - quantize->error = rshift64(error, 23); + quantize->error = (int32_t)rshift64(error, 23); if (quantize->error < 0) quantize->error = -quantize->error; @@ -759,23 +793,27 @@ static void aptx_quantize_difference(struct aptx_quantize *quantize, static void aptx_encode_channel(struct aptx_channel *channel, const int32_t samples[4], int hd) { - int32_t subband_samples[4]; - int subband; + int32_t subband_samples[NB_SUBBANDS]; + int32_t diff; + unsigned subband; + aptx_qmf_tree_analysis(&channel->qmf, samples, subband_samples); aptx_generate_dither(channel); + for (subband = 0; subband < NB_SUBBANDS; subband++) { - int32_t diff = clip_intp2(subband_samples[subband] - channel->prediction[subband].predicted_sample, 23); + diff = clip_intp2(subband_samples[subband] - channel->prediction[subband].predicted_sample, 23); aptx_quantize_difference(&channel->quantize[subband], diff, channel->dither[subband], channel->invert_quantize[subband].quantization_factor, - &tables[hd][subband]); + &all_tables[hd][subband]); } } static void aptx_decode_channel(struct aptx_channel *channel, int32_t samples[4]) { - int32_t subband_samples[4]; - int subband; + int32_t subband_samples[NB_SUBBANDS]; + unsigned subband; + for (subband = 0; subband < NB_SUBBANDS; subband++) subband_samples[subband] = channel->prediction[subband].previous_reconstructed_sample; aptx_qmf_tree_synthesis(&channel->qmf, subband_samples, samples); @@ -793,12 +831,12 @@ static void aptx_invert_quantization(struct aptx_invert_quantize *invert_quantiz if (quantized_sample < 0) qr = -qr; - qr = rshift64_clip24(((int64_t)qr<<32) + (int64_t)dither * (int64_t)tables->invert_quantize_dither_factors[idx], 32); - invert_quantize->reconstructed_difference = ((int64_t)invert_quantize->quantization_factor * (int64_t)qr) >> 19; + qr = rshift64_clip24(((int64_t)qr * ((int64_t)1<<32)) + (int64_t)dither * (int64_t)tables->invert_quantize_dither_factors[idx], 32); + invert_quantize->reconstructed_difference = (int32_t)(((int64_t)invert_quantize->quantization_factor * (int64_t)qr) >> 19); /* update factor_select */ factor_select = 32620 * invert_quantize->factor_select; - factor_select = rshift32(factor_select + (tables->quantize_factor_select_offset[idx] << 15), 15); + factor_select = rshift32(factor_select + (tables->quantize_factor_select_offset[idx] * (1 << 15)), 15); invert_quantize->factor_select = clip(factor_select, 0, tables->factor_max); /* update quantization factor */ @@ -824,25 +862,25 @@ static void aptx_prediction_filtering(struct aptx_prediction *prediction, int32_t reconstructed_difference, int order) { - int32_t reconstructed_sample, predictor, srd0; + int32_t reconstructed_sample, predictor, srd0, srd; int32_t *reconstructed_differences; int64_t predicted_difference = 0; int i; reconstructed_sample = clip_intp2(reconstructed_difference + prediction->predicted_sample, 23); - predictor = clip_intp2(((int64_t)prediction->s_weight[0] * (int64_t)prediction->previous_reconstructed_sample - + (int64_t)prediction->s_weight[1] * (int64_t)reconstructed_sample) >> 22, 23); + predictor = clip_intp2((int32_t)(((int64_t)prediction->s_weight[0] * (int64_t)prediction->previous_reconstructed_sample + + (int64_t)prediction->s_weight[1] * (int64_t)reconstructed_sample) >> 22), 23); prediction->previous_reconstructed_sample = reconstructed_sample; reconstructed_differences = aptx_reconstructed_differences_update(prediction, reconstructed_difference, order); - srd0 = DIFFSIGN(reconstructed_difference, 0) << 23; + srd0 = (int32_t)DIFFSIGN(reconstructed_difference, 0) * ((int32_t)1 << 23); for (i = 0; i < order; i++) { - int32_t srd = (reconstructed_differences[-i-1] >> 31) | 1; + srd = (reconstructed_differences[-i-1] >> 31) | 1; prediction->d_weight[i] -= rshift32(prediction->d_weight[i] - srd*srd0, 8); predicted_difference += (int64_t)reconstructed_differences[-i] * (int64_t)prediction->d_weight[i]; } - prediction->predicted_difference = clip_intp2(predicted_difference >> 22, 23); + prediction->predicted_difference = clip_intp2((int32_t)(predicted_difference >> 22), 23); prediction->predicted_sample = clip_intp2(predictor + prediction->predicted_difference, 23); } @@ -864,7 +902,7 @@ static void aptx_process_subband(struct aptx_invert_quantize *invert_quantize, range = 0x100000; sw1 = rshift32(-same_sign[1] * prediction->s_weight[1], 1); - sw1 = (clip(sw1, -range, range) & ~0xF) << 4; + sw1 = (clip(sw1, -range, range) & ~0xF) * 16; range = 0x300000; weight[0] = 254 * prediction->s_weight[0] + 0x800000*same_sign[0] + sw1; @@ -881,19 +919,19 @@ static void aptx_process_subband(struct aptx_invert_quantize *invert_quantize, static void aptx_invert_quantize_and_prediction(struct aptx_channel *channel, int hd) { - int subband; + unsigned subband; for (subband = 0; subband < NB_SUBBANDS; subband++) aptx_process_subband(&channel->invert_quantize[subband], &channel->prediction[subband], channel->quantize[subband].quantized_sample, channel->dither[subband], - &tables[hd][subband]); + &all_tables[hd][subband]); } -static int32_t aptx_quantized_parity(struct aptx_channel *channel) +static int32_t aptx_quantized_parity(const struct aptx_channel *channel) { int32_t parity = channel->dither_parity; - int subband; + unsigned subband; for (subband = 0; subband < NB_SUBBANDS; subband++) parity ^= channel->quantize[subband].quantized_sample; @@ -901,13 +939,15 @@ static int32_t aptx_quantized_parity(struct aptx_channel *channel) return parity & 1; } -/* For each sample, ensure that the parity of all subbands of all channels - * is 0 except once every 8 samples where the parity is forced to 1. */ -static int aptx_check_parity(struct aptx_channel channels[NB_CHANNELS], uint8_t *sync_idx) +/* + * For each sample, ensure that the parity of all subbands of all channels + * is 0 except once every 8 samples where the parity is forced to 1. + */ +static int aptx_check_parity(const struct aptx_channel channels[NB_CHANNELS], uint8_t *sync_idx) { - int32_t parity = aptx_quantized_parity(&channels[LEFT]) - ^ aptx_quantized_parity(&channels[RIGHT]); - int32_t eighth = *sync_idx == 7; + const int32_t parity = aptx_quantized_parity(&channels[LEFT]) + ^ aptx_quantized_parity(&channels[RIGHT]); + const int32_t eighth = *sync_idx == 7; *sync_idx = (*sync_idx + 1) & 7; return parity ^ eighth; @@ -915,38 +955,41 @@ static int aptx_check_parity(struct aptx_channel channels[NB_CHANNELS], uint8_t static void aptx_insert_sync(struct aptx_channel channels[NB_CHANNELS], uint8_t *sync_idx) { + unsigned i; + struct aptx_channel *c; + static const unsigned map[] = { 1, 2, 0, 3 }; + struct aptx_quantize *min = &channels[NB_CHANNELS-1].quantize[map[0]]; + if (aptx_check_parity(channels, sync_idx)) { - int i; - struct aptx_channel *c; - static const int map[] = { 1, 2, 0, 3 }; - struct aptx_quantize *min = &channels[NB_CHANNELS-1].quantize[map[0]]; for (c = &channels[NB_CHANNELS-1]; c >= channels; c--) for (i = 0; i < NB_SUBBANDS; i++) if (c->quantize[map[i]].error < min->error) min = &c->quantize[map[i]]; - /* Forcing the desired parity is done by offsetting by 1 the quantized - * sample from the subband featuring the smallest quantization error. */ + /* + * Forcing the desired parity is done by offsetting by 1 the quantized + * sample from the subband featuring the smallest quantization error. + */ min->quantized_sample = min->quantized_sample_parity_change; } } -static uint16_t aptx_pack_codeword(struct aptx_channel *channel) +static uint16_t aptx_pack_codeword(const struct aptx_channel *channel) { - int32_t parity = aptx_quantized_parity(channel); - return (((channel->quantize[3].quantized_sample & 0x06) | parity) << 13) - | (((channel->quantize[2].quantized_sample & 0x03) ) << 11) - | (((channel->quantize[1].quantized_sample & 0x0F) ) << 7) - | (((channel->quantize[0].quantized_sample & 0x7F) ) << 0); + const int32_t parity = aptx_quantized_parity(channel); + return (uint16_t)((((channel->quantize[3].quantized_sample & 0x06) | parity) << 13) + | (((channel->quantize[2].quantized_sample & 0x03) ) << 11) + | (((channel->quantize[1].quantized_sample & 0x0F) ) << 7) + | (((channel->quantize[0].quantized_sample & 0x7F) ) << 0)); } -static uint32_t aptxhd_pack_codeword(struct aptx_channel *channel) +static uint32_t aptxhd_pack_codeword(const struct aptx_channel *channel) { - int32_t parity = aptx_quantized_parity(channel); - return (((channel->quantize[3].quantized_sample & 0x01E) | parity) << 19) - | (((channel->quantize[2].quantized_sample & 0x00F) ) << 15) - | (((channel->quantize[1].quantized_sample & 0x03F) ) << 9) - | (((channel->quantize[0].quantized_sample & 0x1FF) ) << 0); + const int32_t parity = aptx_quantized_parity(channel); + return (uint32_t)((((channel->quantize[3].quantized_sample & 0x01E) | parity) << 19) + | (((channel->quantize[2].quantized_sample & 0x00F) ) << 15) + | (((channel->quantize[1].quantized_sample & 0x03F) ) << 9) + | (((channel->quantize[0].quantized_sample & 0x1FF) ) << 0)); } static void aptx_unpack_codeword(struct aptx_channel *channel, uint16_t codeword) @@ -961,19 +1004,19 @@ static void aptx_unpack_codeword(struct aptx_channel *channel, uint16_t codeword static void aptxhd_unpack_codeword(struct aptx_channel *channel, uint32_t codeword) { - channel->quantize[0].quantized_sample = sign_extend(codeword >> 0, 9); - channel->quantize[1].quantized_sample = sign_extend(codeword >> 9, 6); - channel->quantize[2].quantized_sample = sign_extend(codeword >> 15, 4); - channel->quantize[3].quantized_sample = sign_extend(codeword >> 19, 5); + channel->quantize[0].quantized_sample = sign_extend((int32_t)(codeword >> 0), 9); + channel->quantize[1].quantized_sample = sign_extend((int32_t)(codeword >> 9), 6); + channel->quantize[2].quantized_sample = sign_extend((int32_t)(codeword >> 15), 4); + channel->quantize[3].quantized_sample = sign_extend((int32_t)(codeword >> 19), 5); channel->quantize[3].quantized_sample = (channel->quantize[3].quantized_sample & ~1) | aptx_quantized_parity(channel); } static void aptx_encode_samples(struct aptx_context *ctx, - const int32_t samples[NB_CHANNELS][4], + int32_t samples[NB_CHANNELS][4], uint8_t *output) { - int channel; + unsigned channel; for (channel = 0; channel < NB_CHANNELS; channel++) aptx_encode_channel(&ctx->channels[channel], samples[channel], ctx->hd); @@ -983,13 +1026,13 @@ static void aptx_encode_samples(struct aptx_context *ctx, aptx_invert_quantize_and_prediction(&ctx->channels[channel], ctx->hd); if (ctx->hd) { uint32_t codeword = aptxhd_pack_codeword(&ctx->channels[channel]); - output[3*channel+0] = (codeword >> 16) & 0xFF; - output[3*channel+1] = (codeword >> 8) & 0xFF; - output[3*channel+2] = (codeword >> 0) & 0xFF; + output[3*channel+0] = (uint8_t)((codeword >> 16) & 0xFF); + output[3*channel+1] = (uint8_t)((codeword >> 8) & 0xFF); + output[3*channel+2] = (uint8_t)((codeword >> 0) & 0xFF); } else { uint16_t codeword = aptx_pack_codeword(&ctx->channels[channel]); - output[2*channel+0] = (codeword >> 8) & 0xFF; - output[2*channel+1] = (codeword >> 0) & 0xFF; + output[2*channel+0] = (uint8_t)((codeword >> 8) & 0xFF); + output[2*channel+1] = (uint8_t)((codeword >> 0) & 0xFF); } } } @@ -998,7 +1041,8 @@ static int aptx_decode_samples(struct aptx_context *ctx, const uint8_t *input, int32_t samples[NB_CHANNELS][4]) { - int channel, ret; + unsigned channel; + int ret; for (channel = 0; channel < NB_CHANNELS; channel++) { aptx_generate_dither(&ctx->channels[channel]); @@ -1009,9 +1053,9 @@ static int aptx_decode_samples(struct aptx_context *ctx, ((uint32_t)input[3*channel+1] << 8) | ((uint32_t)input[3*channel+2] << 0)); else - aptx_unpack_codeword(&ctx->channels[channel], + aptx_unpack_codeword(&ctx->channels[channel], (uint16_t)( ((uint16_t)input[2*channel+0] << 8) | - ((uint16_t)input[2*channel+1] << 0)); + ((uint16_t)input[2*channel+1] << 0))); aptx_invert_quantize_and_prediction(&ctx->channels[channel], ctx->hd); } @@ -1023,16 +1067,37 @@ static int aptx_decode_samples(struct aptx_context *ctx, return ret; } +static void aptx_reset_decode_sync(struct aptx_context *ctx) +{ + const size_t decode_dropped = ctx->decode_dropped; + const size_t decode_sync_packets = ctx->decode_sync_packets; + const uint8_t decode_sync_buffer_len = ctx->decode_sync_buffer_len; + unsigned char decode_sync_buffer[6]; + unsigned i; + + for (i = 0; i < 6; i++) + decode_sync_buffer[i] = ctx->decode_sync_buffer[i]; + + aptx_reset(ctx); + + for (i = 0; i < 6; i++) + ctx->decode_sync_buffer[i] = decode_sync_buffer[i]; + + ctx->decode_sync_buffer_len = decode_sync_buffer_len; + ctx->decode_sync_packets = decode_sync_packets; + ctx->decode_dropped = decode_dropped; +} + -int aptx_major = OPENAPTX_MAJOR; -int aptx_minor = OPENAPTX_MINOR; -int aptx_patch = OPENAPTX_PATCH; +const int aptx_major = OPENAPTX_MAJOR; +const int aptx_minor = OPENAPTX_MINOR; +const int aptx_patch = OPENAPTX_PATCH; struct aptx_context *aptx_init(int hd) { struct aptx_context *ctx; - ctx = malloc(sizeof(*ctx)); + ctx = (struct aptx_context *)malloc(sizeof(*ctx)); if (!ctx) return NULL; @@ -1044,18 +1109,22 @@ struct aptx_context *aptx_init(int hd) void aptx_reset(struct aptx_context *ctx) { - int hd, chan, subband; + const uint8_t hd = ctx->hd; + unsigned i, chan, subband; + struct aptx_channel *channel; + struct aptx_prediction *prediction; + + for (i = 0; i < sizeof(*ctx); i++) + ((unsigned char *)ctx)[i] = 0; - hd = ctx->hd; - memset(ctx, 0, sizeof(*ctx)); ctx->hd = hd; ctx->decode_skip_leading = (LATENCY_SAMPLES+3)/4; ctx->encode_remaining = (LATENCY_SAMPLES+3)/4; for (chan = 0; chan < NB_CHANNELS; chan++) { - struct aptx_channel *channel = &ctx->channels[chan]; + channel = &ctx->channels[chan]; for (subband = 0; subband < NB_SUBBANDS; subband++) { - struct aptx_prediction *prediction = &channel->prediction[subband]; + prediction = &channel->prediction[subband]; prediction->prev_sign[0] = 1; prediction->prev_sign[1] = 1; } @@ -1069,21 +1138,19 @@ void aptx_finish(struct aptx_context *ctx) size_t aptx_encode(struct aptx_context *ctx, const unsigned char *input, size_t input_size, unsigned char *output, size_t output_size, size_t *written) { + const size_t sample_size = ctx->hd ? 6 : 4; int32_t samples[NB_CHANNELS][4]; + unsigned sample, channel; size_t ipos, opos; - int sample, channel; - int sample_size; - - sample_size = ctx->hd ? 6 : 4; for (ipos = 0, opos = 0; ipos + 3*NB_CHANNELS*4 <= input_size && opos + sample_size <= output_size; opos += sample_size) { for (sample = 0; sample < 4; sample++) { for (channel = 0; channel < NB_CHANNELS; channel++, ipos += 3) { /* samples need to contain 24bit signed integer stored as 32bit signed integers */ - /* last int8_t --> uint32_t cast propagates sign bit for 32bit integer */ - samples[channel][sample] = ((uint32_t)input[ipos+0] << 0) | - ((uint32_t)input[ipos+1] << 8) | - ((uint32_t)(int8_t)input[ipos+2] << 16); + /* last int8_t --> uint32_t cast propagates signedness for 32bit integer */ + samples[channel][sample] = (int32_t)(((uint32_t)input[ipos+0] << 0) | + ((uint32_t)input[ipos+1] << 8) | + ((uint32_t)(int8_t)input[ipos+2] << 16)); } } aptx_encode_samples(ctx, samples, output + opos); @@ -1095,12 +1162,10 @@ size_t aptx_encode(struct aptx_context *ctx, const unsigned char *input, size_t int aptx_encode_finish(struct aptx_context *ctx, unsigned char *output, size_t output_size, size_t *written) { - const int32_t samples[NB_CHANNELS][4] = { }; - int sample_size; + const size_t sample_size = ctx->hd ? 6 : 4; + int32_t samples[NB_CHANNELS][4] = { { 0 } }; size_t opos; - sample_size = ctx->hd ? 6 : 4; - if (ctx->encode_remaining == 0) { *written = 0; return 1; @@ -1120,14 +1185,12 @@ int aptx_encode_finish(struct aptx_context *ctx, unsigned char *output, size_t o size_t aptx_decode(struct aptx_context *ctx, const unsigned char *input, size_t input_size, unsigned char *output, size_t output_size, size_t *written) { + const size_t sample_size = ctx->hd ? 6 : 4; int32_t samples[NB_CHANNELS][4]; + unsigned sample, channel; size_t ipos, opos; - int sample, channel; - int sample_size; - sample_size = ctx->hd ? 6 : 4; - - for (ipos = 0, opos = 0; ipos + sample_size <= input_size && opos + 3*NB_CHANNELS*4 <= output_size; ipos += sample_size) { + for (ipos = 0, opos = 0; ipos + sample_size <= input_size && (opos + 3*NB_CHANNELS*4 <= output_size || ctx->decode_skip_leading > 0); ipos += sample_size) { if (aptx_decode_samples(ctx, input + ipos, samples)) break; sample = 0; @@ -1140,14 +1203,122 @@ size_t aptx_decode(struct aptx_context *ctx, const unsigned char *input, size_t for (; sample < 4; sample++) { for (channel = 0; channel < NB_CHANNELS; channel++, opos += 3) { /* samples contain 24bit signed integers stored as 32bit signed integers */ - /* we do not need to care about negative integers specially as they have 24. bit set */ - output[opos+0] = ((uint32_t)samples[channel][sample] >> 0) & 0xFF; - output[opos+1] = ((uint32_t)samples[channel][sample] >> 8) & 0xFF; - output[opos+2] = ((uint32_t)samples[channel][sample] >> 16) & 0xFF; + /* we do not need to care about negative integers specially as they have 23th bit set */ + output[opos+0] = (uint8_t)(((uint32_t)samples[channel][sample] >> 0) & 0xFF); + output[opos+1] = (uint8_t)(((uint32_t)samples[channel][sample] >> 8) & 0xFF); + output[opos+2] = (uint8_t)(((uint32_t)samples[channel][sample] >> 16) & 0xFF); + } + } + } + + *written = opos; + return ipos; +} + +size_t aptx_decode_sync(struct aptx_context *ctx, const unsigned char *input, size_t input_size, unsigned char *output, size_t output_size, size_t *written, int *synced, size_t *dropped) +{ + const size_t sample_size = ctx->hd ? 6 : 4; + size_t input_size_step; + size_t processed_step; + size_t written_step; + size_t ipos = 0; + size_t opos = 0; + size_t i; + + *synced = 0; + *dropped = 0; + + /* If we have some unprocessed bytes in internal cache, first fill remaining data to internal cache except the final byte */ + if (ctx->decode_sync_buffer_len > 0 && sample_size-1 - ctx->decode_sync_buffer_len <= input_size) { + while (ctx->decode_sync_buffer_len < sample_size-1) + ctx->decode_sync_buffer[ctx->decode_sync_buffer_len++] = input[ipos++]; + } + + /* Internal cache decode loop, use it only when sample is split between internal cache and input buffer */ + while (ctx->decode_sync_buffer_len == sample_size-1 && ipos < sample_size && ipos < input_size && (opos + 3*NB_CHANNELS*4 <= output_size || ctx->decode_skip_leading > 0 || ctx->decode_dropped > 0)) { + ctx->decode_sync_buffer[sample_size-1] = input[ipos++]; + + processed_step = aptx_decode(ctx, ctx->decode_sync_buffer, sample_size, output + opos, output_size - opos, &written_step); + + opos += written_step; + + if (ctx->decode_dropped > 0 && processed_step == sample_size) { + ctx->decode_dropped += processed_step; + ctx->decode_sync_packets++; + if (ctx->decode_sync_packets >= (LATENCY_SAMPLES+3)/4) { + *dropped += ctx->decode_dropped; + ctx->decode_dropped = 0; + ctx->decode_sync_packets = 0; + } + } + + if (processed_step < sample_size) { + aptx_reset_decode_sync(ctx); + *synced = 0; + ctx->decode_dropped++; + ctx->decode_sync_packets = 0; + for (i = 0; i < sample_size-1; i++) + ctx->decode_sync_buffer[i] = ctx->decode_sync_buffer[i+1]; + } else { + if (ctx->decode_dropped == 0) + *synced = 1; + ctx->decode_sync_buffer_len = 0; + } + } + + /* If all unprocessed data are now available only in input buffer, do not use internal cache */ + if (ctx->decode_sync_buffer_len == sample_size-1 && ipos == sample_size) { + ipos = 0; + ctx->decode_sync_buffer_len = 0; + } + + /* Main decode loop, decode as much as possible samples, if decoding fails restart it on next byte */ + while (ipos + sample_size <= input_size && (opos + 3*NB_CHANNELS*4 <= output_size || ctx->decode_skip_leading > 0 || ctx->decode_dropped > 0)) { + input_size_step = (((output_size - opos) / 3*NB_CHANNELS*4) + ctx->decode_skip_leading) * sample_size; + if (input_size_step > ((input_size - ipos) / sample_size) * sample_size) + input_size_step = ((input_size - ipos) / sample_size) * sample_size; + if (input_size_step > ((LATENCY_SAMPLES+3)/4 - ctx->decode_sync_packets) * sample_size && ctx->decode_dropped > 0) + input_size_step = ((LATENCY_SAMPLES+3)/4 - ctx->decode_sync_packets) * sample_size; + + processed_step = aptx_decode(ctx, input + ipos, input_size_step, output + opos, output_size - opos, &written_step); + + ipos += processed_step; + opos += written_step; + + if (ctx->decode_dropped > 0 && processed_step / sample_size > 0) { + ctx->decode_dropped += processed_step; + ctx->decode_sync_packets += processed_step / sample_size; + if (ctx->decode_sync_packets >= (LATENCY_SAMPLES+3)/4) { + *dropped += ctx->decode_dropped; + ctx->decode_dropped = 0; + ctx->decode_sync_packets = 0; } } + + if (processed_step < input_size_step) { + aptx_reset_decode_sync(ctx); + *synced = 0; + ipos++; + ctx->decode_dropped++; + ctx->decode_sync_packets = 0; + } else if (ctx->decode_dropped == 0) { + *synced = 1; + } + } + + /* If number of unprocessed bytes is less then sample size store them to internal cache */ + if (ipos + sample_size > input_size) { + while (ipos < input_size) + ctx->decode_sync_buffer[ctx->decode_sync_buffer_len++] = input[ipos++]; } *written = opos; return ipos; } + +size_t aptx_decode_sync_finish(struct aptx_context *ctx) +{ + const uint8_t dropped = ctx->decode_sync_buffer_len; + aptx_reset(ctx); + return dropped; +} diff --git a/openaptx.h b/openaptx.h index b8fb972..50f893a 100644 --- a/openaptx.h +++ b/openaptx.h @@ -1,6 +1,6 @@ /* * Open Source implementation of Audio Processing Technology codec (aptX) - * Copyright (C) 2018 Pali Rohár + * Copyright (C) 2018-2020 Pali Rohár * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -14,21 +14,21 @@ * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef OPENAPTX_H #define OPENAPTX_H #define OPENAPTX_MAJOR 0 -#define OPENAPTX_MINOR 1 +#define OPENAPTX_MINOR 2 #define OPENAPTX_PATCH 0 #include -extern int aptx_major; -extern int aptx_minor; -extern int aptx_patch; +extern const int aptx_major; +extern const int aptx_minor; +extern const int aptx_patch; struct aptx_context; @@ -52,54 +52,92 @@ void aptx_finish(struct aptx_context *ctx); /* * Encodes sequence of 4 raw 24bit signed stereo samples from input buffer with - * size input_len to aptX audio samples into output buffer with size output_len. + * size input_size to aptX audio samples into output buffer with output_size. * Return value indicates processed length from input buffer and to written * pointer is stored length of encoded aptX audio samples in output buffer. * Therefore input buffer must contain sequence of the 24 bytes in format * LLLRRRLLLRRRLLLRRRLLLRRR (L-left, R-right) and output buffer would contain * encoded sequence of either four bytes (LLRR) of aptX or six bytes (LLLRRR) - * of aptX HD. Due to aptX parity check it is suggested to provide multiple of - * 8*4 raw input samples, therefore multiple of 8*24 bytes. + * of aptX HD. */ size_t aptx_encode(struct aptx_context *ctx, const unsigned char *input, - size_t input_len, + size_t input_size, unsigned char *output, - size_t output_len, + size_t output_size, size_t *written); /* * Finish encoding of current stream and reset internal state to be ready for - * encoding new stream. Due to aptX latency, last 90 samples (rounded to 92) - * will be filled by this finish function. When output buffer is too small, this - * function returns zero, fills buffer only partially, does not reset internal - * state and subsequent calls continue filling output buffer. When output buffer - * is large enough, then function returns non-zero value. In both cases into - * written pointer is stored length of encoded samples. + * encoding or decoding a new stream. Due to aptX latency, last 90 samples + * (rounded to 92) will be filled by this finish function. When output buffer is + * too small, this function returns zero, fills buffer only partially, does not + * reset internal state and subsequent calls continue filling output buffer. + * When output buffer is large enough, then function returns non-zero value. + * In both cases into written pointer is stored length of encoded samples. */ int aptx_encode_finish(struct aptx_context *ctx, unsigned char *output, - size_t output_len, + size_t output_size, size_t *written); /* - * Decodes aptX audio samples in input buffer with size input_len to sequence - * of raw 24bit signed stereo samples into output buffer with size output_len. + * Decodes aptX audio samples in input buffer with size input_size to sequence + * of raw 24bit signed stereo samples into output buffer with size output_size. * Return value indicates processed length from input buffer and to written * pointer is stored length of decoded output samples in output buffer. * Input buffer must contain seqeunce of four bytes (LLRR) of aptX or six * bytes (LLLRRR) of aptX HD samples and output buffer would contain decoded * sequence of 24 bytes in format LLLRRRLLLRRRLLLRRRLLLRRR (L-left, R-right) - * for one aptX sample. Due to aptX parity check it is suggested to provide - * multiple of eight aptX samples, therefore multiple of 8*4 bytes for aptX - * and 8*6 bytes for aptX HD. Due to aptX latency, output buffer starts filling - * after 90 samples. + * for one aptX sample. Due to aptX latency, output buffer starts filling + * after 90 samples. When parity check fails then this function stops decoding + * and returns processed length of input buffer. To detect such failure it is + * needed to compare return value and input_size. Note that if you have a + * finite stream then the last two decoded samples from the last decode call + * does not contain any meaningful value. They are present just because aptX + * samples are rounded to the multiple by four and latency is 90 samples so + * last 2 samples are just padding. */ size_t aptx_decode(struct aptx_context *ctx, const unsigned char *input, - size_t input_len, + size_t input_size, unsigned char *output, - size_t output_len, + size_t output_size, size_t *written); +/* + * Auto synchronization variant of aptx_decode() function suitable for partially + * corrupted continuous stream in which some bytes are missing. All arguments, + * including return value have same meaning as for aptx_decode() function. The + * only difference is that there is no restriction for size of input buffer, + * output buffer must have space for decoding whole input buffer plus space for + * one additional decoded sample (24 bytes) and the last difference is that this + * function continue to decode even when parity check fails. When decoding fails + * this function starts searching for next bytes from the input buffer which + * have valid parity check (to be synchronized) and then starts decoding again. + * Into synced pointer is stored 1 if at the end of processing is decoder fully + * synchronized (in non-error state, with valid parity check) or is stored 0 if + * decoder is unsynchronized (in error state, without valid parity check). Into + * dropped pointer is stored number of dropped (not decoded) bytes which were + * already processed. Functions aptx_decode() and aptx_decode_sync() should not + * be mixed together. + */ +size_t aptx_decode_sync(struct aptx_context *ctx, + const unsigned char *input, + size_t input_size, + unsigned char *output, + size_t output_size, + size_t *written, + int *synced, + size_t *dropped); + +/* + * Finish decoding of current auto synchronization stream and reset internal + * state to be ready for encoding or decoding a new stream. This function + * returns number of unprocessed cached bytes which would have been processed + * by next aptx_decode_sync() call, therefore in time of calling this function + * it is number of dropped input bytes. + */ +size_t aptx_decode_sync_finish(struct aptx_context *ctx); + #endif diff --git a/openaptxdec.c b/openaptxdec.c index a22f940..add2fab 100644 --- a/openaptxdec.c +++ b/openaptxdec.c @@ -1,6 +1,6 @@ /* * aptX decoder utility - * Copyright (C) 2018 Pali Rohár + * Copyright (C) 2018-2020 Pali Rohár * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -14,29 +14,39 @@ * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include +#ifdef _WIN32 +#include +#include +#endif + #include -static unsigned char input_buffer[512*8*6]; -static unsigned char output_buffer[512*8*3*2*4*6/4]; +static unsigned char input_buffer[512*6]; +static unsigned char output_buffer[512*3*2*6+3*2*4]; int main(int argc, char *argv[]) { int i; int hd; + int ret; size_t length; - size_t offset; - size_t sample_size; - size_t process_size; size_t processed; size_t written; + size_t dropped; + int synced; + int syncing; struct aptx_context *ctx; - unsigned int failed; + +#ifdef _WIN32 + _setmode(_fileno(stdin), _O_BINARY); + _setmode(_fileno(stdout), _O_BINARY); +#endif hd = 0; @@ -49,6 +59,9 @@ int main(int argc, char *argv[]) fprintf(stderr, "\n"); fprintf(stderr, "When input is damaged it tries to synchronize and recover\n"); fprintf(stderr, "\n"); + fprintf(stderr, "Non-zero return value indicates that input was damaged\n"); + fprintf(stderr, "and some bytes from input aptX audio stream were dropped\n"); + fprintf(stderr, "\n"); fprintf(stderr, "Usage:\n"); fprintf(stderr, " %s [options]\n", argv[0]); fprintf(stderr, "\n"); @@ -57,9 +70,9 @@ int main(int argc, char *argv[]) fprintf(stderr, " --hd Decode from aptX HD\n"); fprintf(stderr, "\n"); fprintf(stderr, "Examples:\n"); - fprintf(stderr, " %s < sample.aptx > sample.s24\n", argv[0]); - fprintf(stderr, " %s --hd < sample.aptxhd > sample.s24\n", argv[0]); - fprintf(stderr, " %s < sample.aptx | play -t raw -r 44.1k -s -3 -c 2 -\n", argv[0]); + fprintf(stderr, " %s < sample.aptx > sample.s24le\n", argv[0]); + fprintf(stderr, " %s --hd < sample.aptxhd > sample.s24le\n", argv[0]); + fprintf(stderr, " %s < sample.aptx | play -t raw -r 44.1k -L -e s -b 24 -c 2 -\n", argv[0]); return 1; } else if (strcmp(argv[i], "--hd") == 0) { hd = 1; @@ -69,20 +82,16 @@ int main(int argc, char *argv[]) } } - /* every eight sample contains synchronization parity check */ - sample_size = 8 * (hd ? 6 : 4); - ctx = aptx_init(hd); if (!ctx) { - fprintf(stderr, "%s: Cannot initialize aptX encoder\n", argv[0]); + fprintf(stderr, "%s: Cannot initialize aptX decoder\n", argv[0]); return 1; } - failed = 0; - offset = 0; - - /* Try to guess type of input stream based on the first six bytes - * Encoder produces fixed first sample because aptX predictor has fixed values */ + /* + * Try to guess type of input stream based on the first six bytes + * Encoder produces fixed first sample because aptX predictor has fixed values + */ length = fread(input_buffer, 1, 6, stdin); if (length >= 4 && memcmp(input_buffer, "\x4b\xbf\x4b\xbf", 4) == 0) { if (hd) @@ -91,76 +100,89 @@ int main(int argc, char *argv[]) if (!hd) fprintf(stderr, "%s: Input looks like start of aptX HD audio stream, try with --hd\n", argv[0]); } else { - fprintf(stderr, "%s: Input does not look like start of aptX nor aptX HD audio stream, trying to synchronize\n", argv[0]); + if (length >= 4 && memcmp(input_buffer, "\x6b\xbf\x6b\xbf", 4) == 0) + fprintf(stderr, "%s: Input looks like start of standard aptX audio stream, which is not supported yet\n", argv[0]); + else + fprintf(stderr, "%s: Input does not look like start of aptX nor aptX HD audio stream\n", argv[0]); } - while (length > 0 || !feof(stdin)) { - /* For decoding we need at least eight samples for synchronization */ - if (length < sample_size && !feof(stdin)) { - if (length > 0) - memmove(input_buffer, input_buffer + offset, length); - offset = 0; - length += fread(input_buffer + length, 1, sizeof(input_buffer) - length, stdin); - if (ferror(stdin)) - fprintf(stderr, "%s: aptX encoding failed to read input data\n", argv[0]); - } - - process_size = length; - - /* Always process multiple of the 8 samples (expect last) for synchronization support */ - if (length >= sample_size) - process_size -= process_size % sample_size; + ret = 0; + syncing = 0; - /* When decoding previous samples failed, reset internal state, predictor and state of the synchronization parity */ - if (failed > 0) - aptx_reset(ctx); + while (length > 0) { + processed = aptx_decode_sync(ctx, input_buffer, length, output_buffer, sizeof(output_buffer), &written, &synced, &dropped); - processed = aptx_decode(ctx, input_buffer + offset, process_size, output_buffer, sizeof(output_buffer), &written); + /* Check all possible states of synced, syncing and dropped status */ + if (!synced) { + if (!syncing) { + fprintf(stderr, "%s: aptX decoding failed, synchronizing\n", argv[0]); + syncing = 1; + ret = 1; + } + if (dropped) { + fprintf(stderr, "%s: aptX synchronization successful, dropped %lu byte%s\n", argv[0], (unsigned long)dropped, (dropped != 1) ? "s" : ""); + syncing = 0; + ret = 1; + } + if (!syncing) { + fprintf(stderr, "%s: aptX decoding failed, synchronizing\n", argv[0]); + syncing = 1; + ret = 1; + } + } else { + if (dropped) { + if (!syncing) + fprintf(stderr, "%s: aptX decoding failed, synchronizing\n", argv[0]); + fprintf(stderr, "%s: aptX synchronization successful, dropped %lu byte%s\n", argv[0], (unsigned long)dropped, (dropped != 1) ? "s" : ""); + syncing = 0; + ret = 1; + } else if (syncing) { + fprintf(stderr, "%s: aptX synchronization successful\n", argv[0]); + syncing = 0; + ret = 1; + } + } - if (processed > sample_size && failed > 0) { - fprintf(stderr, "%s: ... synchronization successful, dropped %u bytes\n", argv[0], failed); - failed = 0; + /* If we have not decoded all supplied samples then decoding unrecoverable failed */ + if (processed != length) { + fprintf(stderr, "%s: aptX decoding failed\n", argv[0]); + ret = 1; + break; } - /* If we have not decoded all supplied samples then decoding failed */ - if (processed != process_size) { - if (failed == 0) { - if (length < sample_size) - fprintf(stderr, "%s: aptX decoding stopped in the middle of the sample, dropped %u bytes\n", argv[0], (unsigned int)(length-processed)); - else - fprintf(stderr, "%s: aptX decoding failed, trying to synchronize ...\n", argv[0]); - } - if (length >= sample_size) - failed++; - else if (failed > 0) - failed += length; - if (processed <= sample_size) { - /* If we have not decoded at least 8 samples (with proper parity check) - * drop decoded buffer and try decoding again on next byte */ - processed = 1; - written = 0; + if (!feof(stdin)) { + length = fread(input_buffer, 1, sizeof(input_buffer), stdin); + if (ferror(stdin)) { + fprintf(stderr, "%s: aptX decoding failed to read input data\n", argv[0]); + ret = 1; + length = 0; } + } else { + length = 0; } + /* On the end of the input stream last two decoded samples are just padding and not a real data */ + if (length == 0 && !ferror(stdin) && written >= 6*2) + written -= 6*2; + if (written > 0) { if (fwrite(output_buffer, 1, written, stdout) != written) { fprintf(stderr, "%s: aptX decoding failed to write decoded data\n", argv[0]); - failed = 0; - length = 0; + ret = 1; break; } } - - if (length < sample_size) - break; - - length -= processed; - offset += processed; } - if (failed > 0) - fprintf(stderr, "%s ... synchronization failed, dropped %u bytes\n", argv[0], failed); + dropped = aptx_decode_sync_finish(ctx); + if (dropped && !syncing) { + fprintf(stderr, "%s: aptX decoding stopped in the middle of the sample, dropped %lu byte%s\n", argv[0], (unsigned long)dropped, (dropped != 1) ? "s" : ""); + ret = 1; + } else if (syncing) { + fprintf(stderr, "%s: aptX synchronization failed\n", argv[0]); + ret = 1; + } aptx_finish(ctx); - return 0; + return ret; } diff --git a/openaptxenc.c b/openaptxenc.c index 7c71570..08c58ef 100644 --- a/openaptxenc.c +++ b/openaptxenc.c @@ -1,6 +1,6 @@ /* * aptX encoder utility - * Copyright (C) 2018 Pali Rohár + * Copyright (C) 2018-2020 Pali Rohár * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -14,26 +14,37 @@ * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include +#ifdef _WIN32 +#include +#include +#endif + #include -static unsigned char input_buffer[512*8*3*2*4]; -static unsigned char output_buffer[512*8*6]; +static unsigned char input_buffer[512*3*2*4]; +static unsigned char output_buffer[512*6]; int main(int argc, char *argv[]) { int i; int hd; + int ret; size_t length; size_t processed; size_t written; struct aptx_context *ctx; +#ifdef _WIN32 + _setmode(_fileno(stdin), _O_BINARY); + _setmode(_fileno(stdout), _O_BINARY); +#endif + hd = 0; for (i = 1; i < argc; ++i) { @@ -51,9 +62,9 @@ int main(int argc, char *argv[]) fprintf(stderr, " --hd Encode to aptX HD\n"); fprintf(stderr, "\n"); fprintf(stderr, "Examples:\n"); - fprintf(stderr, " %s < sample.s24 > sample.aptx\n", argv[0]); - fprintf(stderr, " %s --hd < sample.s24 > sample.aptxhd\n", argv[0]); - fprintf(stderr, " sox sample.wav -t raw -r 44.1k -s -3 -c 2 - | %s > sample.aptx\n", argv[0]); + fprintf(stderr, " %s < sample.s24le > sample.aptx\n", argv[0]); + fprintf(stderr, " %s --hd < sample.s24le > sample.aptxhd\n", argv[0]); + fprintf(stderr, " sox sample.wav -t raw -r 44.1k -L -e s -b 24 -c 2 - | %s > sample.aptx\n", argv[0]); return 1; } else if (strcmp(argv[i], "--hd") == 0) { hd = 1; @@ -69,17 +80,24 @@ int main(int argc, char *argv[]) return 1; } + ret = 0; + while (!feof(stdin)) { length = fread(input_buffer, 1, sizeof(input_buffer), stdin); - if (ferror(stdin)) + if (ferror(stdin)) { fprintf(stderr, "%s: aptX encoding failed to read input data\n", argv[0]); + ret = 1; + } if (length == 0) break; processed = aptx_encode(ctx, input_buffer, length, output_buffer, sizeof(output_buffer), &written); - if (processed != length) - fprintf(stderr, "%s: aptX encoding stopped in the middle of the sample, dropped %u bytes\n", argv[0], (unsigned int)(length-processed)); + if (processed != length) { + fprintf(stderr, "%s: aptX encoding stopped in the middle of the sample, dropped %lu byte%s\n", argv[0], (unsigned long)(length-processed), (length-processed != 1) ? "s" : ""); + ret = 1; + } if (fwrite(output_buffer, 1, written, stdout) != written) { fprintf(stderr, "%s: aptX encoding failed to write encoded data\n", argv[0]); + ret = 1; break; } if (processed != length) @@ -87,10 +105,12 @@ int main(int argc, char *argv[]) } if (aptx_encode_finish(ctx, output_buffer, sizeof(output_buffer), &written)) { - if (fwrite(output_buffer, 1, written, stdout) != written) + if (fwrite(output_buffer, 1, written, stdout) != written) { fprintf(stderr, "%s: aptX encoding failed to write encoded data\n", argv[0]); + ret = 1; + } } aptx_finish(ctx); - return 0; + return ret; }