pcm/Volume: improved dithering
Instead of just adding a rectangular random value before shifting back to the normal scale, use the existing PcmDither library.
This commit is contained in:
parent
394e2815db
commit
afcf0795c4
2
NEWS
2
NEWS
|
@ -3,6 +3,8 @@ ver 0.19 (not yet released)
|
||||||
- new commands "addtagid", "cleartagid"
|
- new commands "addtagid", "cleartagid"
|
||||||
* input
|
* input
|
||||||
- alsa: new input plugin
|
- alsa: new input plugin
|
||||||
|
* filter
|
||||||
|
- volume: improved software volume dithering
|
||||||
* new resampler option using libsoxr
|
* new resampler option using libsoxr
|
||||||
|
|
||||||
ver 0.18.6 (2013/12/24)
|
ver 0.18.6 (2013/12/24)
|
||||||
|
|
|
@ -62,6 +62,19 @@ PcmDither::Dither(T sample)
|
||||||
return output >> scale_bits;
|
return output >> scale_bits;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename ST, unsigned SBITS, unsigned DBITS>
|
||||||
|
inline ST
|
||||||
|
PcmDither::DitherShift(ST sample)
|
||||||
|
{
|
||||||
|
static_assert(sizeof(ST) * 8 > SBITS, "Source type too small");
|
||||||
|
static_assert(SBITS > DBITS, "Non-positive scale_bits");
|
||||||
|
|
||||||
|
static constexpr ST MIN = -(ST(1) << (SBITS - 1));
|
||||||
|
static constexpr ST MAX = (ST(1) << (SBITS - 1)) - 1;
|
||||||
|
|
||||||
|
return Dither<ST, MIN, MAX, SBITS - DBITS>(sample);
|
||||||
|
}
|
||||||
|
|
||||||
template<typename ST, typename DT>
|
template<typename ST, typename DT>
|
||||||
inline typename DT::value_type
|
inline typename DT::value_type
|
||||||
PcmDither::DitherConvert(typename ST::value_type sample)
|
PcmDither::DitherConvert(typename ST::value_type sample)
|
||||||
|
|
|
@ -32,6 +32,18 @@ public:
|
||||||
constexpr PcmDither()
|
constexpr PcmDither()
|
||||||
:error{0, 0, 0}, random(0) {}
|
:error{0, 0, 0}, random(0) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shift the given sample by #SBITS-#DBITS to the right, and
|
||||||
|
* apply dithering.
|
||||||
|
*
|
||||||
|
* @param ST the input sample type
|
||||||
|
* @param SBITS the input bit width
|
||||||
|
* @param DBITS the output bit width
|
||||||
|
* @param sample the input sample value
|
||||||
|
*/
|
||||||
|
template<typename ST, unsigned SBITS, unsigned DBITS>
|
||||||
|
ST DitherShift(ST sample);
|
||||||
|
|
||||||
void Dither24To16(int16_t *dest, const int32_t *src,
|
void Dither24To16(int16_t *dest, const int32_t *src,
|
||||||
const int32_t *src_end);
|
const int32_t *src_end);
|
||||||
|
|
||||||
|
|
|
@ -25,62 +25,69 @@
|
||||||
#include "util/ConstBuffer.hxx"
|
#include "util/ConstBuffer.hxx"
|
||||||
#include "util/Error.hxx"
|
#include "util/Error.hxx"
|
||||||
|
|
||||||
|
#include "PcmDither.cxx" // including the .cxx file to get inlined templates
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
template<SampleFormat F, class Traits=SampleTraits<F>>
|
template<SampleFormat F, class Traits=SampleTraits<F>>
|
||||||
static inline typename Traits::value_type
|
static inline typename Traits::value_type
|
||||||
pcm_volume_sample(typename Traits::value_type _sample,
|
pcm_volume_sample(PcmDither &dither,
|
||||||
|
typename Traits::value_type _sample,
|
||||||
int volume)
|
int volume)
|
||||||
{
|
{
|
||||||
typename Traits::long_type sample(_sample);
|
typename Traits::long_type sample(_sample);
|
||||||
|
|
||||||
sample = (sample * volume + pcm_volume_dither() +
|
return dither.DitherShift<typename Traits::long_type,
|
||||||
PCM_VOLUME_1S / 2)
|
Traits::BITS + PCM_VOLUME_BITS,
|
||||||
>> PCM_VOLUME_BITS;
|
Traits::BITS>(sample * volume);
|
||||||
|
|
||||||
return PcmClamp<F, Traits>(sample);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<SampleFormat F, class Traits=SampleTraits<F>>
|
template<SampleFormat F, class Traits=SampleTraits<F>>
|
||||||
static void
|
static void
|
||||||
pcm_volume_change(typename Traits::pointer_type dest,
|
pcm_volume_change(PcmDither &dither,
|
||||||
|
typename Traits::pointer_type dest,
|
||||||
typename Traits::const_pointer_type src,
|
typename Traits::const_pointer_type src,
|
||||||
typename Traits::const_pointer_type end,
|
typename Traits::const_pointer_type end,
|
||||||
int volume)
|
int volume)
|
||||||
{
|
{
|
||||||
while (src < end) {
|
while (src < end) {
|
||||||
const auto sample = *src++;
|
const auto sample = *src++;
|
||||||
*dest++ = pcm_volume_sample<F, Traits>(sample, volume);
|
*dest++ = pcm_volume_sample<F, Traits>(dither, sample, volume);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
pcm_volume_change_8(int8_t *dest, const int8_t *src, const int8_t *end,
|
pcm_volume_change_8(PcmDither &dither,
|
||||||
|
int8_t *dest, const int8_t *src, const int8_t *end,
|
||||||
int volume)
|
int volume)
|
||||||
{
|
{
|
||||||
pcm_volume_change<SampleFormat::S8>(dest, src, end, volume);
|
pcm_volume_change<SampleFormat::S8>(dither, dest, src, end, volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
pcm_volume_change_16(int16_t *dest, const int16_t *src, const int16_t *end,
|
pcm_volume_change_16(PcmDither &dither,
|
||||||
|
int16_t *dest, const int16_t *src, const int16_t *end,
|
||||||
int volume)
|
int volume)
|
||||||
{
|
{
|
||||||
pcm_volume_change<SampleFormat::S16>(dest, src, end, volume);
|
pcm_volume_change<SampleFormat::S16>(dither, dest, src, end, volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
pcm_volume_change_24(int32_t *dest, const int32_t *src, const int32_t *end,
|
pcm_volume_change_24(PcmDither &dither,
|
||||||
|
int32_t *dest, const int32_t *src, const int32_t *end,
|
||||||
int volume)
|
int volume)
|
||||||
{
|
{
|
||||||
pcm_volume_change<SampleFormat::S24_P32>(dest, src, end, volume);
|
pcm_volume_change<SampleFormat::S24_P32>(dither, dest, src, end,
|
||||||
|
volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
pcm_volume_change_32(int32_t *dest, const int32_t *src, const int32_t *end,
|
pcm_volume_change_32(PcmDither &dither,
|
||||||
|
int32_t *dest, const int32_t *src, const int32_t *end,
|
||||||
int volume)
|
int volume)
|
||||||
{
|
{
|
||||||
pcm_volume_change<SampleFormat::S32>(dest, src, end, volume);
|
pcm_volume_change<SampleFormat::S32>(dither, dest, src, end, volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -143,28 +150,28 @@ PcmVolume::Apply(ConstBuffer<void> src)
|
||||||
gcc_unreachable();
|
gcc_unreachable();
|
||||||
|
|
||||||
case SampleFormat::S8:
|
case SampleFormat::S8:
|
||||||
pcm_volume_change_8((int8_t *)data,
|
pcm_volume_change_8(dither, (int8_t *)data,
|
||||||
(const int8_t *)src.data,
|
(const int8_t *)src.data,
|
||||||
(const int8_t *)end,
|
(const int8_t *)end,
|
||||||
volume);
|
volume);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SampleFormat::S16:
|
case SampleFormat::S16:
|
||||||
pcm_volume_change_16((int16_t *)data,
|
pcm_volume_change_16(dither, (int16_t *)data,
|
||||||
(const int16_t *)src.data,
|
(const int16_t *)src.data,
|
||||||
(const int16_t *)end,
|
(const int16_t *)end,
|
||||||
volume);
|
volume);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SampleFormat::S24_P32:
|
case SampleFormat::S24_P32:
|
||||||
pcm_volume_change_24((int32_t *)data,
|
pcm_volume_change_24(dither, (int32_t *)data,
|
||||||
(const int32_t *)src.data,
|
(const int32_t *)src.data,
|
||||||
(const int32_t *)end,
|
(const int32_t *)end,
|
||||||
volume);
|
volume);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SampleFormat::S32:
|
case SampleFormat::S32:
|
||||||
pcm_volume_change_32((int32_t *)data,
|
pcm_volume_change_32(dither, (int32_t *)data,
|
||||||
(const int32_t *)src.data,
|
(const int32_t *)src.data,
|
||||||
(const int32_t *)end,
|
(const int32_t *)end,
|
||||||
volume);
|
volume);
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
#include "PcmPrng.hxx"
|
#include "PcmPrng.hxx"
|
||||||
#include "AudioFormat.hxx"
|
#include "AudioFormat.hxx"
|
||||||
#include "PcmBuffer.hxx"
|
#include "PcmBuffer.hxx"
|
||||||
|
#include "PcmDither.hxx"
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
@ -87,6 +88,7 @@ class PcmVolume {
|
||||||
unsigned volume;
|
unsigned volume;
|
||||||
|
|
||||||
PcmBuffer buffer;
|
PcmBuffer buffer;
|
||||||
|
PcmDither dither;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PcmVolume()
|
PcmVolume()
|
||||||
|
|
|
@ -60,8 +60,9 @@ TestVolume(G g=G())
|
||||||
|
|
||||||
const auto _dest = ConstBuffer<value_type>::FromVoid(dest);
|
const auto _dest = ConstBuffer<value_type>::FromVoid(dest);
|
||||||
for (unsigned i = 0; i < N; ++i) {
|
for (unsigned i = 0; i < N; ++i) {
|
||||||
CPPUNIT_ASSERT(_dest.data[i] >= (_src[i] - 1) / 2);
|
const auto expected = (_src[i] + 1) / 2;
|
||||||
CPPUNIT_ASSERT(_dest.data[i] <= _src[i] / 2 + 1);
|
CPPUNIT_ASSERT(_dest.data[i] >= expected - 4);
|
||||||
|
CPPUNIT_ASSERT(_dest.data[i] <= expected + 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
pv.Close();
|
pv.Close();
|
||||||
|
|
Loading…
Reference in New Issue