From 32c4a0c96e239bee7623aef8ae592a5c7f7ec753 Mon Sep 17 00:00:00 2001 From: Jean-Marc Valin <jmvalin@jmvalin.ca> Date: Fri, 1 Mar 2013 15:18:23 -0500 Subject: [PATCH] Applies soft-clipping to the int decoder API. opus_decode() and opus_multistream_decode() now apply soft clipping before converting to 16-bit int. This should produce better a higher quality result than hard clipping like we were doing before. The _float() API isn't affected, but the clipping function is exported so users can manually apply the soft clipping. --- include/opus.h | 14 +++++ src/opus.c | 99 ++++++++++++++++++++++++++++++++++ src/opus_decoder.c | 23 +++++--- src/opus_multistream_decoder.c | 13 ++--- src/opus_private.h | 3 +- 5 files changed, 138 insertions(+), 14 deletions(-) diff --git a/include/opus.h b/include/opus.h index 02033a9d2..180146ae5 100644 --- a/include/opus.h +++ b/include/opus.h @@ -592,6 +592,20 @@ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_nb_samples(const unsigne * @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decoder_get_nb_samples(const OpusDecoder *dec, const unsigned char packet[], opus_int32 len) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2); + +/** Applies soft-clipping to bring a float signal within the [-1,1] range. If + * the signal is already in that range, nothing is done. If there are values + * outside of [-1,1], then the signal is clipped as smoothly as possible to + * both fit in the range and avoid creating excessive distortion in the + * process. + * @param [in,out] pcm <tt>float*</tt>: Input PCM and modified PCM + * @param [in] frame_size <tt>int</tt> Number of samples per channel to process + * @param [in] channels <tt>int</tt>: Number of channels + * @param [in,out] softclip_mem <tt>float*</tt>: State memory for the soft clipping process + */ +OPUS_EXPORT void opus_soft_clip(float *pcm, int frame_size, int channels, float *softclip_mem); + + /**@}*/ /** @defgroup opus_repacketizer Repacketizer diff --git a/src/opus.c b/src/opus.c index d6ae7bab2..170bc4b6e 100644 --- a/src/opus.c +++ b/src/opus.c @@ -32,6 +32,105 @@ #include "opus.h" #include "opus_private.h" +#ifndef DISABLE_FLOAT_API +OPUS_EXPORT void opus_pcm_soft_clip(float *_x, int N, int C, float *declip_mem) +{ + int c; + int i; + float *x; + + /* First thing: saturate everything to +/- 2 which is the highest level our + non-linearity can handle. At the point where the signal reaches +/-2, + the derivative will be zero anyway, so this doesn't introduce any + discontinuity in the derivative. */ + for (i=0;i<N*C;i++) + _x[i] = MAX16(-2.f, MIN16(2.f, _x[i])); + for (c=0;c<C;c++) + { + float a; + float x0; + int curr; + + x = _x+c; + a = declip_mem[c]; + /* Continue applying the non-linearity from the previous frame to avoid + any discontinuity. */ + for (i=0;i<N;i++) + { + if (x[i*C]*a>=0) + break; + x[i*C] = x[i*C]+a*x[i*C]*x[i*C]; + } + + curr=0; + x0 = x[0]; + while(1) + { + int start, end; + float maxval; + int special=0; + int peak_pos; + for (i=curr;i<N;i++) + { + if (x[i*C]>1 || x[i*C]<-1) + break; + } + if (i==N) + { + a=0; + break; + } + peak_pos = i; + start=end=i; + maxval=ABS16(x[i*C]); + /* Look for first zero crossing before clipping */ + while (start>0 && x[i*C]*x[(start-1)*C]>=0) + start--; + /* Look for first zero crossing after clipping */ + while (end<N && x[i*C]*x[end*C]>=0) + { + /* Look for other peaks until the next zero-crossing. */ + if (ABS16(x[end*C])>maxval) + { + maxval = ABS16(x[end*C]); + peak_pos = end; + } + end++; + } + /* Detect the special case where we clip before the first zero crossing */ + special = (start==0 && x[i*C]*x[0]>=0); + + /* Compute a such that maxval + a*maxval^2 = 1 */ + a=(maxval-1)/(maxval*maxval); + if (x[i*C]>0) + a = -a; + /* Apply soft clipping */ + for (i=start;i<end;i++) + x[i*C] = x[i*C]+a*x[i*C]*x[i*C]; + + if (special && peak_pos>=2) + { + /* Add a linear ramp from the first sample to the signal peak. + This avoids a discontinuity at the beginning of the frame. */ + float delta; + float offset = x0-x[0]; + delta = offset / peak_pos; + for (i=curr;i<peak_pos;i++) + { + offset -= delta; + x[i*C] += offset; + x[i*C] = MAX16(-1.f, MIN16(1.f, x[i*C])); + } + } + curr = end; + if (curr==N) + break; + } + declip_mem[c] = a; + } +} +#endif + int encode_size(int size, unsigned char *data) { if (size < 252) diff --git a/src/opus_decoder.c b/src/opus_decoder.c index 85c256fd5..5cae08ca8 100644 --- a/src/opus_decoder.c +++ b/src/opus_decoder.c @@ -65,6 +65,9 @@ struct OpusDecoder { int frame_size; int prev_redundancy; int last_packet_duration; +#ifndef FIXED_POINT + opus_val16 softclip_mem[2]; +#endif opus_uint32 rangeFinal; }; @@ -732,7 +735,7 @@ int opus_packet_parse(const unsigned char *data, opus_int32 len, int opus_decode_native(OpusDecoder *st, const unsigned char *data, opus_int32 len, opus_val16 *pcm, int frame_size, int decode_fec, - int self_delimited, int *packet_offset) + int self_delimited, int *packet_offset, int soft_clip) { int i, nb_samples; int count, offset; @@ -779,10 +782,10 @@ int opus_decode_native(OpusDecoder *st, const unsigned char *data, int ret; /* If no FEC can be present, run the PLC (recursive call) */ if (frame_size <= packet_frame_size || packet_mode == MODE_CELT_ONLY || st->mode == MODE_CELT_ONLY) - return opus_decode_native(st, NULL, 0, pcm, frame_size, 0, 0, NULL); + return opus_decode_native(st, NULL, 0, pcm, frame_size, 0, 0, NULL, soft_clip); /* Otherwise, run the PLC on everything except the size for which we might have FEC */ duration_copy = st->last_packet_duration; - ret = opus_decode_native(st, NULL, 0, pcm, frame_size-packet_frame_size, 0, 0, NULL); + ret = opus_decode_native(st, NULL, 0, pcm, frame_size-packet_frame_size, 0, 0, NULL, soft_clip); if (ret<0) { st->last_packet_duration = duration_copy; @@ -837,6 +840,12 @@ int opus_decode_native(OpusDecoder *st, const unsigned char *data, st->last_packet_duration = nb_samples; if (OPUS_CHECK_ARRAY(pcm, nb_samples*st->channels)) OPUS_PRINT_INT(nb_samples); +#ifndef FIXED_POINT + if (soft_clip) + opus_pcm_soft_clip(pcm, nb_samples, st->channels, st->softclip_mem); + else + st->softclip_mem[0]=st->softclip_mem[1]=0; +#endif return nb_samples; } @@ -845,7 +854,7 @@ int opus_decode_native(OpusDecoder *st, const unsigned char *data, int opus_decode(OpusDecoder *st, const unsigned char *data, opus_int32 len, opus_val16 *pcm, int frame_size, int decode_fec) { - return opus_decode_native(st, data, len, pcm, frame_size, decode_fec, 0, NULL); + return opus_decode_native(st, data, len, pcm, frame_size, decode_fec, 0, NULL, 0); } #ifndef DISABLE_FLOAT_API @@ -858,7 +867,7 @@ int opus_decode_float(OpusDecoder *st, const unsigned char *data, ALLOC(out, frame_size*st->channels, opus_int16); - ret = opus_decode_native(st, data, len, out, frame_size, decode_fec, 0, NULL); + ret = opus_decode_native(st, data, len, out, frame_size, decode_fec, 0, NULL, 0); if (ret > 0) { for (i=0;i<ret*st->channels;i++) @@ -886,7 +895,7 @@ int opus_decode(OpusDecoder *st, const unsigned char *data, ALLOC(out, frame_size*st->channels, float); - ret = opus_decode_native(st, data, len, out, frame_size, decode_fec, 0, NULL); + ret = opus_decode_native(st, data, len, out, frame_size, decode_fec, 0, NULL, 1); if (ret > 0) { for (i=0;i<ret*st->channels;i++) @@ -899,7 +908,7 @@ int opus_decode(OpusDecoder *st, const unsigned char *data, int opus_decode_float(OpusDecoder *st, const unsigned char *data, opus_int32 len, opus_val16 *pcm, int frame_size, int decode_fec) { - return opus_decode_native(st, data, len, pcm, frame_size, decode_fec, 0, NULL); + return opus_decode_native(st, data, len, pcm, frame_size, decode_fec, 0, NULL, 0); } #endif diff --git a/src/opus_multistream_decoder.c b/src/opus_multistream_decoder.c index 7564c7355..47f87b193 100644 --- a/src/opus_multistream_decoder.c +++ b/src/opus_multistream_decoder.c @@ -159,7 +159,8 @@ static int opus_multistream_decode_native( void *pcm, opus_copy_channel_out_func copy_channel_out, int frame_size, - int decode_fec + int decode_fec, + int soft_clip ) { opus_int32 Fs; @@ -199,7 +200,7 @@ static int opus_multistream_decode_native( return OPUS_INVALID_PACKET; } packet_offset = 0; - ret = opus_decode_native(dec, data, len, buf, frame_size, decode_fec, s!=st->layout.nb_streams-1, &packet_offset); + ret = opus_decode_native(dec, data, len, buf, frame_size, decode_fec, s!=st->layout.nb_streams-1, &packet_offset, soft_clip); data += packet_offset; len -= packet_offset; if (ret > frame_size) @@ -333,7 +334,7 @@ int opus_multistream_decode( ) { return opus_multistream_decode_native(st, data, len, - pcm, opus_copy_channel_out_short, frame_size, decode_fec); + pcm, opus_copy_channel_out_short, frame_size, decode_fec, 0); } #ifndef DISABLE_FLOAT_API @@ -341,7 +342,7 @@ int opus_multistream_decode_float(OpusMSDecoder *st, const unsigned char *data, opus_int32 len, float *pcm, int frame_size, int decode_fec) { return opus_multistream_decode_native(st, data, len, - pcm, opus_copy_channel_out_float, frame_size, decode_fec); + pcm, opus_copy_channel_out_float, frame_size, decode_fec, 0); } #endif @@ -351,7 +352,7 @@ int opus_multistream_decode(OpusMSDecoder *st, const unsigned char *data, opus_int32 len, opus_int16 *pcm, int frame_size, int decode_fec) { return opus_multistream_decode_native(st, data, len, - pcm, opus_copy_channel_out_short, frame_size, decode_fec); + pcm, opus_copy_channel_out_short, frame_size, decode_fec, 1); } int opus_multistream_decode_float( @@ -364,7 +365,7 @@ int opus_multistream_decode_float( ) { return opus_multistream_decode_native(st, data, len, - pcm, opus_copy_channel_out_float, frame_size, decode_fec); + pcm, opus_copy_channel_out_float, frame_size, decode_fec, 0); } #endif diff --git a/src/opus_private.h b/src/opus_private.h index 977f4a257..c9a4ff53c 100644 --- a/src/opus_private.h +++ b/src/opus_private.h @@ -88,7 +88,8 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_ unsigned char *data, opus_int32 out_data_bytes, int lsb_depth); int opus_decode_native(OpusDecoder *st, const unsigned char *data, opus_int32 len, - opus_val16 *pcm, int frame_size, int decode_fec, int self_delimited, int *packet_offset); + opus_val16 *pcm, int frame_size, int decode_fec, int self_delimited, + int *packet_offset, int soft_clip); /* Make sure everything's aligned to sizeof(void *) bytes */ static inline int align(int i) -- GitLab