Skip to content

Commit

Permalink
New version 0.1.0 which fixes decoding delay
Browse files Browse the repository at this point in the history
New API function aptx_encode_finish() flushes encoding buffer and adds last
90 encoded samples, so caller has ability to encode whole input stream.

Function aptx_decode() was fixed to drop first 90 decoded samples which are
used just for predictor and contain noise.
  • Loading branch information
pali committed Dec 15, 2018
1 parent 5eb6ce6 commit 5a2f1ec
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 26 deletions.
12 changes: 8 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
MAJOR := 0
MINOR := 0
MINOR := 1
PATCH := 0

PREFIX := /usr/local
Expand All @@ -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)

Expand All @@ -27,15 +28,18 @@ 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 $< $@

$(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)

Expand Down
62 changes: 51 additions & 11 deletions openaptx.c
Original file line number Diff line number Diff line change
Expand Up @@ -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];
};

Expand Down Expand Up @@ -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];
};

Expand Down Expand Up @@ -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 };
Expand Down Expand Up @@ -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;
Expand All @@ -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];
Expand Down Expand Up @@ -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) |
Expand All @@ -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];
Expand All @@ -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 */
Expand Down
25 changes: 23 additions & 2 deletions openaptx.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,15 @@
#define OPENAPTX_H

#define OPENAPTX_MAJOR 0
#define OPENAPTX_MINOR 0
#define OPENAPTX_MINOR 1
#define OPENAPTX_PATCH 0

#include <stddef.h>

extern int aptx_major;
extern int aptx_minor;
extern int aptx_patch;

struct aptx_context;

/*
Expand Down Expand Up @@ -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.
Expand All @@ -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,
Expand Down
10 changes: 4 additions & 6 deletions openaptxdec.c
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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) {
Expand Down
9 changes: 6 additions & 3 deletions openaptxenc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}

0 comments on commit 5a2f1ec

Please sign in to comment.