From 8edde7a4b3d652d0ac6ac3770369ddababe310a3 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sun, 22 Dec 2013 23:26:52 +0100 Subject: pcm/Volume: convert to class Prepare for adding state. --- Makefile.am | 4 +- src/filter/ReplayGainFilterPlugin.cxx | 59 ++++------- src/filter/VolumeFilterPlugin.cxx | 63 +++--------- src/pcm/Volume.cxx | 87 ++++++++++------ src/pcm/Volume.hxx | 79 +++++++++++++-- test/read_mixer.cxx | 9 -- test/software_volume.cxx | 20 ++-- test/test_pcm_volume.cxx | 185 ++++++++++++---------------------- 8 files changed, 233 insertions(+), 273 deletions(-) diff --git a/Makefile.am b/Makefile.am index 767b4dcf2..0448cf416 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1397,7 +1397,7 @@ endif test_software_volume_SOURCES = test/software_volume.cxx \ test/stdbin.h \ - src/CheckAudioFormat.cxx \ + src/AudioFormat.cxx src/CheckAudioFormat.cxx \ src/AudioParser.cxx test_software_volume_LDADD = \ $(PCM_LIBS) \ @@ -1484,6 +1484,7 @@ test_read_mixer_SOURCES = test/read_mixer.cxx \ src/Log.cxx src/LogBackend.cxx \ src/MixerControl.cxx \ src/FilterPlugin.cxx \ + src/AudioFormat.cxx \ src/filter/VolumeFilterPlugin.cxx if ENABLE_BZIP2_TEST @@ -1538,6 +1539,7 @@ test_test_mixramp_LDADD = \ $(CPPUNIT_LIBS) test_test_pcm_SOURCES = \ + src/AudioFormat.cxx \ test/test_pcm_util.hxx \ test/test_pcm_dither.cxx \ test/test_pcm_pack.cxx \ diff --git a/src/filter/ReplayGainFilterPlugin.cxx b/src/filter/ReplayGainFilterPlugin.cxx index c6c3cc2aa..998fda6f7 100644 --- a/src/filter/ReplayGainFilterPlugin.cxx +++ b/src/filter/ReplayGainFilterPlugin.cxx @@ -28,6 +28,7 @@ #include "MixerControl.hxx" #include "pcm/Volume.hxx" #include "pcm/PcmBuffer.hxx" +#include "util/ConstBuffer.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "Log.hxx" @@ -55,8 +56,8 @@ class ReplayGainFilter final : public Filter { ReplayGainInfo info; /** - * The current volume, between 0 and a value that may or may not exceed - * #PCM_VOLUME_1. + * About the current volume: it is between 0 and a value that + * may or may not exceed #PCM_VOLUME_1. * * If the default value of true is used for replaygain_limit, the * application of the volume to the signal will never cause clipping. @@ -66,16 +67,11 @@ class ReplayGainFilter final : public Filter { * maintain a consistent audio level. Whether clipping will actually * occur depends on what value the user is using for replaygain_preamp. */ - unsigned volume; - - AudioFormat format; - - PcmBuffer buffer; + PcmVolume pv; public: ReplayGainFilter() - :mixer(nullptr), mode(REPLAY_GAIN_OFF), - volume(PCM_VOLUME_1) { + :mixer(nullptr), mode(REPLAY_GAIN_OFF) { info.Clear(); } @@ -125,6 +121,7 @@ public: void ReplayGainFilter::Update() { + unsigned volume = PCM_VOLUME_1; if (mode != REPLAY_GAIN_OFF) { const auto &tuple = info.tuples[mode]; float scale = tuple.CalculateScale(replay_gain_preamp, @@ -134,8 +131,9 @@ ReplayGainFilter::Update() "scale=%f\n", (double)scale); volume = pcm_float_to_volume(scale); - } else - volume = PCM_VOLUME_1; + } + + pv.SetVolume(volume); if (mixer != nullptr) { /* update the hardware mixer volume */ @@ -160,48 +158,25 @@ replay_gain_filter_init(gcc_unused const config_param ¶m, AudioFormat ReplayGainFilter::Open(AudioFormat &af, gcc_unused Error &error) { - format = af; + if (!pv.Open(af.format, error)) + return AudioFormat::Undefined(); - return format; + return af; } void ReplayGainFilter::Close() { - buffer.Clear(); + pv.Close(); } const void * ReplayGainFilter::FilterPCM(const void *src, size_t src_size, - size_t *dest_size_r, Error &error) + size_t *dest_size_r, gcc_unused Error &error) { - - *dest_size_r = src_size; - - if (volume == PCM_VOLUME_1) - /* optimized special case: 100% volume = no-op */ - return src; - - void *dest = buffer.Get(src_size); - if (volume <= 0) { - /* optimized special case: 0% volume = memset(0) */ - /* XXX is this valid for all sample formats? What - about floating point? */ - memset(dest, 0, src_size); - return dest; - } - - memcpy(dest, src, src_size); - - bool success = pcm_volume(dest, src_size, - format.format, - volume); - if (!success) { - error.Set(replay_gain_domain, "pcm_volume() has failed"); - return nullptr; - } - - return dest; + const auto dest = pv.Apply({src, src_size}); + *dest_size_r = dest.size; + return dest.data; } const struct filter_plugin replay_gain_filter_plugin = { diff --git a/src/filter/VolumeFilterPlugin.cxx b/src/filter/VolumeFilterPlugin.cxx index 2438e0336..8b9c6f8e9 100644 --- a/src/filter/VolumeFilterPlugin.cxx +++ b/src/filter/VolumeFilterPlugin.cxx @@ -23,8 +23,8 @@ #include "FilterInternal.hxx" #include "FilterRegistry.hxx" #include "pcm/Volume.hxx" -#include "pcm/PcmBuffer.hxx" #include "AudioFormat.hxx" +#include "util/ConstBuffer.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" @@ -32,29 +32,15 @@ #include class VolumeFilter final : public Filter { - /** - * The current volume, from 0 to #PCM_VOLUME_1. - */ - unsigned volume; - - AudioFormat format; - - PcmBuffer buffer; + PcmVolume pv; public: - VolumeFilter() - :volume(PCM_VOLUME_1) {} - unsigned GetVolume() const { - assert(volume <= PCM_VOLUME_1); - - return volume; + return pv.GetVolume(); } void SetVolume(unsigned _volume) { - assert(_volume <= PCM_VOLUME_1); - - volume = _volume; + pv.SetVolume(_volume); } virtual AudioFormat Open(AudioFormat &af, Error &error) override; @@ -73,50 +59,27 @@ volume_filter_init(gcc_unused const config_param ¶m, } AudioFormat -VolumeFilter::Open(AudioFormat &audio_format, gcc_unused Error &error) +VolumeFilter::Open(AudioFormat &audio_format, Error &error) { - format = audio_format; + if (!pv.Open(audio_format.format, error)) + return AudioFormat::Undefined(); - return format; + return audio_format; } void VolumeFilter::Close() { - buffer.Clear(); + pv.Close(); } const void * VolumeFilter::FilterPCM(const void *src, size_t src_size, - size_t *dest_size_r, Error &error) + size_t *dest_size_r, gcc_unused Error &error) { - *dest_size_r = src_size; - - if (volume >= PCM_VOLUME_1) - /* optimized special case: 100% volume = no-op */ - return src; - - void *dest = buffer.Get(src_size); - - if (volume <= 0) { - /* optimized special case: 0% volume = memset(0) */ - /* XXX is this valid for all sample formats? What - about floating point? */ - memset(dest, 0, src_size); - return dest; - } - - memcpy(dest, src, src_size); - - bool success = pcm_volume(dest, src_size, - format.format, - volume); - if (!success) { - error.Set(volume_domain, "pcm_volume() has failed"); - return NULL; - } - - return dest; + const auto dest = pv.Apply({src, src_size}); + *dest_size_r = dest.size; + return dest.data; } const struct filter_plugin volume_filter_plugin = { diff --git a/src/pcm/Volume.cxx b/src/pcm/Volume.cxx index 410df7695..539ef1238 100644 --- a/src/pcm/Volume.cxx +++ b/src/pcm/Volume.cxx @@ -19,9 +19,11 @@ #include "config.h" #include "Volume.hxx" +#include "Domain.hxx" #include "PcmUtils.hxx" #include "Traits.hxx" -#include "AudioFormat.hxx" +#include "util/ConstBuffer.hxx" +#include "util/Error.hxx" #include #include @@ -143,61 +145,88 @@ pcm_volume_change_float(float *dest, const float *src, const float *end, } bool -pcm_volume(void *buffer, size_t length, - SampleFormat format, - int volume) +PcmVolume::Open(SampleFormat _format, Error &error) { - if (volume == PCM_VOLUME_1S) - return true; + assert(format == SampleFormat::UNDEFINED); - if (volume <= 0) { - memset(buffer, 0, length); - return true; + switch (_format) { + case SampleFormat::UNDEFINED: + case SampleFormat::DSD: + error.Format(pcm_domain, + "Software volume for %s is not implemented", + sample_format_to_string(_format)); + return false; + + case SampleFormat::S8: + case SampleFormat::S16: + case SampleFormat::S24_P32: + case SampleFormat::S32: + case SampleFormat::FLOAT: + break; } - const void *end = pcm_end_pointer(buffer, length); + format = _format; + return true; +} + +ConstBuffer +PcmVolume::Apply(ConstBuffer src) +{ + if (volume == PCM_VOLUME_1) + return src; + + void *data = buffer.Get(src.size); + + if (volume == 0) { + /* optimized special case: 0% volume = memset(0) */ + /* TODO: is this valid for all sample formats? What + about floating point? */ + memset(data, 0, src.size); + return { data, src.size }; + } + + const void *end = pcm_end_pointer(src.data, src.size); switch (format) { case SampleFormat::UNDEFINED: case SampleFormat::DSD: - /* not implemented */ - return false; + assert(false); + gcc_unreachable(); case SampleFormat::S8: - pcm_volume_change_8((int8_t *)buffer, - (const int8_t *)buffer, + pcm_volume_change_8((int8_t *)data, + (const int8_t *)src.data, (const int8_t *)end, volume); - return true; + break; case SampleFormat::S16: - pcm_volume_change_16((int16_t *)buffer, - (const int16_t *)buffer, + pcm_volume_change_16((int16_t *)data, + (const int16_t *)src.data, (const int16_t *)end, volume); - return true; + break; case SampleFormat::S24_P32: - pcm_volume_change_24((int32_t *)buffer, - (const int32_t *)buffer, + pcm_volume_change_24((int32_t *)data, + (const int32_t *)src.data, (const int32_t *)end, volume); - return true; + break; case SampleFormat::S32: - pcm_volume_change_32((int32_t *)buffer, - (const int32_t *)buffer, + pcm_volume_change_32((int32_t *)data, + (const int32_t *)src.data, (const int32_t *)end, volume); - return true; + break; case SampleFormat::FLOAT: - pcm_volume_change_float((float *)buffer, - (const float *)buffer, + pcm_volume_change_float((float *)data, + (const float *)src.data, (const float *)end, pcm_volume_to_float(volume)); - return true; + break; } - assert(false); - gcc_unreachable(); + return { data, src.size }; } diff --git a/src/pcm/Volume.hxx b/src/pcm/Volume.hxx index 52102a294..c31aafb6e 100644 --- a/src/pcm/Volume.hxx +++ b/src/pcm/Volume.hxx @@ -22,10 +22,18 @@ #include "PcmPrng.hxx" #include "AudioFormat.hxx" +#include "PcmBuffer.hxx" #include #include +#ifndef NDEBUG +#include +#endif + +class Error; +template struct ConstBuffer; + /** * Number of fractional bits for a fixed-point volume value. */ @@ -71,17 +79,66 @@ pcm_volume_dither(void) } /** - * Adjust the volume of the specified PCM buffer. - * - * @param buffer the PCM buffer - * @param length the length of the PCM buffer - * @param format the sample format of the PCM buffer - * @param volume the volume between 0 and #PCM_VOLUME_1 - * @return true on success, false if the audio format is not supported + * A class that converts samples from one format to another. */ -bool -pcm_volume(void *buffer, size_t length, - SampleFormat format, - int volume); +class PcmVolume { + SampleFormat format; + + unsigned volume; + + PcmBuffer buffer; + +public: + PcmVolume() + :volume(PCM_VOLUME_1) { +#ifndef NDEBUG + format = SampleFormat::UNDEFINED; +#endif + } + +#ifndef NDEBUG + ~PcmVolume() { + assert(format == SampleFormat::UNDEFINED); + } +#endif + + unsigned GetVolume() const { + return volume; + } + + /** + * @param _volume the volume level in the range + * [0..#PCM_VOLUME_1]; may be bigger than #PCM_VOLUME_1, but + * then it will most likely clip a lot + */ + void SetVolume(unsigned _volume) { + volume = _volume; + } + + /** + * Opens the object, prepare for Apply(). + * + * @param format the sample format + * @param error location to store the error + * @return true on success + */ + bool Open(SampleFormat format, Error &error); + + /** + * Closes the object. After that, you may call Open() again. + */ + void Close() { +#ifndef NDEBUG + assert(format != SampleFormat::UNDEFINED); + format = SampleFormat::UNDEFINED; +#endif + } + + /** + * Apply the volume level. + */ + gcc_pure + ConstBuffer Apply(ConstBuffer src); +}; #endif diff --git a/test/read_mixer.cxx b/test/read_mixer.cxx index 07ff931c8..5decc5798 100644 --- a/test/read_mixer.cxx +++ b/test/read_mixer.cxx @@ -101,15 +101,6 @@ filter_plugin_by_name(gcc_unused const char *name) return NULL; } -bool -pcm_volume(gcc_unused void *buffer, gcc_unused size_t length, - gcc_unused SampleFormat format, - gcc_unused int volume) -{ - assert(false); - return false; -} - int main(int argc, gcc_unused char **argv) { int volume; diff --git a/test/software_volume.cxx b/test/software_volume.cxx index b850217df..ae2d53a08 100644 --- a/test/software_volume.cxx +++ b/test/software_volume.cxx @@ -27,12 +27,14 @@ #include "pcm/Volume.hxx" #include "AudioParser.hxx" #include "AudioFormat.hxx" +#include "util/ConstBuffer.hxx" #include "util/Error.hxx" #include "stdbin.h" #include #include +#include #include int main(int argc, char **argv) @@ -55,14 +57,16 @@ int main(int argc, char **argv) } } - while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) { - if (!pcm_volume(buffer, nbytes, - audio_format.format, - PCM_VOLUME_1 / 2)) { - g_printerr("pcm_volume() has failed\n"); - return 2; - } + PcmVolume pv; + if (!pv.Open(audio_format.format, error)) { + fprintf(stderr, "%s\n", error.GetMessage()); + return EXIT_FAILURE; + } - gcc_unused ssize_t ignored = write(1, buffer, nbytes); + while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) { + auto dest = pv.Apply({buffer, size_t(nbytes)}); + gcc_unused ssize_t ignored = write(1, dest.data, dest.size); } + + pv.Close(); } diff --git a/test/test_pcm_volume.cxx b/test/test_pcm_volume.cxx index fae937c30..bba5d3bbf 100644 --- a/test/test_pcm_volume.cxx +++ b/test/test_pcm_volume.cxx @@ -17,169 +17,108 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "test_pcm_all.hxx" #include "pcm/Volume.hxx" +#include "pcm/Traits.hxx" +#include "util/ConstBuffer.hxx" +#include "util/Error.hxx" #include "test_pcm_util.hxx" #include #include -void -PcmVolumeTest::TestVolume8() +template, + typename G=RandomInt> +static void +TestVolume(G g=G()) { - constexpr unsigned N = 256; - static int8_t zero[N]; - const auto src = TestDataBuffer(); + typedef typename Traits::value_type value_type; + + PcmVolume pv; + CPPUNIT_ASSERT(pv.Open(F, IgnoreError())); - int8_t dest[N]; + constexpr size_t N = 256; + static value_type zero[N]; + const auto _src = TestDataBuffer(g); + const ConstBuffer src(_src, sizeof(_src)); - std::copy(src.begin(), src.end(), dest); - CPPUNIT_ASSERT_EQUAL(true, - pcm_volume(dest, sizeof(dest), - SampleFormat::S8, 0)); - CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, zero, sizeof(zero))); + pv.SetVolume(0); + auto dest = pv.Apply(src); + CPPUNIT_ASSERT_EQUAL(src.size, dest.size); + CPPUNIT_ASSERT_EQUAL(0, memcmp(dest.data, zero, sizeof(zero))); - std::copy(src.begin(), src.end(), dest); - CPPUNIT_ASSERT_EQUAL(true, - pcm_volume(dest, sizeof(dest), - SampleFormat::S8, PCM_VOLUME_1)); - CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, src, sizeof(src))); + pv.SetVolume(PCM_VOLUME_1); + dest = pv.Apply(src); + CPPUNIT_ASSERT_EQUAL(src.size, dest.size); + CPPUNIT_ASSERT_EQUAL(0, memcmp(dest.data, src.data, src.size)); - std::copy(src.begin(), src.end(), dest); - CPPUNIT_ASSERT_EQUAL(true, - pcm_volume(dest, sizeof(dest), - SampleFormat::S8, PCM_VOLUME_1 / 2)); + pv.SetVolume(PCM_VOLUME_1 / 2); + dest = pv.Apply(src); + CPPUNIT_ASSERT_EQUAL(src.size, dest.size); + const auto _dest = ConstBuffer::FromVoid(dest); for (unsigned i = 0; i < N; ++i) { - CPPUNIT_ASSERT(dest[i] >= (src[i] - 1) / 2); - CPPUNIT_ASSERT(dest[i] <= src[i] / 2 + 1); + CPPUNIT_ASSERT(_dest.data[i] >= (_src[i] - 1) / 2); + CPPUNIT_ASSERT(_dest.data[i] <= _src[i] / 2 + 1); } + + pv.Close(); } void -PcmVolumeTest::TestVolume16() +PcmVolumeTest::TestVolume8() { - constexpr unsigned N = 256; - static int16_t zero[N]; - const auto src = TestDataBuffer(); - - int16_t dest[N]; - - std::copy(src.begin(), src.end(), dest); - CPPUNIT_ASSERT_EQUAL(true, - pcm_volume(dest, sizeof(dest), - SampleFormat::S16, 0)); - CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, zero, sizeof(zero))); - - std::copy(src.begin(), src.end(), dest); - CPPUNIT_ASSERT_EQUAL(true, - pcm_volume(dest, sizeof(dest), - SampleFormat::S16, PCM_VOLUME_1)); - CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, src, sizeof(src))); - - std::copy(src.begin(), src.end(), dest); - CPPUNIT_ASSERT_EQUAL(true, - pcm_volume(dest, sizeof(dest), - SampleFormat::S16, PCM_VOLUME_1 / 2)); + TestVolume(); +} - for (unsigned i = 0; i < N; ++i) { - CPPUNIT_ASSERT(dest[i] >= (src[i] - 1) / 2); - CPPUNIT_ASSERT(dest[i] <= src[i] / 2 + 1); - } +void +PcmVolumeTest::TestVolume16() +{ + TestVolume(); } void PcmVolumeTest::TestVolume24() { - constexpr unsigned N = 256; - static int32_t zero[N]; - const auto src = TestDataBuffer(RandomInt24()); - - int32_t dest[N]; - - std::copy(src.begin(), src.end(), dest); - CPPUNIT_ASSERT_EQUAL(true, - pcm_volume(dest, sizeof(dest), - SampleFormat::S24_P32, 0)); - CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, zero, sizeof(zero))); - - std::copy(src.begin(), src.end(), dest); - CPPUNIT_ASSERT_EQUAL(true, - pcm_volume(dest, sizeof(dest), - SampleFormat::S24_P32, PCM_VOLUME_1)); - CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, src, sizeof(src))); - - std::copy(src.begin(), src.end(), dest); - CPPUNIT_ASSERT_EQUAL(true, - pcm_volume(dest, sizeof(dest), - SampleFormat::S24_P32, PCM_VOLUME_1 / 2)); - - for (unsigned i = 0; i < N; ++i) { - CPPUNIT_ASSERT(dest[i] >= (src[i] - 1) / 2); - CPPUNIT_ASSERT(dest[i] <= src[i] / 2 + 1); - } + TestVolume(RandomInt24()); } void PcmVolumeTest::TestVolume32() { - constexpr unsigned N = 256; - static int32_t zero[N]; - const auto src = TestDataBuffer(); - - int32_t dest[N]; - - std::copy(src.begin(), src.end(), dest); - CPPUNIT_ASSERT_EQUAL(true, - pcm_volume(dest, sizeof(dest), - SampleFormat::S32, 0)); - CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, zero, sizeof(zero))); - - std::copy(src.begin(), src.end(), dest); - CPPUNIT_ASSERT_EQUAL(true, - pcm_volume(dest, sizeof(dest), - SampleFormat::S32, PCM_VOLUME_1)); - CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, src, sizeof(src))); - - std::copy(src.begin(), src.end(), dest); - CPPUNIT_ASSERT_EQUAL(true, - pcm_volume(dest, sizeof(dest), - SampleFormat::S32, PCM_VOLUME_1 / 2)); - - for (unsigned i = 0; i < N; ++i) { - CPPUNIT_ASSERT(dest[i] >= (src[i] - 1) / 2); - CPPUNIT_ASSERT(dest[i] <= src[i] / 2 + 1); - } + TestVolume(); } void PcmVolumeTest::TestVolumeFloat() { - constexpr unsigned N = 256; - static float zero[N]; - const auto src = TestDataBuffer(RandomFloat()); + PcmVolume pv; + CPPUNIT_ASSERT(pv.Open(SampleFormat::FLOAT, IgnoreError())); - float dest[N]; + constexpr size_t N = 256; + static float zero[N]; + const auto _src = TestDataBuffer(RandomFloat()); + const ConstBuffer src(_src, sizeof(_src)); - std::copy(src.begin(), src.end(), dest); - CPPUNIT_ASSERT_EQUAL(true, - pcm_volume(dest, sizeof(dest), - SampleFormat::FLOAT, 0)); - CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, zero, sizeof(zero))); + pv.SetVolume(0); + auto dest = pv.Apply(src); + CPPUNIT_ASSERT_EQUAL(src.size, dest.size); + CPPUNIT_ASSERT_EQUAL(0, memcmp(dest.data, zero, sizeof(zero))); - std::copy(src.begin(), src.end(), dest); - CPPUNIT_ASSERT_EQUAL(true, - pcm_volume(dest, sizeof(dest), - SampleFormat::FLOAT, PCM_VOLUME_1)); - CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, src, sizeof(src))); + pv.SetVolume(PCM_VOLUME_1); + dest = pv.Apply(src); + CPPUNIT_ASSERT_EQUAL(src.size, dest.size); + CPPUNIT_ASSERT_EQUAL(0, memcmp(dest.data, src.data, src.size)); - std::copy(src.begin(), src.end(), dest); - CPPUNIT_ASSERT_EQUAL(true, - pcm_volume(dest, sizeof(dest), - SampleFormat::FLOAT, - PCM_VOLUME_1 / 2)); + pv.SetVolume(PCM_VOLUME_1 / 2); + dest = pv.Apply(src); + CPPUNIT_ASSERT_EQUAL(src.size, dest.size); + const auto _dest = ConstBuffer::FromVoid(dest); for (unsigned i = 0; i < N; ++i) - CPPUNIT_ASSERT_DOUBLES_EQUAL(src[i] / 2, dest[i], 1); + CPPUNIT_ASSERT_DOUBLES_EQUAL(_src[i] / 2, _dest.data[i], 1); + + pv.Close(); } -- cgit v1.2.3