~aleteoryx/muditaos

ece6a51e5b5e92bc50bf93dd1d67bb7bfbbd15b4 — Marcin Smoczyński 4 years ago fe79d2d
[EGD-6800] Enable voice transcoding

Add voice transcoding during phone call with two basic transforms:
 - sample rate downscaling by a factor of 2 with a decimator
 - sample rate upscaling by a factor of 2 with an interpolator (no
low-pass filter)

Signed-off-by: Marcin Smoczyński <smoczynski.marcin@gmail.com>
M module-audio/Audio/Operation/RouterOperation.cpp => module-audio/Audio/Operation/RouterOperation.cpp +18 -29
@@ 7,6 7,7 @@
#include <Audio/AudioCommon.hpp>
#include <Audio/Profiles/Profile.hpp>
#include <Audio/StreamFactory.hpp>
#include <Audio/transcode/TransformFactory.hpp>

#include <log/log.hpp>
#include <mutex.hpp>


@@ 50,16 51,6 @@ namespace audio
        operationToken = token;
        state          = State::Active;

        // check if audio devices support desired audio format
        if (!audioDevice->isFormatSupportedBySource(currentProfile->getAudioFormat())) {
            return RetCode::InvalidFormat;
        }

        if (!audioDeviceCellular->isFormatSupportedBySource(currentProfile->getAudioFormat())) {
            return RetCode::InvalidFormat;
        }

        // try to run devices with the format
        if (auto ret = audioDevice->Start(); ret != AudioDevice::RetCode::Success) {
            return GetDeviceError(ret);
        }


@@ 71,26 62,24 @@ namespace audio
        // create streams
        StreamFactory streamFactory(callTimeConstraint);
        try {
            dataStreamIn =
                streamFactory.makeStream(*audioDevice, *audioDeviceCellular, currentProfile->getAudioFormat());
            dataStreamOut =
                streamFactory.makeStream(*audioDevice, *audioDeviceCellular, currentProfile->getAudioFormat());
            dataStreamIn  = streamFactory.makeStream(*audioDevice, *audioDeviceCellular);
            dataStreamOut = streamFactory.makeStream(*audioDeviceCellular, *audioDevice);
        }
        catch (std::invalid_argument &e) {
        catch (const std::exception &e) {
            LOG_FATAL("Cannot create audio stream: %s", e.what());
            return audio::RetCode::Failed;
        }

        // create audio connections
        inputConnection =
            std::make_unique<audio::StreamConnection>(audioDeviceCellular.get(), audioDevice.get(), dataStreamIn.get());
        outputConnection = std::make_unique<audio::StreamConnection>(
            audioDevice.get(), audioDeviceCellular.get(), dataStreamOut.get());
        voiceInputConnection =
            std::make_unique<audio::StreamConnection>(audioDevice.get(), audioDeviceCellular.get(), dataStreamIn.get());
        voiceOutputConnection = std::make_unique<audio::StreamConnection>(
            audioDeviceCellular.get(), audioDevice.get(), dataStreamOut.get());

        // enable audio connections
        inputConnection->enable();
        voiceOutputConnection->enable();
        if (!IsMuted()) {
            outputConnection->enable();
            voiceInputConnection->enable();
        }

        return audio::RetCode::Success;


@@ 103,8 92,8 @@ namespace audio
        }

        state = State::Idle;
        outputConnection.reset();
        inputConnection.reset();
        voiceOutputConnection.reset();
        voiceInputConnection.reset();

        audioDevice->Stop();
        audioDeviceCellular->Stop();


@@ 122,8 111,8 @@ namespace audio
        }

        state = State::Paused;
        outputConnection->disable();
        inputConnection->disable();
        voiceOutputConnection->disable();
        voiceInputConnection->disable();
        return RetCode::Success;
    }



@@ 134,8 123,8 @@ namespace audio
        }

        state = State::Active;
        inputConnection->enable();
        outputConnection->enable();
        voiceInputConnection->enable();
        voiceOutputConnection->enable();
        return RetCode::Success;
    }



@@ 219,13 208,13 @@ namespace audio

    void RouterOperation::Mute()
    {
        outputConnection->disable();
        voiceInputConnection->disable();
        mute = Mute::Enabled;
    }

    void RouterOperation::Unmute()
    {
        outputConnection->enable();
        voiceInputConnection->enable();
        mute = Mute::Disabled;
    }


M module-audio/Audio/Operation/RouterOperation.hpp => module-audio/Audio/Operation/RouterOperation.hpp +4 -6
@@ 6,7 6,6 @@
#include "Operation.hpp"

#include <Audio/AudioDevice.hpp>
#include <Audio/encoder/Encoder.hpp>
#include <Audio/AudioCommon.hpp>
#include <Audio/Profiles/Profile.hpp>
#include <Audio/Endpoint.hpp>


@@ 68,12 67,11 @@ namespace audio
        void Unmute();
        [[nodiscard]] auto IsMuted() const noexcept -> bool;

        std::unique_ptr<Stream> dataStreamOut;
        std::unique_ptr<Stream> dataStreamIn;
        std::unique_ptr<Encoder> enc;
        std::unique_ptr<AbstractStream> dataStreamOut;
        std::unique_ptr<AbstractStream> dataStreamIn;
        std::shared_ptr<AudioDevice> audioDeviceCellular;
        std::unique_ptr<StreamConnection> outputConnection;
        std::unique_ptr<StreamConnection> inputConnection;
        std::unique_ptr<StreamConnection> voiceOutputConnection;
        std::unique_ptr<StreamConnection> voiceInputConnection;
    };

} // namespace audio

M module-audio/Audio/StreamFactory.cpp => module-audio/Audio/StreamFactory.cpp +49 -3
@@ 4,6 4,8 @@
#include "StreamFactory.hpp"
#include "Endpoint.hpp"

#include "transcode/TransformFactory.hpp"

#include <math/Math.hpp>

#include <algorithm>


@@ 15,24 17,45 @@
#include <cassert>
#include <cmath>

using audio::AudioFormat;
using audio::Sink;
using audio::Source;
using audio::Stream;
using audio::StreamFactory;
using audio::transcode::InputTranscodeProxy;
using audio::transcode::Transform;
using audio::transcode::TransformFactory;
using namespace std::chrono_literals;

StreamFactory::StreamFactory(std::chrono::milliseconds operationPeriodRequirement)
    : periodRequirement(operationPeriodRequirement)
{}

auto StreamFactory::makeStream(Source &source, Sink &sink, AudioFormat streamFormat) -> std::unique_ptr<Stream>
auto StreamFactory::makeStream(Source &source, Sink &sink) -> std::unique_ptr<AbstractStream>
{
    auto sourceFormat = source.getSourceFormat();
    auto sinkFormat   = sink.getSinkFormat();

    if (sourceFormat == sinkFormat) {
        return makeStream(source.getTraits(), sink.getTraits(), sourceFormat);
    }
    else {
        auto transformFactory = TransformFactory();
        auto transform        = transformFactory.makeTransform(sourceFormat, sinkFormat);

        return makeInputTranscodingStream(source, sink, sinkFormat, std::move(transform));
    }
}

