diff --git a/Makefile b/Makefile index 0e844f9..42303b4 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ MAJOR := 0 -MINOR := 0 +MINOR := 1 PATCH := 0 PREFIX := /usr/local @@ -11,9 +11,10 @@ LIBNAME := libopenaptx.so SONAME := $(LIBNAME).$(MAJOR) FILENAME := $(SONAME).$(MINOR).$(PATCH) -UTILITIES := openaptxenc openaptxdec +UTILITIES := openaptxenc openaptxdec openaptxenc-static openaptxdec-static HEADERS := openaptx.h +SOURCES := openaptx.c BUILD := $(FILENAME) $(SONAME) $(LIBNAME) $(UTILITIES) @@ -27,8 +28,8 @@ install: $(BUILD) mkdir -p $(DESTDIR)/$(PREFIX)/$(INCDIR) cp -a $(HEADERS) $(DESTDIR)/$(PREFIX)/$(INCDIR) -$(FILENAME): openaptx.c $(HEADERS) - $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -I. -shared -fPIC -Wl,-soname,$(SONAME) -o $@ $< +$(FILENAME): $(SOURCES) $(HEADERS) + $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -I. -shared -fPIC -Wl,-soname,$(SONAME) -o $@ $(SOURCES) $(SONAME): $(FILENAME) ln -sf $< $@ @@ -36,6 +37,9 @@ $(SONAME): $(FILENAME) $(LIBNAME): $(SONAME) ln -sf $< $@ +%-static: %.c $(SOURCES) $(HEADERS) + $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -I. -o $@ $< $(SOURCES) + %: %.c $(LIBNAME) $(HEADERS) $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -I. -o $@ $< $(LIBNAME) diff --git a/openaptx.c b/openaptx.c index fc26483..acf1218 100644 --- a/openaptx.c +++ b/openaptx.c @@ -78,9 +78,10 @@ enum subbands { #define NB_FILTERS 2 #define FILTER_TAPS 16 +#define LATENCY_SAMPLES 90 struct aptx_filter_signal { - int pos; + uint8_t pos; int32_t buffer[2*FILTER_TAPS]; }; @@ -124,8 +125,10 @@ struct aptx_channel { } ; struct aptx_context { - int hd; - int32_t sync_idx; + uint8_t hd; + uint8_t sync_idx; + uint8_t encode_remaining; + uint8_t decode_skip_leading; struct aptx_channel channels[NB_CHANNELS]; }; @@ -900,20 +903,19 @@ static int32_t aptx_quantized_parity(struct aptx_channel *channel) /* 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], int32_t *idx) +static int aptx_check_parity(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; - int eighth = *idx == 7; - *idx = (*idx + 1) & 7; - + *sync_idx = (*sync_idx + 1) & 7; return parity ^ eighth; } -static void aptx_insert_sync(struct aptx_channel channels[NB_CHANNELS], int32_t *idx) +static void aptx_insert_sync(struct aptx_channel channels[NB_CHANNELS], uint8_t *sync_idx) { - if (aptx_check_parity(channels, idx)) { + if (aptx_check_parity(channels, sync_idx)) { int i; struct aptx_channel *c; static const int map[] = { 1, 2, 0, 3 }; @@ -1022,6 +1024,10 @@ static int aptx_decode_samples(struct aptx_context *ctx, } +int aptx_major = OPENAPTX_MAJOR; +int aptx_minor = OPENAPTX_MINOR; +int aptx_patch = OPENAPTX_PATCH; + struct aptx_context *aptx_init(int hd) { struct aptx_context *ctx; @@ -1043,6 +1049,8 @@ void aptx_reset(struct aptx_context *ctx) 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]; @@ -1071,7 +1079,7 @@ size_t aptx_encode(struct aptx_context *ctx, const unsigned char *input, size_t 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 intger stored as 32bit signed integers */ + /* 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) | @@ -1085,6 +1093,31 @@ size_t aptx_encode(struct aptx_context *ctx, const unsigned char *input, size_t return ipos; } +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; + size_t opos; + + sample_size = ctx->hd ? 6 : 4; + + if (ctx->encode_remaining == 0) { + *written = 0; + return 1; + } + + for (opos = 0; ctx->encode_remaining > 0 && opos + sample_size <= output_size; ctx->encode_remaining--, opos += sample_size) + aptx_encode_samples(ctx, samples, output + opos); + + *written = opos; + + if (ctx->encode_remaining > 0) + return 0; + + aptx_reset(ctx); + return 1; +} + 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) { int32_t samples[NB_CHANNELS][4]; @@ -1097,7 +1130,14 @@ size_t aptx_decode(struct aptx_context *ctx, const unsigned char *input, size_t for (ipos = 0, opos = 0; ipos + sample_size <= input_size && opos + 3*NB_CHANNELS*4 <= output_size; ipos += sample_size) { if (aptx_decode_samples(ctx, input + ipos, samples)) break; - for (sample = 0; sample < 4; sample++) { + sample = 0; + if (ctx->decode_skip_leading > 0) { + ctx->decode_skip_leading--; + if (ctx->decode_skip_leading > 0) + continue; + sample = LATENCY_SAMPLES%4; + } + 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 */ diff --git a/openaptx.h b/openaptx.h index 8d1c62f..b8fb972 100644 --- a/openaptx.h +++ b/openaptx.h @@ -21,9 +21,15 @@ #define OPENAPTX_H #define OPENAPTX_MAJOR 0 -#define OPENAPTX_MINOR 0 +#define OPENAPTX_MINOR 1 #define OPENAPTX_PATCH 0 +#include + +extern int aptx_major; +extern int aptx_minor; +extern int aptx_patch; + struct aptx_context; /* @@ -62,6 +68,20 @@ size_t aptx_encode(struct aptx_context *ctx, size_t output_len, 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. + */ +int aptx_encode_finish(struct aptx_context *ctx, + unsigned char *output, + size_t output_len, + 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. @@ -72,7 +92,8 @@ size_t aptx_encode(struct aptx_context *ctx, * 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 - * reps. 8*6 bytes for aptX HD. + * and 8*6 bytes for aptX HD. Due to aptX latency, output buffer starts filling + * after 90 samples. */ size_t aptx_decode(struct aptx_context *ctx, const unsigned char *input, diff --git a/openaptxdec.c b/openaptxdec.c index b65f076..a22f940 100644 --- a/openaptxdec.c +++ b/openaptxdec.c @@ -42,7 +42,7 @@ int main(int argc, char *argv[]) for (i = 1; i < argc; ++i) { if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { - fprintf(stderr, "aptX decoder utility\n"); + fprintf(stderr, "aptX decoder utility %d.%d.%d (using libopenaptx %d.%d.%d)\n", OPENAPTX_MAJOR, OPENAPTX_MINOR, OPENAPTX_PATCH, aptx_major, aptx_minor, aptx_patch); fprintf(stderr, "\n"); fprintf(stderr, "This utility decodes aptX or aptX HD audio stream\n"); fprintf(stderr, "from stdin to a raw 24 bit signed stereo on stdout\n"); @@ -86,12 +86,12 @@ int main(int argc, char *argv[]) length = fread(input_buffer, 1, 6, stdin); if (length >= 4 && memcmp(input_buffer, "\x4b\xbf\x4b\xbf", 4) == 0) { if (hd) - fprintf(stderr, "%s: Input looks like aptX audio stream (not aptX HD), try without --hd\n", argv[0]); + fprintf(stderr, "%s: Input looks like start of aptX audio stream (not aptX HD), try without --hd\n", argv[0]); } else if (length >= 6 && memcmp(input_buffer, "\x73\xbe\xff\x73\xbe\xff", 6) == 0) { if (!hd) - fprintf(stderr, "%s: Input looks like aptX HD audio stream, try with --hd\n", argv[0]); + 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 aptX nor aptX HD audio stream\n", argv[0]); + fprintf(stderr, "%s: Input does not look like start of aptX nor aptX HD audio stream, trying to synchronize\n", argv[0]); } while (length > 0 || !feof(stdin)) { @@ -140,8 +140,6 @@ int main(int argc, char *argv[]) processed = 1; written = 0; } - } else if (length < sample_size) { - fprintf(stderr, "%s: aptX decoding stopped in the middle of the sample\n", argv[0]); } if (written > 0) { diff --git a/openaptxenc.c b/openaptxenc.c index 01eb041..7c71570 100644 --- a/openaptxenc.c +++ b/openaptxenc.c @@ -38,7 +38,7 @@ int main(int argc, char *argv[]) for (i = 1; i < argc; ++i) { if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { - fprintf(stderr, "aptX encoder utility\n"); + fprintf(stderr, "aptX encoder utility %d.%d.%d (using libopenaptx %d.%d.%d)\n", OPENAPTX_MAJOR, OPENAPTX_MINOR, OPENAPTX_PATCH, aptx_major, aptx_minor, aptx_patch); fprintf(stderr, "\n"); fprintf(stderr, "This utility encodes a raw 24 bit signed stereo\n"); fprintf(stderr, "samples from stdin to aptX or aptX HD on stdout\n"); @@ -78,8 +78,6 @@ int main(int argc, char *argv[]) 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)); - else if (processed % (8*3*2*4)) - fprintf(stderr, "%s: aptX encoding stopped in the middle of the sample\n", argv[0]); if (fwrite(output_buffer, 1, written, stdout) != written) { fprintf(stderr, "%s: aptX encoding failed to write encoded data\n", argv[0]); break; @@ -88,6 +86,11 @@ int main(int argc, char *argv[]) break; } + if (aptx_encode_finish(ctx, output_buffer, sizeof(output_buffer), &written)) { + if (fwrite(output_buffer, 1, written, stdout) != written) + fprintf(stderr, "%s: aptX encoding failed to write encoded data\n", argv[0]); + } + aptx_finish(ctx); return 0; }