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.
This commit is contained in:
parent
69c3dcd105
commit
32c4a0c96e
5 changed files with 138 additions and 14 deletions
|
@ -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
|
* @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);
|
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
|
/** @defgroup opus_repacketizer Repacketizer
|
||||||
|
|
99
src/opus.c
99
src/opus.c
|
@ -32,6 +32,105 @@
|
||||||
#include "opus.h"
|
#include "opus.h"
|
||||||
#include "opus_private.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)
|
int encode_size(int size, unsigned char *data)
|
||||||
{
|
{
|
||||||
if (size < 252)
|
if (size < 252)
|
||||||
|
|
|
@ -65,6 +65,9 @@ struct OpusDecoder {
|
||||||
int frame_size;
|
int frame_size;
|
||||||
int prev_redundancy;
|
int prev_redundancy;
|
||||||
int last_packet_duration;
|
int last_packet_duration;
|
||||||
|
#ifndef FIXED_POINT
|
||||||
|
opus_val16 softclip_mem[2];
|
||||||
|
#endif
|
||||||
|
|
||||||
opus_uint32 rangeFinal;
|
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,
|
int opus_decode_native(OpusDecoder *st, const unsigned char *data,
|
||||||
opus_int32 len, opus_val16 *pcm, int frame_size, int decode_fec,
|
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 i, nb_samples;
|
||||||
int count, offset;
|
int count, offset;
|
||||||
|
@ -779,10 +782,10 @@ int opus_decode_native(OpusDecoder *st, const unsigned char *data,
|
||||||
int ret;
|
int ret;
|
||||||
/* If no FEC can be present, run the PLC (recursive call) */
|
/* 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)
|
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 */
|
/* Otherwise, run the PLC on everything except the size for which we might have FEC */
|
||||||
duration_copy = st->last_packet_duration;
|
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)
|
if (ret<0)
|
||||||
{
|
{
|
||||||
st->last_packet_duration = duration_copy;
|
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;
|
st->last_packet_duration = nb_samples;
|
||||||
if (OPUS_CHECK_ARRAY(pcm, nb_samples*st->channels))
|
if (OPUS_CHECK_ARRAY(pcm, nb_samples*st->channels))
|
||||||
OPUS_PRINT_INT(nb_samples);
|
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;
|
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,
|
int opus_decode(OpusDecoder *st, const unsigned char *data,
|
||||||
opus_int32 len, opus_val16 *pcm, int frame_size, int decode_fec)
|
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
|
#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);
|
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)
|
if (ret > 0)
|
||||||
{
|
{
|
||||||
for (i=0;i<ret*st->channels;i++)
|
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);
|
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)
|
if (ret > 0)
|
||||||
{
|
{
|
||||||
for (i=0;i<ret*st->channels;i++)
|
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,
|
int opus_decode_float(OpusDecoder *st, const unsigned char *data,
|
||||||
opus_int32 len, opus_val16 *pcm, int frame_size, int decode_fec)
|
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
|
#endif
|
||||||
|
|
|
@ -159,7 +159,8 @@ static int opus_multistream_decode_native(
|
||||||
void *pcm,
|
void *pcm,
|
||||||
opus_copy_channel_out_func copy_channel_out,
|
opus_copy_channel_out_func copy_channel_out,
|
||||||
int frame_size,
|
int frame_size,
|
||||||
int decode_fec
|
int decode_fec,
|
||||||
|
int soft_clip
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
opus_int32 Fs;
|
opus_int32 Fs;
|
||||||
|
@ -199,7 +200,7 @@ static int opus_multistream_decode_native(
|
||||||
return OPUS_INVALID_PACKET;
|
return OPUS_INVALID_PACKET;
|
||||||
}
|
}
|
||||||
packet_offset = 0;
|
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;
|
data += packet_offset;
|
||||||
len -= packet_offset;
|
len -= packet_offset;
|
||||||
if (ret > frame_size)
|
if (ret > frame_size)
|
||||||
|
@ -333,7 +334,7 @@ int opus_multistream_decode(
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
return opus_multistream_decode_native(st, data, len,
|
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
|
#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)
|
opus_int32 len, float *pcm, int frame_size, int decode_fec)
|
||||||
{
|
{
|
||||||
return opus_multistream_decode_native(st, data, len,
|
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
|
#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)
|
opus_int32 len, opus_int16 *pcm, int frame_size, int decode_fec)
|
||||||
{
|
{
|
||||||
return opus_multistream_decode_native(st, data, len,
|
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(
|
int opus_multistream_decode_float(
|
||||||
|
@ -364,7 +365,7 @@ int opus_multistream_decode_float(
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
return opus_multistream_decode_native(st, data, len,
|
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
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -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);
|
unsigned char *data, opus_int32 out_data_bytes, int lsb_depth);
|
||||||
|
|
||||||
int opus_decode_native(OpusDecoder *st, 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);
|
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 */
|
/* Make sure everything's aligned to sizeof(void *) bytes */
|
||||||
static inline int align(int i)
|
static inline int align(int i)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue