Applies soft-clipping to the int decoder API.
authorJean-Marc Valin <jmvalin@jmvalin.ca>
Fri, 1 Mar 2013 20:18:23 +0000 (15:18 -0500)
committerJean-Marc Valin <jmvalin@jmvalin.ca>
Fri, 1 Mar 2013 20:23:01 +0000 (15:23 -0500)
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
src/opus.c
src/opus_decoder.c
src/opus_multistream_decoder.c
src/opus_private.h

index 02033a9..180146a 100644 (file)
@@ -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
index d6ae7ba..170bc4b 100644 (file)
 #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)
index 85c256f..5cae08c 100644 (file)
@@ -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
index 7564c73..47f87b1 100644 (file)
@@ -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
 
index 977f4a2..c9a4ff5 100644 (file)
@@ -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)