@@ 13,6 13,8 @@
#include <Audio/transcode/Transform.hpp>
#include <Audio/transcode/MonoToStereo.hpp>
#include <Audio/transcode/TransformComposite.hpp>
+#include <Audio/transcode/BasicInterpolator.hpp>
+#include <Audio/transcode/BasicDecimator.hpp>
#include <cstdlib>
@@ 264,3 266,64 @@ TEST(Transform, Composite)
auto outputBlockSize = composite.transformBlockSize(sizeof(inputBuffer));
EXPECT_EQ(outputBlockSize, 2 * sizeof(inputBuffer));
}
+
+TEST(Transform, BasicInterpolator)
+{
+ audio::transcode::BasicInterpolator<std::uint16_t, 2, 2> interp2;
+ audio::transcode::BasicInterpolator<std::uint32_t, 1, 3> interp3;
+
+ EXPECT_EQ(interp2.transformBlockSize(128), 256);
+ EXPECT_EQ(interp3.transformBlockSize(100), 300);
+
+ auto format = audio::AudioFormat{8000, 16, 2};
+ auto outputFormat2 = interp2.transformFormat(format);
+ auto outputFormat3 = interp3.transformFormat(format);
+
+ EXPECT_EQ(outputFormat2.getSampleRate(), 16000);
+ EXPECT_EQ(outputFormat3.getSampleRate(), 24000);
+
+ EXPECT_EQ(outputFormat2.getBitWidth(), 16);
+ EXPECT_EQ(outputFormat3.getBitWidth(), 16);
+
+ EXPECT_EQ(outputFormat2.getChannels(), 2);
+ EXPECT_EQ(outputFormat3.getChannels(), 2);
+
+ EXPECT_TRUE(interp2.validateInputFormat(format));
+ EXPECT_FALSE(interp3.validateInputFormat(format));
+
+ std::uint16_t inputBuffer[8] = {1, 2, 3, 4, 0, 0, 0, 0};
+ static const uint16_t expectBuffer[8] = {1, 2, 1, 2, 3, 4, 3, 4};
+ auto inputSpan = ::audio::AbstractStream::Span{.data = reinterpret_cast<uint8_t *>(inputBuffer),
+ .dataSize = 4 * sizeof(std::uint16_t)};
+ auto outputSpan = interp2.transform(inputSpan, inputSpan);
+
+ EXPECT_EQ(outputSpan.dataSize, sizeof(uint16_t) * 8);
+ EXPECT_EQ(memcmp(outputSpan.data, expectBuffer, outputSpan.dataSize), 0);
+}
+
+TEST(Transform, BasicDecimator)
+{
+ audio::transcode::BasicDecimator<std::uint16_t, 2, 2> decim2;
+
+ EXPECT_EQ(decim2.transformBlockSize(128), 64);
+
+ auto format = audio::AudioFormat{16000, 16, 2};
+ auto outputFormat2 = decim2.transformFormat(format);
+
+ EXPECT_EQ(outputFormat2.getSampleRate(), 8000);
+ EXPECT_EQ(outputFormat2.getBitWidth(), 16);
+ EXPECT_EQ(outputFormat2.getChannels(), 2);
+
+ auto invalidFormat = audio::AudioFormat{16000, 8, 2};
+ EXPECT_TRUE(decim2.validateInputFormat(format));
+ EXPECT_FALSE(decim2.validateInputFormat(invalidFormat));
+
+ std::uint16_t inputBuffer[8] = {1, 2, 1, 2, 3, 4, 3, 4};
+ static const uint16_t expectBuffer[8] = {1, 2, 3, 4, 0, 0, 0, 0};
+ auto inputSpan = ::audio::AbstractStream::Span{.data = reinterpret_cast<uint8_t *>(inputBuffer),
+ .dataSize = 8 * sizeof(std::uint16_t)};
+ auto outputSpan = decim2.transform(inputSpan, inputSpan);
+
+ EXPECT_EQ(outputSpan.dataSize, sizeof(uint16_t) * 4);
+ EXPECT_EQ(memcmp(outputSpan.data, expectBuffer, outputSpan.dataSize), 0);
+}
@@ 0,0 1,70 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include "Transform.hpp"
+
+#include <integer.hpp>
+
+#include <type_traits>
+
+#include <cstdint>
+
+namespace audio::transcode
+{
+ /**
+ * @brief Basic decimation transformation - for every Ratio samples it drops
+ * Ratio - 1 samples. The transformation is performed using basic integer type
+ * to allow compiler to perform loop optimizations. The transformation is performed
+ * in-place.
+ *
+ * @tparam SampleType - type of a single PCM sample, e.g., std::uint16_t for LPCM16
+ * @tparam Channels - number of channels; 1 for mono, 2 for stereo
+ * @tparam Ratio - order of the decimator; e.g.: for Ratio = 4 drops 3 sample for each block of 4
+ * reducing sample rate by the factor of 4.
+ */
+ template <typename SampleType, unsigned int Channels, unsigned int Ratio> class BasicDecimator : public Transform
+ {
+ static_assert(Channels == 1 || Channels == 2);
+ static_assert(std::is_integral<SampleType>::value);
+ static_assert(Ratio > 0);
+
+ /**
+ * @brief Integer type to be used to read and write data from/to a buffer.
+ */
+ using IntegerType = typename decltype(
+ utils::integer::getIntegerType<sizeof(SampleType) * utils::integer::BitsInByte * Channels>())::type;
+
+ public:
+ auto transformBlockSize(std::size_t blockSize) const noexcept -> std::size_t override
+ {
+ return blockSize / Ratio;
+ }
+
+ auto transformFormat(const audio::AudioFormat &inputFormat) const noexcept -> audio::AudioFormat override
+ {
+ return audio::AudioFormat{
+ inputFormat.getSampleRate() / Ratio, inputFormat.getBitWidth(), inputFormat.getChannels()};
+ }
+
+ auto validateInputFormat(const audio::AudioFormat &inputFormat) const noexcept -> bool override
+ {
+ return sizeof(SampleType) * utils::integer::BitsInByte == inputFormat.getBitWidth();
+ }
+
+ auto transform(const Span &inputSpan, const Span &transformSpace) const -> Span override
+ {
+ auto outputSpan = Span{.data = transformSpace.data, .dataSize = transformBlockSize(inputSpan.dataSize)};
+ IntegerType *input = reinterpret_cast<IntegerType *>(inputSpan.data);
+ IntegerType *output = reinterpret_cast<IntegerType *>(outputSpan.data);
+
+ for (unsigned i = 0; i < inputSpan.dataSize / sizeof(IntegerType) / Ratio; i++) {
+ output[i] = input[i * Ratio];
+ }
+
+ return outputSpan;
+ }
+ };
+
+} // namespace audio::transcode
@@ 0,0 1,72 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include "Transform.hpp"
+
+#include <integer.hpp>
+
+#include <type_traits>
+
+#include <cstdint>
+
+namespace audio::transcode
+{
+ /**
+ * @brief Basic interpolation transformation - for every Ratio samples it repeats
+ * Ratio - 1 samples. The transformation is performed using basic integer type
+ * to allow compiler to perform loop optimizations. The transformation is performed
+ * in-place. The transformed signal is not filtered with a low-pass filter.
+ *
+ * @tparam SampleType - type of a single PCM sample, e.g., std::uint16_t for LPCM16
+ * @tparam Channels - number of channels; 1 for mono, 2 for stereo
+ * @tparam Ratio - order of the interpolator; e.g.: for Ratio = 4 repeats first sample 3
+ * times for each block of 4 increasing sample rate by the factor of 4.
+ */
+ template <typename SampleType, unsigned int Channels, unsigned int Ratio> class BasicInterpolator : public Transform
+ {
+ static_assert(Channels == 1 || Channels == 2);
+ static_assert(std::is_integral<SampleType>::value);
+ static_assert(Ratio > 0);
+
+ /**
+ * @brief Integer type to be used to read and write data from/to a buffer.
+ */
+ using IntegerType = typename decltype(
+ utils::integer::getIntegerType<sizeof(SampleType) * utils::integer::BitsInByte * Channels>())::type;
+
+ public:
+ auto transformBlockSize(std::size_t blockSize) const noexcept -> std::size_t override
+ {
+ return blockSize * Ratio;
+ }
+
+ auto transformFormat(const audio::AudioFormat &inputFormat) const noexcept -> audio::AudioFormat override
+ {
+ return audio::AudioFormat{
+ inputFormat.getSampleRate() * Ratio, inputFormat.getBitWidth(), inputFormat.getChannels()};
+ }
+
+ auto validateInputFormat(const audio::AudioFormat &inputFormat) const noexcept -> bool override
+ {
+ return sizeof(SampleType) * utils::integer::BitsInByte == inputFormat.getBitWidth();
+ }
+
+ auto transform(const Span &inputSpan, const Span &transformSpace) const -> Span override
+ {
+ auto outputSpan = Span{.data = transformSpace.data, .dataSize = transformBlockSize(inputSpan.dataSize)};
+ IntegerType *input = reinterpret_cast<IntegerType *>(inputSpan.data);
+ IntegerType *output = reinterpret_cast<IntegerType *>(outputSpan.data);
+
+ for (unsigned i = inputSpan.dataSize / sizeof(IntegerType); i > 0; i--) {
+ for (unsigned j = 1; j <= Ratio; j++) {
+ output[i * Ratio - j] = input[i - 1];
+ }
+ }
+
+ return outputSpan;
+ }
+ };
+
+} // namespace audio::transcode