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:
		
							
								
								
									
										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(); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Max Kellermann
					Max Kellermann