From 4d48b091d49818772a47559ba7fd7ab58fdd7682 Mon Sep 17 00:00:00 2001 From: nu774 Date: Tue, 29 Oct 2013 17:34:28 +0900 Subject: [PATCH] smart padding for better gapless playback Taken smart padding code using LPC extrapolation from vorbis/opus. Padding is done on both beginning and ending, but enc_delay and padding remains the same (we discard extra padding frame introduced on our side after encoding). --- MSVC/fdkaac.vcxproj | 3 + MSVC/fdkaac.vcxproj.filters | 9 ++ Makefile.am | 2 + src/aacenc.c | 2 +- src/extrapolater.c | 204 ++++++++++++++++++++++++++++++++++++ src/lpc.c | 169 +++++++++++++++++++++++++++++ src/lpc.h | 27 +++++ src/lpcm.c | 29 ----- src/lpcm.h | 30 ++++++ src/main.c | 83 ++++++++++----- src/pcm_reader.h | 2 + 11 files changed, 505 insertions(+), 55 deletions(-) create mode 100644 src/extrapolater.c create mode 100644 src/lpc.c create mode 100644 src/lpc.h diff --git a/MSVC/fdkaac.vcxproj b/MSVC/fdkaac.vcxproj index 03591a9..fc9a7ef 100644 --- a/MSVC/fdkaac.vcxproj +++ b/MSVC/fdkaac.vcxproj @@ -98,6 +98,8 @@ copy ..\fdk-aac\libSYS\include\machine_type.h include\fdk-aac\ + + @@ -114,6 +116,7 @@ copy ..\fdk-aac\libSYS\include\machine_type.h include\fdk-aac\ + diff --git a/MSVC/fdkaac.vcxproj.filters b/MSVC/fdkaac.vcxproj.filters index feae3ab..3ad1065 100644 --- a/MSVC/fdkaac.vcxproj.filters +++ b/MSVC/fdkaac.vcxproj.filters @@ -24,6 +24,12 @@ Source Files + + Source Files + + + Source Files + Source Files @@ -62,6 +68,9 @@ Header Files + + Header Files + Header Files diff --git a/Makefile.am b/Makefile.am index 94a1c7c..f410b67 100644 --- a/Makefile.am +++ b/Makefile.am @@ -6,6 +6,8 @@ bin_PROGRAMS = fdkaac fdkaac_SOURCES = \ src/aacenc.c \ src/caf_reader.c \ + src/extrapolater.c \ + src/lpc.c \ src/lpcm.c \ src/m4af.c \ src/main.c \ diff --git a/src/aacenc.c b/src/aacenc.c index d25be8a..bf975d0 100644 --- a/src/aacenc.c +++ b/src/aacenc.c @@ -231,5 +231,5 @@ int aac_encode_frame(HANDLE_AACENCODER encoder, return -1; } *olen = oargs.numOutBytes; - return oargs.numInSamples; + return oargs.numInSamples / format->channels_per_frame; } diff --git a/src/extrapolater.c b/src/extrapolater.c new file mode 100644 index 0000000..22b0f12 --- /dev/null +++ b/src/extrapolater.c @@ -0,0 +1,204 @@ +#if HAVE_CONFIG_H +# include "config.h" +#endif +#if HAVE_STDINT_H +# include +#endif +#include +#include +#include +#include "pcm_reader.h" +#include "lpc.h" + +typedef int16_t sample_t; + +typedef struct buffer_t { + sample_t *data; + unsigned count; /* count in frames */ + unsigned capacity; /* size in bytes */ +} buffer_t; + +typedef struct extrapolater_t { + pcm_reader_vtbl_t *vtbl; + pcm_reader_t *src; + pcm_sample_description_t format; + buffer_t buffer[2]; + unsigned nbuffer; + int (*process)(struct extrapolater_t *, void *, unsigned); +} extrapolater_t; + +#define LPC_ORDER 32 + +static inline pcm_reader_t *get_source(pcm_reader_t *reader) +{ + return ((extrapolater_t *)reader)->src; +} + +static const +pcm_sample_description_t *get_format(pcm_reader_t *reader) +{ + return pcm_get_format(get_source(reader)); +} + +static int64_t get_length(pcm_reader_t *reader) +{ + return pcm_get_length(get_source(reader)); +} + +static int64_t get_position(pcm_reader_t *reader) +{ + return pcm_get_position(get_source(reader)); +} + +static int realloc_buffer(buffer_t *bp, size_t size) +{ + if (bp->capacity < size) { + void *p = realloc(bp->data, size); + if (!p) return -1; + bp->data = p; + bp->capacity = size; + } + return 0; +} + +static void reverse_buffer(sample_t *data, unsigned nframes, unsigned nchannels) +{ + unsigned i = 0, j = nchannels * (nframes - 1), n; + + for (; i < j; i += nchannels, j -= nchannels) { + for (n = 0; n < nchannels; ++n) { + sample_t tmp = data[i + n]; + data[i + n] = data[j + n]; + data[j + n] = tmp; + } + } +} + +static int fetch(extrapolater_t *self, unsigned nframes) +{ + const pcm_sample_description_t *sfmt = pcm_get_format(self->src); + buffer_t *bp = &self->buffer[self->nbuffer]; + int rc = 0; + + if (realloc_buffer(bp, nframes * sfmt->bytes_per_frame) == 0) { + rc = pcm_read_frames(self->src, bp->data, nframes); + bp->count = rc > 0 ? rc : 0; + } + if (rc > 0) + self->nbuffer ^= 1; + return bp->count; +} + +static int extrapolate(extrapolater_t *self, const buffer_t *bp, + void *dst, unsigned nframes) +{ + const pcm_sample_description_t *sfmt = pcm_get_format(self->src); + unsigned i, n = sfmt->channels_per_frame; + float lpc[LPC_ORDER]; + + for (i = 0; i < n; ++i) { + vorbis_lpc_from_data(bp->data + i, lpc, bp->count, LPC_ORDER, n); + vorbis_lpc_predict(lpc, &bp->data[i + n * (bp->count - LPC_ORDER)], + LPC_ORDER, (sample_t*)dst + i, nframes, n); + } + return nframes; +} + +static int process1(extrapolater_t *self, void *buffer, unsigned nframes); +static int process2(extrapolater_t *self, void *buffer, unsigned nframes); +static int process3(extrapolater_t *self, void *buffer, unsigned nframes); + +static int process0(extrapolater_t *self, void *buffer, unsigned nframes) +{ + const pcm_sample_description_t *sfmt = pcm_get_format(self->src); + unsigned nchannels = sfmt->channels_per_frame; + buffer_t *bp = &self->buffer[self->nbuffer]; + + if (fetch(self, nframes) < 2 * LPC_ORDER) + memset(buffer, 0, nframes * sfmt->bytes_per_frame); + else { + reverse_buffer(bp->data, bp->count, nchannels); + extrapolate(self, bp, buffer, nframes); + reverse_buffer(buffer, nframes, nchannels); + reverse_buffer(bp->data, bp->count, nchannels); + } + self->process = bp->count ? process1 : process2; + return nframes; +} + +static int process1(extrapolater_t *self, void *buffer, unsigned nframes) +{ + const pcm_sample_description_t *sfmt = pcm_get_format(self->src); + buffer_t *bp = &self->buffer[self->nbuffer ^ 1]; + + assert(bp->count <= nframes); + memcpy(buffer, bp->data, bp->count * sfmt->bytes_per_frame); + if (!fetch(self, nframes)) + self->process = process2; + return bp->count; +} + +static int process2(extrapolater_t *self, void *buffer, unsigned nframes) +{ + const pcm_sample_description_t *sfmt = pcm_get_format(self->src); + buffer_t *bp = &self->buffer[self->nbuffer]; + buffer_t *bbp = &self->buffer[self->nbuffer ^ 1]; + + if (bp->count < 2 * LPC_ORDER) { + size_t total = bp->count + bbp->count; + if (bbp->count && + realloc_buffer(bbp, total * sfmt->bytes_per_frame) == 0) + { + memcpy(bbp->data + bbp->count * sfmt->channels_per_frame, + bp->data, bp->count * sfmt->bytes_per_frame); + bbp->count = total; + bp->count = 0; + bp = bbp; + self->nbuffer ^= 1; + } + } + self->process = process3; + + if (bp->count >= 2 * LPC_ORDER) + extrapolate(self, bp, buffer, nframes); + else + memset(buffer, 0, nframes * sfmt->bytes_per_frame); + return nframes; +} + +static int process3(extrapolater_t *self, void *buffer, unsigned nframes) +{ + return 0; +} + +static int read_frames(pcm_reader_t *reader, void *buffer, unsigned nframes) +{ + extrapolater_t *self = (extrapolater_t *)reader; + return self->process(self, buffer, nframes); +} + +static void teardown(pcm_reader_t **reader) +{ + extrapolater_t *self = (extrapolater_t *)*reader; + pcm_teardown(&self->src); + free(self->buffer[0].data); + free(self->buffer[1].data); + free(self); + *reader = 0; +} + +static pcm_reader_vtbl_t my_vtable = { + get_format, get_length, get_position, read_frames, teardown +}; + +pcm_reader_t *extrapolater_open(pcm_reader_t *reader) +{ + extrapolater_t *self = 0; + + if ((self = calloc(1, sizeof(extrapolater_t))) == 0) + return 0; + self->src = reader; + self->vtbl = &my_vtable; + self->process = process0; + return (pcm_reader_t *)self; +} diff --git a/src/lpc.c b/src/lpc.c new file mode 100644 index 0000000..dbd8d90 --- /dev/null +++ b/src/lpc.c @@ -0,0 +1,169 @@ +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2009 * + * by the Xiph.Org Foundation http://www.xiph.org/ * + * * + ******************************************************************** + + function: LPC low level routines + last mod: $Id: lpc.c 16227 2009-07-08 06:58:46Z xiphmont $ + + ********************************************************************/ + +/* Some of these routines (autocorrelator, LPC coefficient estimator) + are derived from code written by Jutta Degener and Carsten Bormann; + thus we include their copyright below. The entirety of this file + is freely redistributable on the condition that both of these + copyright notices are preserved without modification. */ + +/* Preserved Copyright: *********************************************/ + +/* Copyright 1992, 1993, 1994 by Jutta Degener and Carsten Bormann, +Technische Universita"t Berlin + +Any use of this software is permitted provided that this notice is not +removed and that neither the authors nor the Technische Universita"t +Berlin are deemed to have made any representations as to the +suitability of this software for any purpose nor are held responsible +for any defects of this software. THERE IS ABSOLUTELY NO WARRANTY FOR +THIS SOFTWARE. + +As a matter of courtesy, the authors request to be informed about uses +this software has found, about bugs in this software, and about any +improvements that may be of general interest. + +Berlin, 28.11.1994 +Jutta Degener +Carsten Bormann + +*********************************************************************/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#if HAVE_STDINT_H +# include +#endif + +#include +#include +#include +#include "lpc.h" +#include "lpcm.h" + +/* Autocorrelation LPC coeff generation algorithm invented by + N. Levinson in 1947, modified by J. Durbin in 1959. */ + +/* Input : n elements of time doamin data + Output: m lpc coefficients, excitation energy */ + +float vorbis_lpc_from_data(short *data,float *lpci,int n,int m,int stride){ + double *aut=malloc(sizeof(*aut)*(m+1)); + double *lpc=malloc(sizeof(*lpc)*(m)); + double error; + double epsilon; + int i,j; + + /* autocorrelation, p+1 lag coefficients */ + j=m+1; + while(j--){ + double d=0; /* double needed for accumulator depth */ + for(i=j;i -inline int lrint(double x) -{ - return _mm_cvtsd_si32(_mm_load_sd(&x)); -} -# endif -#endif - -static -inline double pcm_clip(double n, double min_value, double max_value) -{ - if (n < min_value) - return min_value; - else if (n > max_value) - return max_value; - return n; -} static inline float pcm_i2f(int32_t n) { diff --git a/src/lpcm.h b/src/lpcm.h index 8e896ae..2ac93ab 100644 --- a/src/lpcm.h +++ b/src/lpcm.h @@ -31,6 +31,36 @@ typedef struct pcm_sample_description_t { #define PCM_BYTES_PER_CHANNEL(desc) \ ((desc)->bytes_per_frame / (desc)->channels_per_frame) +#if defined(_MSC_VER) && _MSC_VER < 1800 +# ifdef _M_IX86 +static inline int lrint(double x) +{ + int n; + _asm { + fld x + fistp n + } + return n; +} +# else +# include +static inline int lrint(double x) +{ + return _mm_cvtsd_si32(_mm_load_sd(&x)); +} +# endif +#endif + +static +inline double pcm_clip(double n, double min_value, double max_value) +{ + if (n < min_value) + return min_value; + else if (n > max_value) + return max_value; + return n; +} + int pcm_convert_to_native_sint16(const pcm_sample_description_t *format, const void *input, uint32_t nframes, int16_t *result); diff --git a/src/main.c b/src/main.c index 0d07690..5e0dc0c 100644 --- a/src/main.c +++ b/src/main.c @@ -489,51 +489,82 @@ int write_sample(FILE *ofp, m4af_ctx_t *m4af, } static -int encode(pcm_reader_t *reader, HANDLE_AACENCODER encoder, - uint32_t frame_length, FILE *ofp, m4af_ctx_t *m4af, - int show_progress) +int encode(aacenc_param_ex_t *params, pcm_reader_t *reader, + HANDLE_AACENCODER encoder, uint32_t frame_length, + m4af_ctx_t *m4af) { - int16_t *ibuf = 0; - uint8_t *obuf = 0; - uint32_t olen; - uint32_t osize = 0; + struct buffer_t { + uint8_t *data; + uint32_t len, size; + }; + int16_t *ibuf = 0, *ip; + struct buffer_t obuf[2] = {{ 0 }}, *obp; + unsigned flip = 0; int nread = 1; - int consumed; int rc = -1; - int frames_written = 0; + int remaining, consumed; + int frames_written = 0, encoded = 0; aacenc_progress_t progress = { 0 }; const pcm_sample_description_t *fmt = pcm_get_format(reader); ibuf = malloc(frame_length * fmt->bytes_per_frame); aacenc_progress_init(&progress, pcm_get_length(reader), fmt->sample_rate); - do { + + for (;;) { + /* + * Since we delay the write, we cannot just exit loop when interrupted. + * Instead, we regard it as EOF. + */ if (g_interrupted) nread = 0; - else if (nread) { + if (nread > 0) { if ((nread = pcm_read_frames(reader, ibuf, frame_length)) < 0) { fprintf(stderr, "ERROR: read failed\n"); goto END; } - if (show_progress) + if (!params->silent) aacenc_progress_update(&progress, pcm_get_position(reader), fmt->sample_rate * 2); } - if ((consumed = aac_encode_frame(encoder, fmt, ibuf, nread, - &obuf, &olen, &osize)) < 0) - goto END; - if (olen > 0) { - if (write_sample(ofp, m4af, obuf, olen, frame_length) < 0) + ip = ibuf; + remaining = nread; + do { + obp = &obuf[flip]; + consumed = aac_encode_frame(encoder, fmt, ip, remaining, + &obp->data, &obp->len, &obp->size); + if (consumed < 0) goto END; + if (consumed == 0 && obp->len == 0) goto DONE; + if (obp->len == 0) break; + + remaining -= consumed; + ip += consumed * fmt->channels_per_frame; + flip ^= 1; + /* + * As we pad 1 frame at beginning and ending by our extrapolator, + * we want to drop them. + * We delay output by 1 frame by double buffering, and discard + * second frame and final frame from the encoder. + * Since sbr_header is included in the first frame (in case of + * SBR), we cannot discard first frame. So we pick second instead. + */ + ++encoded; + if (encoded == 1 || encoded == 3) + continue; + obp = &obuf[flip]; + if (write_sample(params->output_fp, m4af, obp->data, obp->len, + frame_length) < 0) goto END; ++frames_written; - } - } while (nread > 0 || olen > 0); - - if (show_progress) + } while (remaining > 0); + } +DONE: + if (!params->silent) aacenc_progress_finish(&progress, pcm_get_position(reader)); rc = frames_written; END: if (ibuf) free(ibuf); - if (obuf) free(obuf); + if (obuf[0].data) free(obuf[0].data); + if (obuf[1].data) free(obuf[1].data); return rc; } @@ -709,10 +740,13 @@ pcm_reader_t *open_input(aacenc_param_ex_t *params) } break; default: + fprintf(stderr, "ERROR: unsupported input file\n"); goto END; } } - return pcm_open_sint16_converter(reader); + if ((reader = pcm_open_sint16_converter(reader)) != 0) + reader = extrapolater_open(reader); + return reader; END: return 0; } @@ -794,8 +828,7 @@ int main(int argc, char **argv) m4af_set_priming_mode(m4af, params.gapless_mode + 1); m4af_begin_write(m4af); } - frame_count = encode(reader, encoder, aacinfo.frameLength, - params.output_fp, m4af, !params.silent); + frame_count = encode(¶ms, reader, encoder, aacinfo.frameLength, m4af); if (frame_count < 0) goto END; if (m4af) { diff --git a/src/pcm_reader.h b/src/pcm_reader.h index ee062aa..ba998e3 100644 --- a/src/pcm_reader.h +++ b/src/pcm_reader.h @@ -109,4 +109,6 @@ int apple_chan_chunk(pcm_io_context_t *io, uint32_t chunk_size, pcm_reader_t *pcm_open_sint16_converter(pcm_reader_t *reader); +pcm_reader_t *extrapolater_open(pcm_reader_t *reader); + #endif -- 2.30.2