auto StreamFactory::makeStream(Traits sourceTraits, Traits sinkTraits, AudioFormat streamFormat)
    -> std::unique_ptr<Stream>
{
    auto streamBuffering     = defaultBuffering;
    auto endpointsTraits     = {source.getTraits(), sink.getTraits()};
    auto endpointsTraits     = {sourceTraits, sinkTraits};
    auto blockSizeConstraint = getBlockSizeConstraint(endpointsTraits);
    auto &streamAllocator    = negotiateAllocator(endpointsTraits);
    auto timingConstraint    = getTimingConstraints(std::initializer_list<std::optional<std::chrono::milliseconds>>{
        sink.getTraits().timeConstraint, source.getTraits().timeConstraint, periodRequirement});
        sinkTraits.timeConstraint, sourceTraits.timeConstraint, periodRequirement});

    if (streamFormat == audio::nullFormat) {
        throw std::invalid_argument("No source format provided");


@@ 53,6 76,29 @@ auto StreamFactory::makeStream(Source &source, Sink &sink, AudioFormat streamFor
    return std::make_unique<Stream>(streamFormat, streamAllocator, blockSizeConstraint.value(), streamBuffering);
}

auto StreamFactory::makeStream(Source &source, Sink &sink, AudioFormat streamFormat) -> std::unique_ptr<Stream>
{
    return makeStream(source.getTraits(), sink.getTraits(), streamFormat);
}

auto StreamFactory::makeInputTranscodingStream(Source &source,
                                               Sink &sink,
                                               AudioFormat streamFormat,
                                               std::shared_ptr<Transform>(transform))
    -> std::unique_ptr<InputTranscodeProxy>
{
    auto sourceTraits = source.getTraits();

    if (sourceTraits.blockSizeConstraint.has_value()) {
        sourceTraits.blockSizeConstraint = transform->transformBlockSize(sourceTraits.blockSizeConstraint.value());
    }

    auto stream            = makeStream(sourceTraits, sink.getTraits(), streamFormat);
    auto transcodingStream = std::make_unique<InputTranscodeProxy>(std::move(stream), transform);

    return transcodingStream;
}

auto StreamFactory::getBlockSizeConstraint(std::initializer_list<audio::Endpoint::Traits> traitsList) const
    -> std::optional<std::size_t>
{

M module-audio/Audio/StreamFactory.hpp => module-audio/Audio/StreamFactory.hpp +15 -4
@@ 5,6 5,8 @@

#include "Endpoint.hpp"
#include "Stream.hpp"
#include "transcode/Transform.hpp"
#include "transcode/InputTranscodeProxy.hpp"

#include <initializer_list>
#include <optional>


@@ 18,17 20,26 @@ namespace audio
      public:
        StreamFactory() = default;
        explicit StreamFactory(std::chrono::milliseconds operationPeriodRequirement);

        auto makeStream(Source &source, Sink &sink) -> std::unique_ptr<AbstractStream>;
        auto makeStream(Source &source, Sink &sink, AudioFormat streamFormat) -> std::unique_ptr<Stream>;
        auto makeInputTranscodingStream(Source &source,
                                        Sink &sink,
                                        AudioFormat streamFormat,
                                        std::shared_ptr<transcode::Transform>(transform))
            -> std::unique_ptr<transcode::InputTranscodeProxy>;

      private:
        using Traits = audio::Endpoint::Traits;

        static constexpr auto defaultBuffering = 2U;

        auto getBlockSizeConstraint(std::initializer_list<audio::Endpoint::Traits> traitsList) const
            -> std::optional<std::size_t>;
        auto makeStream(Traits sourceTraits, Traits sinkTraits, AudioFormat streamFormat) -> std::unique_ptr<Stream>;

        auto getBlockSizeConstraint(std::initializer_list<Traits> traitsList) const -> std::optional<std::size_t>;
        auto getTimingConstraints(std::initializer_list<std::optional<std::chrono::milliseconds>> timingConstraints)
            const -> std::chrono::milliseconds;
        auto negotiateAllocator(std::initializer_list<audio::Endpoint::Traits> traitsList) noexcept
            -> Stream::Allocator &;
        auto negotiateAllocator(std::initializer_list<Traits> traitsList) noexcept -> Stream::Allocator &;

        std::optional<std::chrono::milliseconds> periodRequirement = std::nullopt;


M module-audio/Audio/StreamProxy.cpp => module-audio/Audio/StreamProxy.cpp +19 -19
@@ 6,95 6,95 @@

using audio::StreamProxy;

StreamProxy::StreamProxy(AbstractStream &wrappedStream) noexcept : wrappedStream(wrappedStream)
StreamProxy::StreamProxy(std::shared_ptr<AbstractStream> wrappedStream) noexcept : wrappedStream(wrappedStream)
{}

bool StreamProxy::push(void *data, std::size_t dataSize)
{
    return wrappedStream.push(data, dataSize);
    return wrappedStream->push(data, dataSize);
}

bool StreamProxy::push(const Span &span)
{
    return wrappedStream.push(span);
    return wrappedStream->push(span);
}

bool StreamProxy::push()
{
    return wrappedStream.push();
    return wrappedStream->push();
}

bool StreamProxy::pop(Span &span)
{
    return wrappedStream.pop(span);
    return wrappedStream->pop(span);
}

bool StreamProxy::reserve(Span &span)
{
    return wrappedStream.reserve(span);
    return wrappedStream->reserve(span);
}

void StreamProxy::commit()
{
    wrappedStream.commit();
    wrappedStream->commit();
}

void StreamProxy::release()
{
    wrappedStream.release();
    wrappedStream->release();
}

bool StreamProxy::peek(Span &span)
{
    return wrappedStream.peek(span);
    return wrappedStream->peek(span);
}

void StreamProxy::consume()
{
    wrappedStream.consume();
    wrappedStream->consume();
}

void StreamProxy::unpeek()
{
    wrappedStream.unpeek();
    wrappedStream->unpeek();
}

void StreamProxy::reset()
{
    wrappedStream.reset();
    wrappedStream->reset();
}

void StreamProxy::registerListener(EventListener *listener)
{
    wrappedStream.registerListener(listener);
    wrappedStream->registerListener(listener);
}

void StreamProxy::unregisterListeners(EventListener *listener)
{
    wrappedStream.unregisterListeners(listener);
    wrappedStream->unregisterListeners(listener);
}

auto StreamProxy::getOutputTraits() const noexcept -> Traits
{
    return wrappedStream.getOutputTraits();
    return wrappedStream->getOutputTraits();
}

auto StreamProxy::getInputTraits() const noexcept -> Traits
{
    return wrappedStream.getInputTraits();
    return wrappedStream->getInputTraits();
}

bool StreamProxy::isEmpty() const noexcept
{
    return wrappedStream.isEmpty();
    return wrappedStream->isEmpty();
}

bool StreamProxy::isFull() const noexcept
{
    return wrappedStream.isFull();
    return wrappedStream->isFull();
}

auto StreamProxy::getWrappedStream() -> AbstractStream &
{
    return wrappedStream;
    return *wrappedStream;
}

M module-audio/Audio/StreamProxy.hpp => module-audio/Audio/StreamProxy.hpp +4 -2
@@ 5,6 5,8 @@

#include "AbstractStream.hpp"

#include <memory>

namespace audio
{



@@ 20,7 22,7 @@ namespace audio
         *
         * @param wrappedStream - stream to wrap.
         */
        explicit StreamProxy(AbstractStream &wrappedStream) noexcept;
        explicit StreamProxy(std::shared_ptr<AbstractStream> wrappedStream) noexcept;

        // listener registration
        void registerListener(EventListener *listener) override;


@@ 55,7 57,7 @@ namespace audio
        auto getWrappedStream() -> AbstractStream &;

      private:
        AbstractStream &wrappedStream;
        std::shared_ptr<AbstractStream> wrappedStream;
    };

} // namespace audio

M module-audio/Audio/test/TestStream.cpp => module-audio/Audio/test/TestStream.cpp +1 -1
@@ 51,6 51,7 @@ bool TestStream::pop(AbstractStream::Span &span)

bool TestStream::reserve(AbstractStream::Span &span)
{
    span = Span{.data = data.get(), .dataSize = bufSize};
    return true;
}



@@ 62,7 63,6 @@ void TestStream::release()

bool TestStream::peek(AbstractStream::Span &span)
{
    span = Span{.data = data.get(), .dataSize = bufSize};
    return true;
}


M module-audio/Audio/test/unittest_stream.cpp => module-audio/Audio/test/unittest_stream.cpp +68 -19
@@ 8,10 8,13 @@
#include <Audio/AudioFormat.hpp>
#include <Audio/StreamProxy.hpp>
#include <Audio/StreamFactory.hpp>
#include <Audio/transcode/BasicDecimator.hpp>

#include "MockEndpoint.hpp"
#include "MockStream.hpp"

#include <memory>

#include <cstdint>
#include <cstring>



@@ 356,21 359,21 @@ TEST(Stream, spanEquality)

TEST(Proxy, Write)
{
    MockStream mock;
    auto proxy = ::audio::StreamProxy(mock);
    auto mock  = std::make_shared<MockStream>();
    auto proxy = ::audio::StreamProxy(std::static_pointer_cast<::audio::AbstractStream>(mock));
    ::audio::AbstractStream::Span span{.data = reinterpret_cast<uint8_t *>(0xdeadbeef), .dataSize = 0xc00f33b4d};

    EXPECT_CALL(mock, push(span)).Times(1).WillOnce(Return(true));
    EXPECT_CALL(mock, push(nullptr, 128)).WillOnce(Return(false));
    EXPECT_CALL(mock, push()).WillOnce(Return(true));
    EXPECT_CALL(*mock, push(span)).Times(1).WillOnce(Return(true));
    EXPECT_CALL(*mock, push(nullptr, 128)).WillOnce(Return(false));
    EXPECT_CALL(*mock, push()).WillOnce(Return(true));

    EXPECT_TRUE(proxy.push(span));
    EXPECT_FALSE(proxy.push(nullptr, 128));
    EXPECT_TRUE(proxy.push());

    EXPECT_CALL(mock, reserve).Times(1);
    EXPECT_CALL(mock, commit).Times(1);
    EXPECT_CALL(mock, release).Times(1);
    EXPECT_CALL(*mock, reserve).Times(1);
    EXPECT_CALL(*mock, commit).Times(1);
    EXPECT_CALL(*mock, release).Times(1);

    proxy.reserve(span);
    proxy.commit();


@@ 379,16 382,16 @@ TEST(Proxy, Write)

TEST(Proxy, Read)
{
    MockStream mock;
    auto proxy = ::audio::StreamProxy(mock);
    auto mock  = std::make_shared<MockStream>();
    auto proxy = ::audio::StreamProxy(std::static_pointer_cast<::audio::AbstractStream>(mock));
    ::audio::AbstractStream::Span span{.data = reinterpret_cast<uint8_t *>(0xdeadbeef), .dataSize = 0xc00f33b4d};

    EXPECT_CALL(mock, pop(span)).Times(1).WillOnce(Return(true));
    EXPECT_CALL(*mock, pop(span)).Times(1).WillOnce(Return(true));
    EXPECT_TRUE(proxy.pop(span));

    EXPECT_CALL(mock, peek(span)).Times(2).WillOnce(Return(true)).WillOnce(Return(false));
    EXPECT_CALL(mock, consume());
    EXPECT_CALL(mock, unpeek());
    EXPECT_CALL(*mock, peek(span)).Times(2).WillOnce(Return(true)).WillOnce(Return(false));
    EXPECT_CALL(*mock, consume());
    EXPECT_CALL(*mock, unpeek());

    EXPECT_TRUE(proxy.peek(span));
    EXPECT_FALSE(proxy.peek(span));


@@ 399,12 402,12 @@ TEST(Proxy, Read)

TEST(Proxy, Misc)
{
    MockStream mock;
    auto proxy = ::audio::StreamProxy(mock);
    auto mock  = std::make_shared<MockStream>();
    auto proxy = ::audio::StreamProxy(std::static_pointer_cast<::audio::AbstractStream>(mock));

    EXPECT_CALL(mock, reset).Times(1);
    EXPECT_CALL(mock, isEmpty).Times(1).WillOnce(Return(true));
    EXPECT_CALL(mock, isFull).Times(1).WillOnce(Return(false));
    EXPECT_CALL(*mock, reset).Times(1);
    EXPECT_CALL(*mock, isEmpty).Times(1).WillOnce(Return(true));
    EXPECT_CALL(*mock, isFull).Times(1).WillOnce(Return(false));

    proxy.reset();
    EXPECT_TRUE(proxy.isEmpty());


@@ 518,6 521,52 @@ TEST(Factory, NoConstraints)
    EXPECT_EQ(stream->getOutputTraits().blockSize, 64);
}

TEST(Factory, TranscodingStreamSinkTraits)
{
    testing::audio::MockSink mockSink;
    testing::audio::MockSource mockSource;
    ::audio::StreamFactory factory(2ms);
    auto format = ::audio::AudioFormat(8000, 16, 1);

    EXPECT_CALL(mockSource, getTraits).WillRepeatedly(Return(::audio::Endpoint::Traits{}));
    EXPECT_CALL(mockSink, getTraits).WillRepeatedly(Return(::audio::Endpoint::Traits{.blockSizeConstraint = 60U}));

    auto stream = factory.makeStream(mockSource, mockSink, format);

    EXPECT_EQ(stream->getBlockCount(), 2);
    EXPECT_EQ(stream->getOutputTraits().blockSize, 60);

    auto decimatorTransform = std::make_shared<::audio::transcode::BasicDecimator<std::uint16_t, 1, 2>>();
    auto transcodingStream  = factory.makeInputTranscodingStream(
        mockSource, mockSink, format, std::static_pointer_cast<::audio::transcode::Transform>(decimatorTransform));

    EXPECT_EQ(transcodingStream->getInputTraits().blockSize, 120);
    EXPECT_EQ(transcodingStream->getOutputTraits().blockSize, 60);
}

TEST(Factory, TranscodingStreamSourceTraits)
{
    testing::audio::MockSink mockSink;
    testing::audio::MockSource mockSource;
    ::audio::StreamFactory factory(2ms);
    auto format = ::audio::AudioFormat(8000, 16, 1);

    EXPECT_CALL(mockSource, getTraits).WillRepeatedly(Return(::audio::Endpoint::Traits{.blockSizeConstraint = 60U}));
    EXPECT_CALL(mockSink, getTraits).WillRepeatedly(Return(::audio::Endpoint::Traits{}));

    auto stream = factory.makeStream(mockSource, mockSink, format);

    EXPECT_EQ(stream->getBlockCount(), 2);
    EXPECT_EQ(stream->getOutputTraits().blockSize, 60);

    auto decimatorTransform = std::make_shared<::audio::transcode::BasicDecimator<std::uint16_t, 1, 2>>();
    auto transcodingStream  = factory.makeInputTranscodingStream(
        mockSource, mockSink, format, std::static_pointer_cast<::audio::transcode::Transform>(decimatorTransform));

    EXPECT_EQ(transcodingStream->getInputTraits().blockSize, 60);
    EXPECT_EQ(transcodingStream->getOutputTraits().blockSize, 30);
}

int main(int argc, char **argv)
{
    ::testing::InitGoogleTest(&argc, argv);

M module-audio/Audio/test/unittest_transcode.cpp => module-audio/Audio/test/unittest_transcode.cpp +205 -69
@@ 15,10 15,15 @@
#include <Audio/transcode/TransformComposite.hpp>
#include <Audio/transcode/BasicInterpolator.hpp>
#include <Audio/transcode/BasicDecimator.hpp>
#include <Audio/transcode/NullTransform.hpp>
#include <Audio/transcode/TransformFactory.hpp>

#include <cstdlib>

using ::audio::transcode::NullTransform;
using ::testing::_;
using ::testing::Return;
using ::testing::SetArgReferee;
using ::testing::audio::MockStream;
using ::testing::audio::MockStreamEventListener;



@@ 27,12 32,12 @@ constexpr std::size_t testStreamSize = 8;
class InverseTransform : public audio::transcode::Transform
{
  public:
    auto transform(const Span &span, const Span &transformSpace) const -> Span override
    auto transform(const Span &input, const Span &output) const -> Span override
    {
        for (std::size_t i = 0; i < span.dataSize; i++) {
            span.data[i] = ~span.data[i];
        for (std::size_t i = 0; i < input.dataSize; i++) {
            output.data[i] = ~input.data[i];
        }
        return span;
        return output;
    }

    auto transformBlockSize(std::size_t inputBufferSize) const noexcept -> std::size_t override


@@ 40,23 45,9 @@ class InverseTransform : public audio::transcode::Transform
        return inputBufferSize;
    }

    auto validateInputFormat(const audio::AudioFormat &inputFormat) const noexcept -> bool override
    {
        return true;
    }

    auto transformFormat(const audio::AudioFormat &inputFormat) const noexcept -> audio::AudioFormat override
    {
        return inputFormat;
    }
};

class NullTransform : public audio::transcode::Transform
{
  public:
    auto transform(const Span &span, const Span &transformSpace) const -> Span override
    auto transformBlockSizeInverted(std::size_t outputBufferSize) const noexcept -> std::size_t override
    {
        return span;
        return outputBufferSize;
    }

    auto validateInputFormat(const audio::AudioFormat &inputFormat) const noexcept -> bool override


@@ 68,22 59,17 @@ class NullTransform : public audio::transcode::Transform
    {
        return inputFormat;
    }

    auto transformBlockSize(std::size_t inputBufferSize) const noexcept -> std::size_t override
    {
        return inputBufferSize;
    }
};

TEST(Transcode, Reset)
{
    MockStream mock;
    NullTransform nullTransform;
    auto mock          = std::make_shared<MockStream>();
    auto nullTransform = std::make_shared<NullTransform>();

    EXPECT_CALL(mock, getInputTraits);
    audio::transcode::InputTranscodeProxy proxy(mock, nullTransform);
    EXPECT_CALL(*mock, getInputTraits);
    audio::transcode::InputTranscodeProxy proxy(std::static_pointer_cast<::audio::AbstractStream>(mock), nullTransform);

    EXPECT_CALL(mock, reset()).Times(1);
    EXPECT_CALL(*mock, reset()).Times(1);

    proxy.reset();
}


@@ 92,38 78,39 @@ TEST(Transcode, Push)
{
    static std::uint8_t testData[testStreamSize]     = {0, 1, 2, 3, 4, 5, 6, 7};
    static std::uint8_t invertedData[testStreamSize] = {255, 254, 253, 252, 251, 250, 249, 248};
    InverseTransform inv;
    ::testing::audio::TestStream stream(testStreamSize);
    ::audio::transcode::InputTranscodeProxy transform(stream, inv);
    auto inv                                         = std::make_shared<InverseTransform>();
    auto stream                                      = std::make_shared<::testing::audio::TestStream>(testStreamSize);
    ::audio::transcode::InputTranscodeProxy transform(std::static_pointer_cast<::audio::AbstractStream>(stream), inv);

    transform.push(::audio::AbstractStream::Span{testData, testStreamSize});
    EXPECT_TRUE(stream.checkData(::audio::AbstractStream::Span{invertedData, testStreamSize}));
    EXPECT_TRUE(stream->checkData(::audio::AbstractStream::Span{invertedData, testStreamSize}));
}

TEST(Transcode, FailedPeek)
{
    NullTransform nullTransform;
    MockStream mockStream;
    auto nullTransform = std::make_shared<NullTransform>();
    auto mockStream    = std::make_shared<MockStream>();

    EXPECT_CALL(mockStream, getInputTraits);
    ::audio::transcode::InputTranscodeProxy transform(mockStream, nullTransform);
    EXPECT_CALL(*mockStream, getInputTraits);
    ::audio::transcode::InputTranscodeProxy transform(std::static_pointer_cast<::audio::AbstractStream>(mockStream),
                                                      nullTransform);
    ::audio::AbstractStream::Span dataSpan;

    EXPECT_CALL(mockStream, peek).Times(1).WillOnce(Return(false));
    EXPECT_CALL(*mockStream, peek).Times(1).WillOnce(Return(false));
    EXPECT_EQ(transform.peek(dataSpan), false);
}

TEST(Transcode, Commit)
{
    static std::uint8_t invertedData[testStreamSize] = {255, 254, 253, 252, 251, 250, 249, 248};
    InverseTransform inv;
    ::testing::audio::TestStream stream(testStreamSize);
    ::audio::transcode::InputTranscodeProxy transform(stream, inv);
    auto inv                                         = std::make_shared<InverseTransform>();
    auto stream                                      = std::make_shared<::testing::audio::TestStream>(testStreamSize);
    ::audio::transcode::InputTranscodeProxy transform(std::static_pointer_cast<::audio::AbstractStream>(stream), inv);
    ::audio::AbstractStream::Span span;

    stream.setData(0);
    stream->setData(0);

    ASSERT_TRUE(transform.peek(span));
    ASSERT_TRUE(transform.reserve(span));
    ASSERT_TRUE(span.data != nullptr);
    ASSERT_TRUE(span.dataSize == testStreamSize);



@@ 132,36 119,106 @@ TEST(Transcode, Commit)
    }

    transform.commit();
    EXPECT_TRUE(stream.checkData(::audio::AbstractStream::Span{invertedData, testStreamSize}));
    EXPECT_TRUE(stream->checkData(::audio::AbstractStream::Span{invertedData, testStreamSize}));

    // no conversion on commit with no reserve
    stream->commit();
    EXPECT_TRUE(stream->checkData(::audio::AbstractStream::Span{invertedData, testStreamSize}));
}

TEST(Transcode, ReserveShrinking)
{
    static std::uint16_t streamData[8];
    auto streamDataSpan = ::audio::AbstractStream::Span{.data     = reinterpret_cast<std::uint8_t *>(streamData),
                                                        .dataSize = sizeof(streamData)};

    auto decimatorTransform = std::make_shared<::audio::transcode::BasicDecimator<std::uint16_t, 1, 2>>();
    auto mockStream         = std::make_shared<MockStream>();

    EXPECT_CALL(*mockStream, getInputTraits)
        .WillRepeatedly(Return(::audio::AbstractStream::Traits{.blockSize = streamDataSpan.dataSize,
                                                               .format    = audio::AudioFormat(8000, 16, 1)}));

    auto transcodingProxy = ::audio::transcode::InputTranscodeProxy(mockStream, decimatorTransform);

    EXPECT_CALL(*mockStream, reserve(_)).WillOnce(DoAll(SetArgReferee<0>(streamDataSpan), Return(true)));

    ::audio::AbstractStream::Span span;
    transcodingProxy.reserve(span);

    EXPECT_EQ(span.dataSize, 16 * sizeof(std::uint16_t));

    auto buf = reinterpret_cast<std::uint16_t *>(span.data);
    for (unsigned int i = 0; i < 16; i++) {
        buf[i] = i;
    }

    EXPECT_CALL(*mockStream, commit);

    transcodingProxy.commit();

    static std::uint16_t expectedData[8] = {0, 2, 4, 6, 8, 10, 12, 14};
    EXPECT_TRUE(memcmp(expectedData, streamData, sizeof(expectedData)) == 0);
}

TEST(Transcode, ReserveBloating)
{
    static std::uint16_t streamData[8];
    auto streamDataSpan = ::audio::AbstractStream::Span{.data     = reinterpret_cast<std::uint8_t *>(streamData),
                                                        .dataSize = sizeof(streamData)};

    auto interpolatorTransform = std::make_shared<::audio::transcode::BasicInterpolator<std::uint16_t, 1, 2>>();
    auto mockStream            = std::make_shared<MockStream>();

    EXPECT_CALL(*mockStream, getInputTraits)
        .WillRepeatedly(Return(::audio::AbstractStream::Traits{.blockSize = streamDataSpan.dataSize,
                                                               .format    = audio::AudioFormat(8000, 16, 1)}));

    // no conversion on commit with no peek
    stream.commit();
    EXPECT_TRUE(stream.checkData(::audio::AbstractStream::Span{invertedData, testStreamSize}));
    auto transcodingProxy = ::audio::transcode::InputTranscodeProxy(mockStream, interpolatorTransform);

    EXPECT_CALL(*mockStream, reserve(_)).WillOnce(DoAll(SetArgReferee<0>(streamDataSpan), Return(true)));

    ::audio::AbstractStream::Span span;
    transcodingProxy.reserve(span);

    EXPECT_EQ(span.dataSize, 4 * sizeof(std::uint16_t));

    auto buf = reinterpret_cast<std::uint16_t *>(span.data);
    for (unsigned int i = 0; i < 4; i++) {
        buf[i] = i;
    }

    EXPECT_CALL(*mockStream, commit);

    transcodingProxy.commit();

    static std::uint16_t expectedData[8] = {0, 0, 1, 1, 2, 2, 3, 3};
    EXPECT_TRUE(memcmp(expectedData, streamData, sizeof(expectedData)) == 0);
}

TEST(Transcode, Traits)
{
    auto testFormat = ::audio::AudioFormat(44100, 16, 1);
    ::audio::transcode::MonoToStereo m2s;
    MockStream mockStream;
    auto m2s        = std::make_shared<::audio::transcode::MonoToStereo>();
    auto mockStream = std::make_shared<MockStream>();

    EXPECT_CALL(mockStream, getInputTraits)
    EXPECT_CALL(*mockStream, getInputTraits)
        .Times(1)
        .WillOnce(Return(::audio::AbstractStream::Traits{.blockSize = 128, .format = testFormat}));

    ::audio::transcode::InputTranscodeProxy proxy(mockStream, m2s);
    ::audio::transcode::InputTranscodeProxy proxy(std::static_pointer_cast<::audio::AbstractStream>(mockStream), m2s);

    EXPECT_CALL(mockStream, getInputTraits)
    EXPECT_CALL(*mockStream, getInputTraits)
        .Times(1)
        .WillOnce(Return(::audio::AbstractStream::Traits{.blockSize = 128, .format = testFormat}));

    EXPECT_CALL(mockStream, getOutputTraits)
    EXPECT_CALL(*mockStream, getOutputTraits)
        .Times(1)
        .WillOnce(Return(::audio::AbstractStream::Traits{.blockSize = 128, .format = testFormat}));

    auto proxyInputTraits = proxy.getInputTraits();

    EXPECT_EQ(proxyInputTraits.blockSize, 256);
    EXPECT_EQ(proxyInputTraits.blockSize, 64);
    EXPECT_EQ(proxyInputTraits.format.getChannels(), 2);
    EXPECT_EQ(proxyInputTraits.format.getSampleRate(), 44100);
    EXPECT_EQ(proxyInputTraits.format.getBitWidth(), 16);


@@ 176,18 233,18 @@ TEST(Transcode, Traits)

TEST(Transcode, ListenersWrap)
{
    MockStream mock;
    auto mock          = std::make_shared<MockStream>();
    auto nullTransform = std::make_shared<NullTransform>();
    MockStreamEventListener listener;
    NullTransform nullTransform;

    EXPECT_CALL(mock, getInputTraits);
    audio::transcode::InputTranscodeProxy proxy(mock, nullTransform);
    EXPECT_CALL(*mock, getInputTraits);
    audio::transcode::InputTranscodeProxy proxy(std::static_pointer_cast<::audio::AbstractStream>(mock), nullTransform);

    EXPECT_CALL(mock, registerListener(&listener)).Times(1);
    EXPECT_CALL(*mock, registerListener(&listener)).Times(1);

    proxy.registerListener(&listener);

    EXPECT_CALL(mock, unregisterListeners(&listener)).Times(1);
    EXPECT_CALL(*mock, unregisterListeners(&listener)).Times(1);

    proxy.unregisterListeners(&listener);
}


@@ 212,10 269,6 @@ TEST(Transform, MonoToStereo)
    auto outputFormat = m2s.transformFormat(audio::AudioFormat(44100, 16, 1));
    EXPECT_EQ(outputFormat, audio::AudioFormat(44100, 16, 2));

    EXPECT_EQ(m2s.getTransformSize(4), 8);
    EXPECT_EQ(m2s.getTransformSize(128), 256);
    EXPECT_EQ(m2s.getTransformSize(0), 0);

    static std::uint16_t expectedResult[16] = {0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7};
    ASSERT_EQ(m2s.transform(input, output), output);
    ASSERT_EQ(memcmp(output.data, expectedResult, 32), 0);


@@ 223,9 276,10 @@ TEST(Transform, MonoToStereo)

TEST(Transform, Composite)
{
    audio::transcode::MonoToStereo m2s;
    InverseTransform inv;
    audio::transcode::TransformComposite composite{&inv, &m2s};
    auto m2s = std::make_shared<audio::transcode::MonoToStereo>();
    auto inv = std::make_shared<InverseTransform>();
    audio::transcode::TransformComposite composite(
        std::vector<std::shared_ptr<::audio::transcode::Transform>>{m2s, inv});
    static std::uint16_t inputBuffer[8] = {0, 1, 2, 3, 4, 5, 6, 7};
    static std::uint16_t outputBuffer[16];
    static std::uint16_t expectedResult[] = {


@@ 252,7 306,7 @@ TEST(Transform, Composite)
    auto output = ::audio::AbstractStream::Span{.data     = reinterpret_cast<std::uint8_t *>(&outputBuffer[0]),
                                                .dataSize = sizeof(outputBuffer)};

    ASSERT_EQ(output.dataSize, composite.getTransformSize(sizeof(inputBuffer)));
    ASSERT_EQ(output.dataSize, composite.transformBlockSize(sizeof(inputBuffer)));
    auto result = composite.transform(input, output);
    ASSERT_EQ(result.dataSize, sizeof(expectedResult));
    ASSERT_EQ(memcmp(result.data, expectedResult, sizeof(expectedResult)), 0);


@@ 327,3 381,85 @@ TEST(Transform, BasicDecimator)
    EXPECT_EQ(outputSpan.dataSize, sizeof(uint16_t) * 4);
    EXPECT_EQ(memcmp(outputSpan.data, expectBuffer, outputSpan.dataSize), 0);
}

TEST(Transform, FactorySampleRateInterpolator)
{
    auto factory      = ::audio::transcode::TransformFactory();
    auto sourceFormat = ::audio::AudioFormat{8000, 16, 1};
    auto sinkFormat   = ::audio::AudioFormat{16000, 16, 1};

    auto transform = factory.makeTransform(sourceFormat, sinkFormat);

    EXPECT_STREQ(typeid(*transform).name(), typeid(::audio::transcode::BasicInterpolator<std::uint16_t, 1, 2>).name());
}

TEST(Transform, FactorySampleRateDecimator)
{
    auto factory      = ::audio::transcode::TransformFactory();
    auto sourceFormat = ::audio::AudioFormat{16000, 16, 1};
    auto sinkFormat   = ::audio::AudioFormat{8000, 16, 1};

    auto transform = factory.makeTransform(sourceFormat, sinkFormat);

    EXPECT_STREQ(typeid(*transform).name(), typeid(::audio::transcode::BasicDecimator<std::uint16_t, 1, 2>).name());
    EXPECT_EQ(transform->transformFormat(sourceFormat), sinkFormat);
}

TEST(Tranform, FactoryNullTransform)
{
    auto factory      = ::audio::transcode::TransformFactory();
    auto sourceFormat = ::audio::AudioFormat{8000, 16, 1};
    auto sinkFormat   = ::audio::AudioFormat{8000, 16, 1};

    auto transform = factory.makeTransform(sourceFormat, sinkFormat);

    EXPECT_STREQ(typeid(*transform).name(), typeid(::audio::transcode::NullTransform).name());
    EXPECT_EQ(transform->transformFormat(sourceFormat), sinkFormat);
}

TEST(Transform, FactoryMonoToStereo)
{
    auto factory      = ::audio::transcode::TransformFactory();
    auto sourceFormat = ::audio::AudioFormat{8000, 16, 1};
    auto sinkFormat   = ::audio::AudioFormat{8000, 16, 2};

    auto transform = factory.makeTransform(sourceFormat, sinkFormat);

    EXPECT_STREQ(typeid(*transform).name(), typeid(::audio::transcode::MonoToStereo).name());
    EXPECT_EQ(transform->transformFormat(sourceFormat), sinkFormat);
}

TEST(Transform, FactoryComposite)
{
    auto factory      = ::audio::transcode::TransformFactory();
    auto sourceFormat = ::audio::AudioFormat{16000, 16, 1};
    auto sinkFormat   = ::audio::AudioFormat{32000, 16, 2};

    auto transform = factory.makeTransform(sourceFormat, sinkFormat);

    EXPECT_STREQ(typeid(*transform).name(), typeid(::audio::transcode::TransformComposite).name());
    EXPECT_EQ(transform->transformFormat(sourceFormat), sinkFormat);
}

TEST(Transform, FactoryErrors)
{
    auto factory = ::audio::transcode::TransformFactory();
    EXPECT_THROW(factory.makeTransform(::audio::AudioFormat{16000, 16, 1}, ::audio::AudioFormat{16000, 24, 1}),
                 std::runtime_error);
    EXPECT_THROW(factory.makeTransform(::audio::AudioFormat{44100, 16, 1}, ::audio::AudioFormat{48000, 16, 1}),
                 std::invalid_argument);
    EXPECT_THROW(factory.makeTransform(::audio::AudioFormat{8000, 16, 1}, ::audio::AudioFormat{24000, 16, 1}),
                 std::invalid_argument);
    EXPECT_THROW(factory.makeTransform(::audio::AudioFormat{16000, 32, 1}, ::audio::AudioFormat{8000, 32, 1}),
                 std::invalid_argument);
    EXPECT_THROW(factory.makeTransform(::audio::AudioFormat{8000, 16, 2}, ::audio::AudioFormat{16000, 16, 2}),
                 std::invalid_argument);

    // channel conversions
    EXPECT_THROW(factory.makeTransform(::audio::AudioFormat{8000, 16, 1}, ::audio::AudioFormat{8000, 16, 3}),
                 std::invalid_argument);
    EXPECT_THROW(factory.makeTransform(::audio::AudioFormat{8000, 16, 2}, ::audio::AudioFormat{8000, 16, 1}),
                 std::invalid_argument);
    EXPECT_THROW(factory.makeTransform(::audio::AudioFormat{8000, 32, 1}, ::audio::AudioFormat{8000, 32, 2}),
                 std::invalid_argument);
}

M module-audio/Audio/transcode/BasicDecimator.hpp => module-audio/Audio/transcode/BasicDecimator.hpp +5 -0
@@ 42,6 42,11 @@ namespace audio::transcode
            return blockSize / Ratio;
        }

        auto transformBlockSizeInverted(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{

M module-audio/Audio/transcode/BasicInterpolator.hpp => module-audio/Audio/transcode/BasicInterpolator.hpp +5 -0
@@ 42,6 42,11 @@ namespace audio::transcode
            return blockSize * Ratio;
        }

        auto transformBlockSizeInverted(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{

M module-audio/Audio/transcode/InputTranscodeProxy.cpp => module-audio/Audio/transcode/InputTranscodeProxy.cpp +18 -13
@@ 4,13 4,15 @@
#include "InputTranscodeProxy.hpp"
#include "Transform.hpp"

#include <memory>
#include <utility>

using audio::transcode::InputTranscodeProxy;

InputTranscodeProxy::InputTranscodeProxy(AbstractStream &wrappedStream, Transform &transform) noexcept
InputTranscodeProxy::InputTranscodeProxy(std::shared_ptr<AbstractStream> wrappedStream,
                                         std::shared_ptr<Transform> transform) noexcept
    : StreamProxy(wrappedStream), transform(transform),
      transcodingSpaceSize(transform.getTransformSize(wrappedStream.getInputTraits().blockSize)),
      transcodingSpaceSize(transform->transformBlockSizeInverted(wrappedStream->getInputTraits().blockSize)),
      transcodingSpace(std::make_unique<std::uint8_t[]>(transcodingSpaceSize)), transcodingSpaceSpan{
                                                                                    .data     = transcodingSpace.get(),
                                                                                    .dataSize = transcodingSpaceSize}


@@ 18,23 20,26 @@ InputTranscodeProxy::InputTranscodeProxy(AbstractStream &wrappedStream, Transfor

bool InputTranscodeProxy::push(const Span &span)
{
    return getWrappedStream().push(transform.transform(span, transcodingSpaceSpan));
    return getWrappedStream().push(transform->transform(span, transcodingSpaceSpan));
}

void InputTranscodeProxy::commit()
{
    transform.transform(peekedSpan, transcodingSpaceSpan);
    getWrappedStream().commit();
    peekedSpan.reset();
    if (isReserved) {
        transform->transform(transcodingSpaceSpan, reservedSpan);
        getWrappedStream().commit();
        reservedSpan.reset();
        isReserved = false;
    }
}

bool InputTranscodeProxy::peek(Span &span)
bool InputTranscodeProxy::reserve(Span &span)
{
    auto result = getWrappedStream().peek(span);
    auto result = getWrappedStream().reserve(span);

    if (result) {
        peekedSpan = span;
    }
    reservedSpan = span;
    span         = transcodingSpaceSpan;
    isReserved   = true;

    return result;
}


@@ 42,8 47,8 @@ bool InputTranscodeProxy::peek(Span &span)
auto InputTranscodeProxy::getInputTraits() const noexcept -> Traits
{
    auto originalTraits      = StreamProxy::getInputTraits();
    auto transcodedFormat    = transform.transformFormat(originalTraits.format);
    auto transcodedBlockSize = transform.transformBlockSize(originalTraits.blockSize);
    auto transcodedFormat    = transform->transformFormat(originalTraits.format);
    auto transcodedBlockSize = transform->transformBlockSizeInverted(originalTraits.blockSize);

    return Traits{.blockSize = transcodedBlockSize, .format = transcodedFormat};
}

M module-audio/Audio/transcode/InputTranscodeProxy.hpp => module-audio/Audio/transcode/InputTranscodeProxy.hpp +6 -4
@@ 23,19 23,21 @@ namespace audio::transcode
         * @param wrappedStream
         * @param transform to apply on the input
         */
        explicit InputTranscodeProxy(AbstractStream &wrappedStream, Transform &transform) noexcept;
        explicit InputTranscodeProxy(std::shared_ptr<AbstractStream> wrappedStream,
                                     std::shared_ptr<Transform> transform) noexcept;

        bool push(const Span &span) override;
        void commit() override;
        bool peek(Span &span) override;
        bool reserve(Span &span) override;
        [[nodiscard]] auto getInputTraits() const noexcept -> Traits override;

      private:
        Transform &transform;
        Span peekedSpan;
        std::shared_ptr<Transform> transform;
        Span reservedSpan;
        std::size_t transcodingSpaceSize;
        std::unique_ptr<std::uint8_t[]> transcodingSpace;
        Span transcodingSpaceSpan;
        bool isReserved = false;
    };

}; // namespace audio::transcode

M module-audio/Audio/transcode/MonoToStereo.cpp => module-audio/Audio/transcode/MonoToStereo.cpp +6 -1
@@ 9,7 9,7 @@ using audio::transcode::MonoToStereo;

auto MonoToStereo::transform(const Span &span, const Span &transformSpace) const -> Span
{
    auto outputSpan   = Span{.data = transformSpace.data, .dataSize = getTransformSize(span.dataSize)};
    auto outputSpan   = Span{.data = transformSpace.data, .dataSize = transformBlockSize(span.dataSize)};
    auto outputBuffer = reinterpret_cast<std::uint16_t *>(transformSpace.data);
    auto inputBuffer  = reinterpret_cast<std::uint16_t *>(span.data);



@@ 25,6 25,11 @@ auto MonoToStereo::transformBlockSize(std::size_t inputBufferSize) const noexcep
    return 2 * inputBufferSize;
}

auto MonoToStereo::transformBlockSizeInverted(std::size_t outputBufferSize) const noexcept -> std::size_t
{
    return outputBufferSize / 2;
}

auto MonoToStereo::validateInputFormat(const audio::AudioFormat &inputFormat) const noexcept -> bool
{
    return inputFormat.getChannels() == 1;

M module-audio/Audio/transcode/MonoToStereo.hpp => module-audio/Audio/transcode/MonoToStereo.hpp +1 -0
@@ 17,6 17,7 @@ namespace audio::transcode
        auto validateInputFormat(const audio::AudioFormat &inputFormat) const noexcept -> bool override;
        auto transformFormat(const audio::AudioFormat &inputFormat) const noexcept -> audio::AudioFormat override;
        auto transformBlockSize(std::size_t blockSize) const noexcept -> std::size_t override;
        auto transformBlockSizeInverted(std::size_t blockSize) const noexcept -> std::size_t override;
    };

} // namespace audio::transcode

A module-audio/Audio/transcode/NullTransform.cpp => module-audio/Audio/transcode/NullTransform.cpp +31 -0
@@ 0,0 1,31 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "NullTransform.hpp"

using ::audio::transcode::NullTransform;

auto NullTransform::transform(const Span &span, const Span &transformSpace) const -> Span
{
    return span;
}

auto NullTransform::validateInputFormat(const audio::AudioFormat &inputFormat) const noexcept -> bool
{
    return true;
}

auto NullTransform::transformFormat(const audio::AudioFormat &inputFormat) const noexcept -> audio::AudioFormat
{
    return inputFormat;
}

auto NullTransform::transformBlockSize(std::size_t inputBufferSize) const noexcept -> std::size_t
{
    return inputBufferSize;
}

auto NullTransform::transformBlockSizeInverted(std::size_t outputBufferSize) const noexcept -> std::size_t
{
    return outputBufferSize;
}

A module-audio/Audio/transcode/NullTransform.hpp => module-audio/Audio/transcode/NullTransform.hpp +21 -0
@@ 0,0 1,21 @@
// 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"

namespace audio::transcode
{

    class NullTransform : public Transform
    {
      public:
        auto transform(const Span &span, const Span &transformSpace) const -> Span override;
        auto validateInputFormat(const audio::AudioFormat &inputFormat) const noexcept -> bool override;
        auto transformFormat(const audio::AudioFormat &inputFormat) const noexcept -> audio::AudioFormat override;
        auto transformBlockSize(std::size_t inputBufferSize) const noexcept -> std::size_t override;
        auto transformBlockSizeInverted(std::size_t outputBufferSize) const noexcept -> std::size_t override;
    };

} // namespace audio::transcode

M module-audio/Audio/transcode/Transform.hpp => module-audio/Audio/transcode/Transform.hpp +7 -11
@@ 23,10 23,10 @@ namespace audio::transcode
         * @brief Transforms data within range
         *
         * @param inputSpan - input data to transform
         * @param transformSpace - overall space available to the transform
         * @return Output data (contained within )
         * @param outputSpan - space for transformation output
         * @return Output data
         */
        virtual auto transform(const Span &inputSpan, const Span &transformSpace) const -> Span = 0;
        virtual auto transform(const Span &inputSpan, const Span &outputSpan) const -> Span = 0;

        /**
         * @brief Checks if audio format is suitable for transformation.


@@ 54,17 54,13 @@ namespace audio::transcode
        virtual auto transformBlockSize(std::size_t blockSize) const noexcept -> std::size_t = 0;

        /**
         * @brief Get the overall space size required for transformation. Some transforms
         * may need to use more space than the size of an output data, e.g. resampling
         * where there is a signal interpolation before the final decimation.
         * @brief Calculates required size of an input data to get transformed data of size equal
         * outputBlockSize
         *
         * @param inputBufferSize
         * @param outputBlockSize - size of an output block
         * @return std::size_t
         */
        virtual auto getTransformSize(std::size_t inputBufferSize) const noexcept -> std::size_t
        {
            return transformBlockSize(inputBufferSize);
        }
        virtual auto transformBlockSizeInverted(std::size_t outputBlockSize) const noexcept -> std::size_t = 0;

        /**
         * @brief A convenience transform operator.

M module-audio/Audio/transcode/TransformComposite.cpp => module-audio/Audio/transcode/TransformComposite.cpp +13 -14
@@ 7,12 7,13 @@
#include <Audio/AudioFormat.hpp>

#include <algorithm>
#include <memory>
#include <initializer_list>

using audio::transcode::Transform;
using audio::transcode::TransformComposite;

TransformComposite::TransformComposite(std::initializer_list<Transform *> transforms) : children(transforms)
TransformComposite::TransformComposite(std::vector<std::shared_ptr<Transform>> transforms) : children(transforms)
{}

auto TransformComposite::transform(const Span &input, const Span &conversionSpace) const -> Span


@@ 25,19 26,6 @@ auto TransformComposite::transform(const Span &input, const Span &conversionSpac
    return output;
}

auto TransformComposite::getTransformSize(std::size_t inputBufferSize) const noexcept -> std::size_t
{
    auto nextBlockSize  = inputBufferSize;
    auto inputBlockSize = inputBufferSize;

    for (auto t : children) {
        nextBlockSize  = t->getTransformSize(nextBlockSize);
        inputBlockSize = std::max(nextBlockSize, inputBlockSize);
    }

    return inputBlockSize;
}

auto TransformComposite::validateInputFormat(const audio::AudioFormat &inputFormat) const noexcept -> bool
{
    auto checkFormat = inputFormat;


@@ 73,3 61,14 @@ auto TransformComposite::transformBlockSize(std::size_t blockSize) const noexcep

    return transformedBlockSize;
}

auto TransformComposite::transformBlockSizeInverted(std::size_t blockSize) const noexcept -> std::size_t
{
    std::size_t transformedBlockSize = blockSize;

    for (auto t : children) {
        transformedBlockSize = t->transformBlockSizeInverted(transformedBlockSize);
    }

    return transformedBlockSize;
}

M module-audio/Audio/transcode/TransformComposite.hpp => module-audio/Audio/transcode/TransformComposite.hpp +4 -4
@@ 7,7 7,7 @@

#include <Audio/AudioFormat.hpp>

#include <initializer_list>
#include <memory>
#include <vector>

namespace audio::transcode


@@ 24,16 24,16 @@ namespace audio::transcode
         *
         * @param children - transforms to be executed on each transform call.
         */
        TransformComposite(std::initializer_list<Transform *> children);
        TransformComposite(std::vector<std::shared_ptr<Transform>> children);

        auto transform(const Span &span, const Span &transformSpace) const -> Span override;
        auto getTransformSize(std::size_t inputBufferSize) const noexcept -> std::size_t override;
        auto validateInputFormat(const audio::AudioFormat &inputFormat) const noexcept -> bool override;
        auto transformFormat(const audio::AudioFormat &inputFormat) const noexcept -> audio::AudioFormat override;
        auto transformBlockSize(std::size_t blockSize) const noexcept -> std::size_t override;
        auto transformBlockSizeInverted(std::size_t blockSize) const noexcept -> std::size_t override;

      private:
        std::vector<Transform *> children;
        std::vector<std::shared_ptr<Transform>> children;
    };

} // namespace audio::transcode

A module-audio/Audio/transcode/TransformFactory.cpp => module-audio/Audio/transcode/TransformFactory.cpp +111 -0
@@ 0,0 1,111 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "TransformFactory.hpp"

#include <Audio/AudioFormat.hpp>

#include "BasicDecimator.hpp"
#include "BasicInterpolator.hpp"
#include "MonoToStereo.hpp"
#include "NullTransform.hpp"
#include "Transform.hpp"
#include "TransformComposite.hpp"

#include <algorithm>
#include <memory>
#include <stdexcept>
#include <vector>

#include <cassert>

using audio::transcode::NullTransform;
using audio::transcode::Transform;
using audio::transcode::TransformFactory;

auto TransformFactory::makeTransform(AudioFormat sourceFormat, AudioFormat sinkFormat) const
    -> std::unique_ptr<Transform>
{
    auto transforms = std::vector<std::unique_ptr<Transform>>{};

    if (sourceFormat == sinkFormat) {
        return std::make_unique<NullTransform>();
    }

    if (sourceFormat.getBitWidth() != sinkFormat.getBitWidth()) {
        throw std::runtime_error("Bitwidth conversion is not implemented");
    }

    if (sourceFormat.getSampleRate() != sinkFormat.getSampleRate()) {
        transforms.push_back(getSamplerateTransform(sourceFormat, sinkFormat));
    }

    if (sourceFormat.getChannels() != sinkFormat.getChannels()) {
        transforms.push_back(getChannelsTransform(sourceFormat, sinkFormat));
    }

    assert(!transforms.empty());

    if (transforms.size() > 1) {
        auto transformsListForComposite = std::vector<std::shared_ptr<Transform>>{};
        std::move(std::begin(transforms), std::end(transforms), std::back_inserter(transformsListForComposite));
        return std::make_unique<audio::transcode::TransformComposite>(transformsListForComposite);
    }
    else {
        return std::move(transforms[0]);
    }
}

auto TransformFactory::getSamplerateTransform(AudioFormat sourceFormat, AudioFormat sinkFormat) const
    -> std::unique_ptr<Transform>
{
    static constexpr auto supportedSampleRateCoversionRatio = 2U;
    static constexpr auto supportedBitWidth                 = 16U;
    static constexpr auto supportedChannelCount             = 1U;

    auto sourceRate = sourceFormat.getSampleRate();
    auto sinkRate   = sinkFormat.getSampleRate();

    auto greater = std::max(sourceRate, sinkRate);
    auto lesser  = std::min(sourceRate, sinkRate);

    if (greater % lesser != 0) {
        throw std::invalid_argument("Sample rate conversion is not supported");
    }

    auto ratio = greater / lesser;
    if (ratio != supportedSampleRateCoversionRatio) {
        throw std::invalid_argument("Sample rate conversion is not supported (ratio != 2)");
    }

    if (sourceFormat.getBitWidth() != supportedBitWidth) {
        throw std::invalid_argument("Sample rate conversion with bit width other than 16 is not supported");
    }

    if (sourceFormat.getChannels() != supportedChannelCount) {
        throw std::invalid_argument("Sample rate conversion supported with mono only");
    }

    if (sourceRate > sinkRate) {
        return std::make_unique<audio::transcode::BasicDecimator<std::uint16_t,
                                                                 supportedChannelCount,
                                                                 supportedSampleRateCoversionRatio>>();
    }
    else {
        return std::make_unique<audio::transcode::BasicInterpolator<std::uint16_t,
                                                                    supportedChannelCount,
                                                                    supportedSampleRateCoversionRatio>>();
    }
}

auto TransformFactory::getChannelsTransform(AudioFormat sourceFormat, AudioFormat sinkFormat) const
    -> std::unique_ptr<Transform>
{
    if (sourceFormat.getChannels() == 1 && sinkFormat.getChannels() == 2 && sourceFormat.getBitWidth() == 16) {
        auto transform = std::make_unique<audio::transcode::MonoToStereo>();
        return transform;
    }
    else {
        throw std::invalid_argument("Channels conversion is not supported");
    }
}

A module-audio/Audio/transcode/TransformFactory.hpp => module-audio/Audio/transcode/TransformFactory.hpp +24 -0
@@ 0,0 1,24 @@
// 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 <Audio/AudioFormat.hpp>
#include <Audio/transcode/Transform.hpp>

#include <memory>

namespace audio::transcode
{
    class TransformFactory
    {
      public:
        auto makeTransform(AudioFormat sourceFormat, AudioFormat sinkFormat) const -> std::unique_ptr<Transform>;

      private:
        auto getSamplerateTransform(AudioFormat sourceFormat, AudioFormat sinkFormat) const
            -> std::unique_ptr<Transform>;
        auto getChannelsTransform(AudioFormat sourceFormat, AudioFormat sinkFormat) const -> std::unique_ptr<Transform>;
    };

}; // namespace audio::transcode

M module-audio/CMakeLists.txt => module-audio/CMakeLists.txt +2 -0
@@ 44,7 44,9 @@ target_sources(
                ${CMAKE_CURRENT_SOURCE_DIR}/Audio/StreamQueuedEventsListener.cpp
                ${CMAKE_CURRENT_SOURCE_DIR}/Audio/transcode/InputTranscodeProxy.cpp
                ${CMAKE_CURRENT_SOURCE_DIR}/Audio/transcode/MonoToStereo.cpp
                ${CMAKE_CURRENT_SOURCE_DIR}/Audio/transcode/NullTransform.cpp
                ${CMAKE_CURRENT_SOURCE_DIR}/Audio/transcode/TransformComposite.cpp
                ${CMAKE_CURRENT_SOURCE_DIR}/Audio/transcode/TransformFactory.cpp
                ${CMAKE_CURRENT_SOURCE_DIR}/Audio/VolumeScaler.cpp
)
target_include_directories(${PROJECT_NAME} PRIVATE ${TAGLIB_INCLUDE_DIRS})