~aleteoryx/muditaos

871b250d86a456e5ec7d732962b6f307ffc40b70 — Marcin Smoczyński 5 years ago e3d98f5
[EGD-4534] Change audio data path synchronization

Refactor audio data path to fix several synchronization issues and
excessive copy operations on large memory blocks. Introduce
audio::Stream data structure to allow connecting audio source and sink
with a zero-copy capability.

Introduce system mechanisms:
 - critical section guard lock needed for stream synchronization
 - non-cacheable memory allocator to allocate memory for DMA safe
   buffers

Update the Googletest CMake template to match the capabilities of the
Catch2 template.

Signed-off-by: Marcin Smoczyński <smoczynski.marcin@gmail.com>
Signed-off-by: Hubert Chrzaniuk <hubert.chrzaniuk@mudita.com>
45 files changed, 1450 insertions(+), 652 deletions(-)

M changelog.md
M module-apps/application-alarm-clock/widgets/AlarmOptionsItem.hpp
M module-apps/application-music-player/ApplicationMusicPlayer.hpp
M module-apps/application-music-player/models/SongsModel.hpp
M module-audio/Audio/Audio.cpp
M module-audio/Audio/Audio.hpp
A module-audio/Audio/Endpoint.cpp
A module-audio/Audio/Endpoint.hpp
M module-audio/Audio/Operation/Operation.cpp
M module-audio/Audio/Operation/Operation.hpp
M module-audio/Audio/Operation/PlaybackOperation.cpp
M module-audio/Audio/Operation/PlaybackOperation.hpp
M module-audio/Audio/Operation/RouterOperation.cpp
M module-audio/Audio/Operation/RouterOperation.hpp
A module-audio/Audio/Stream.cpp
A module-audio/Audio/Stream.hpp
A module-audio/Audio/StreamQueuedEventsListener.cpp
A module-audio/Audio/StreamQueuedEventsListener.hpp
R module-audio/Audio/decoder/{decoder => Decoder}.cpp
R module-audio/Audio/decoder/{decoder => Decoder}.hpp
A module-audio/Audio/decoder/DecoderWorker.cpp
A module-audio/Audio/decoder/DecoderWorker.hpp
M module-audio/Audio/decoder/decoderFLAC.cpp
M module-audio/Audio/decoder/decoderFLAC.hpp
M module-audio/Audio/decoder/decoderMP3.cpp
M module-audio/Audio/decoder/decoderMP3.hpp
M module-audio/Audio/decoder/decoderWAV.cpp
M module-audio/Audio/decoder/decoderWAV.hpp
M module-audio/Audio/test/CMakeLists.txt
M module-audio/Audio/test/unittest_audio.cpp
A module-audio/Audio/test/unittest_stream.cpp
M module-audio/CMakeLists.txt
M module-bsp/board/rt1051/bsp/audio/RT1051Audiocodec.cpp
M module-bsp/board/rt1051/bsp/audio/RT1051Audiocodec.hpp
M module-bsp/board/rt1051/bsp/audio/RT1051CellularAudio.cpp
M module-bsp/board/rt1051/bsp/audio/RT1051CellularAudio.hpp
M module-bsp/bsp/audio/bsp_audio.hpp
M module-os/CMakeLists.txt
A module-os/CriticalSectionGuard.cpp
A module-os/CriticalSectionGuard.hpp
A module-os/memory/NonCachedMemAllocator.hpp
M module-services/service-audio/AudioServiceAPI.cpp
M module-services/service-audio/service-audio/AudioMessage.hpp
M module-services/service-audio/service-audio/AudioServiceAPI.hpp
M test/CMakeLists.txt
M changelog.md => changelog.md +1 -0
@@ 17,6 17,7 @@
* `[PowerManagement]` Change hardware timers clock source
* `[bluetooth]` Underlying communication with the Bluetooth module over DMA (direct access)
* `[system]` Workers refactor and state transition fixes
* Change audio data path synchronization.




M module-apps/application-alarm-clock/widgets/AlarmOptionsItem.hpp => module-apps/application-alarm-clock/widgets/AlarmOptionsItem.hpp +1 -1
@@ 8,7 8,7 @@
#include <Label.hpp>
#include <Image.hpp>
#include <BoxLayout.hpp>
#include <module-audio/Audio/decoder/decoder.hpp>
#include <Audio/decoder/Decoder.hpp>

namespace gui
{

M module-apps/application-music-player/ApplicationMusicPlayer.hpp => module-apps/application-music-player/ApplicationMusicPlayer.hpp +1 -1
@@ 4,7 4,7 @@
#pragma once

#include <Application.hpp>
#include <module-audio/Audio/decoder/decoder.hpp>
#include <Audio/decoder/Decoder.hpp>

namespace gui
{

M module-apps/application-music-player/models/SongsModel.hpp => module-apps/application-music-player/models/SongsModel.hpp +1 -1
@@ 8,7 8,7 @@
#include "Application.hpp"

#include <ListItemProvider.hpp>
#include <module-audio/Audio/decoder/decoder.hpp>
#include <Audio/decoder/Decoder.hpp>

class SongsModel : public app::InternalModel<gui::ListItem *>, public gui::ListItemProvider
{

M module-audio/Audio/Audio.cpp => module-audio/Audio/Audio.cpp +3 -1
@@ 28,7 28,7 @@ namespace audio

    std::optional<Tags> Audio::GetFileTags(const char *filename)
    {
        auto ret = decoder::Create(filename);
        auto ret = Decoder::Create(filename);
        if (ret == nullptr) {
            return {};
        }


@@ 91,6 91,8 @@ namespace audio
                break;
            }
            currentOperation = std::move(ret);
            currentOperation->SetDataStreams(&dataStreamOut, &dataStreamIn);

            UpdateProfiles();

            if (btData) {

M module-audio/Audio/Audio.hpp => module-audio/Audio/Audio.hpp +10 -1
@@ 11,8 11,9 @@
#include <service-bluetooth/ServiceBluetoothCommon.hpp>

#include "AudioCommon.hpp"
#include "Stream.hpp"
#include "Operation/Operation.hpp"
#include "decoder/decoder.hpp"
#include "decoder/Decoder.hpp"

namespace audio
{


@@ 117,6 118,14 @@ namespace audio

        AsyncCallback asyncCallback;
        DbCallback dbCallback;

        // for efficiency multiple of 24 and 32 (max audio samples size)
        static constexpr auto defaultAudioStreamBlockSize = 2048;
        StandardStreamAllocator allocatorOut;
        Stream dataStreamOut{allocatorOut, defaultAudioStreamBlockSize};

        StandardStreamAllocator allocatorIn;
        Stream dataStreamIn{allocatorIn, defaultAudioStreamBlockSize};
    };

} // namespace audio

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

#include "Endpoint.hpp"

#include <cassert> // assert

using namespace audio;

void Endpoint::setStream(Stream &stream)
{
    assert(_stream == nullptr);
    _stream = &stream;
}

Stream *Endpoint::getStream() const noexcept
{
    return _stream;
}

void Endpoint::unsetStream()
{
    assert(_stream != nullptr);
    _stream = nullptr;
}

bool Endpoint::isConnected() const noexcept
{
    return _stream != nullptr;
}

void Source::connect(Sink &sink, Stream &stream)
{
    connectedSink = &sink;
    connectedSink->setStream(stream);
    setStream(stream);
}

void Source::disconnectStream()
{
    unsetStream();
    connectedSink->unsetStream();
    connectedSink = nullptr;
}

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

#pragma once

#include "Stream.hpp"

namespace audio
{
    class Endpoint
    {
      public:
        void setStream(Stream &stream);
        Stream *getStream() const noexcept;
        void unsetStream();
        bool isConnected() const noexcept;

      private:
        Stream *_stream = nullptr;
    };

    class Sink : public Endpoint
    {};

    class Source : public Endpoint
    {
      public:
        void connect(Sink &sink, Stream &stream);
        void disconnectStream();

      private:
        Sink *connectedSink = nullptr;
    };

}; // namespace audio

M module-audio/Audio/Operation/Operation.cpp => module-audio/Audio/Operation/Operation.cpp +1 -1
@@ 10,7 10,7 @@
#include "RecorderOperation.hpp"
#include "RouterOperation.hpp"

#include "Audio/decoder/decoder.hpp"
#include "Audio/decoder/Decoder.hpp"

#include <bsp/audio/bsp_audio.hpp>
#include <module-bsp/board/rt1051/bsp/audio/RT1051BluetoothAudio.hpp>

M module-audio/Audio/Operation/Operation.hpp => module-audio/Audio/Operation/Operation.hpp +10 -2
@@ 8,6 8,7 @@
#include <functional>

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



@@ 112,6 113,12 @@ namespace audio

        audio::RetCode SwitchToPriorityProfile();

        void SetDataStreams(Stream *dStreamOut, Stream *dStreamIn)
        {
            dataStreamOut = dStreamOut;
            dataStreamIn  = dStreamIn;
        }

      protected:
        struct SupportedProfile
        {


@@ 123,6 130,9 @@ namespace audio
            bool isAvailable;
        };

        Stream *dataStreamOut = nullptr;
        Stream *dataStreamIn  = nullptr;

        std::shared_ptr<Profile> currentProfile;
        std::unique_ptr<bsp::AudioDevice> audioDevice;



@@ 132,8 142,6 @@ namespace audio
        State state = State::Idle;
        audio::AsyncCallback eventCallback;

        AudioSinkState audioSinkState;

        audio::Token operationToken;
        Type opType = Type::Idle;
        std::string filePath;

M module-audio/Audio/Operation/PlaybackOperation.cpp => module-audio/Audio/Operation/PlaybackOperation.cpp +16 -20
@@ 3,12 3,11 @@

#include "PlaybackOperation.hpp"

#include "Audio/decoder/decoder.hpp"
#include "Audio/decoder/Decoder.hpp"
#include "Audio/Profiles/Profile.hpp"

#include "Audio/AudioCommon.hpp"

#include <bsp/audio/bsp_audio.hpp>
#include <log/log.hpp>

namespace audio


@@ 22,23 21,6 @@ namespace audio
        std::function<uint32_t(const std::string &path, const uint32_t &defaultValue)> dbCallback)
        : Operation(playbackType), dec(nullptr)
    {
        audioCallback = [this](const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer) -> int32_t {

#if PERF_STATS_ON == 1
            auto tstamp = xTaskGetTickCount();
#endif
            auto ret = dec->decode(framesPerBuffer, reinterpret_cast<int16_t *>(outputBuffer));
#if PERF_STATS_ON == 1
            LOG_DEBUG("Dec:%dms", xTaskGetTickCount() - tstamp);
            // LOG_DEBUG("Watermark:%lu",uxTaskGetStackHighWaterMark2(NULL));  M.P: left here on purpose, it's handy
            // during sf tests on hardware
#endif
            if (ret == 0) {
                state = State::Idle;
                eventCallback({PlaybackEventType::EndOfFile, operationToken});
            }
            return ret;
        };

        constexpr audio::Volume defaultLoudspeakerVolume = 10;
        constexpr audio::Volume defaultHeadphonesVolume  = 2;


@@ 65,7 47,7 @@ namespace audio
        }
        currentProfile = defaultProfile;

        dec = decoder::Create(file);
        dec = Decoder::Create(file);
        if (dec == nullptr) {
            throw AudioInitException("Error during initializing decoder", RetCode::FileDoesntExist);
        }


@@ 74,6 56,11 @@ namespace audio
        if (retCode != RetCode::Success) {
            throw AudioInitException("Failed to switch audio profile", retCode);
        }

        endOfFileCallback = [this]() {
            state = State::Idle;
            eventCallback({PlaybackEventType::EndOfFile, operationToken});
        };
    }

    audio::RetCode PlaybackOperation::Start(audio::AsyncCallback callback, audio::Token token)


@@ 83,6 70,9 @@ namespace audio
        }
        operationToken = token;

        assert(dataStreamOut);
        dec->startDecodingWorker(*dataStreamOut, endOfFileCallback);

        if (!tags) {
            tags = dec->fetchTags();
        }


@@ 188,12 178,18 @@ namespace audio
            return RetCode::UnsupportedProfile;
        }

        if (dec->isConnected()) {
            dec->disconnectStream();
        }

        audioDevice = bsp::AudioDevice::Create(currentProfile->GetAudioDeviceType(), audioCallback).value_or(nullptr);
        if (audioDevice == nullptr) {
            LOG_ERROR("Error creating AudioDevice");
            return RetCode::Failed;
        }

        dec->connect(audioDevice->sink, *dataStreamOut);

        currentProfile->SetSampleRate(currentSampleRate);
        currentProfile->SetInOutFlags(currentInOutFlags);


M module-audio/Audio/Operation/PlaybackOperation.hpp => module-audio/Audio/Operation/PlaybackOperation.hpp +8 -4
@@ 4,6 4,11 @@
#pragma once

#include "Operation.hpp"
#include "Audio/Stream.hpp"
#include "Audio/Endpoint.hpp"
#include "Audio/decoder/DecoderWorker.hpp"
#include "Audio/StreamQueuedEventsListener.hpp"
#include "Audio/decoder/Decoder.hpp"

#include <bsp/audio/bsp_audio.hpp>



@@ 15,9 20,6 @@ namespace audio::playbackDefaults

namespace audio
{
    class decoder;
    struct Tags;

    class PlaybackOperation : public Operation
    {
      public:


@@ 40,8 42,10 @@ namespace audio
        Position GetPosition() final;

      private:
        std::unique_ptr<decoder> dec;
        std::unique_ptr<Decoder> dec;
        std::unique_ptr<Tags> tags;

        DecoderWorker::EndOfFileCallback endOfFileCallback;
    };

} // namespace audio

M module-audio/Audio/Operation/RouterOperation.cpp => module-audio/Audio/Operation/RouterOperation.cpp +5 -83
@@ 14,10 14,6 @@
#include <optional>
#include <vector>

// enforced optimization is needed for std::vector::insert and std::fill to be
// as quick as memcpy and memset respectively
#pragma GCC optimize("O3")

namespace audio
{



@@ 25,81 21,6 @@ namespace audio
        [[maybe_unused]] const char *file,
        std::function<std::uint32_t(const std::string &path, const std::uint32_t &defaultValue)> dbCallback)
    {

        audioDeviceCallback =
            [this](const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer) -> std::int32_t {
            if (inputBuffer != nullptr) {
                cpp_freertos::LockGuard lock(audioMutex);
                receivedFramesDiffAudio++;

                if (framesPerBuffer > audioDeviceBuffer.size()) {
                    audioDeviceBuffer.resize(framesPerBuffer, 0);
                }

                if (muteEnable) {
                    std::fill(std::begin(audioDeviceBuffer), std::end(audioDeviceBuffer), 0);
                }
                else {
                    auto rangeStart = static_cast<const std::uint16_t *>(inputBuffer);
                    auto rangeEnd   = rangeStart + framesPerBuffer;
                    std::copy(rangeStart, rangeEnd, std::begin(audioDeviceBuffer));
                }
            }

            if (outputBuffer != nullptr) {
                cpp_freertos::LockGuard lock(cellularMutex);
                receivedFramesDiffCellular--;

                if (receivedFramesDiffCellular != 0) {
                    LOG_FATAL("Audio router synchronization fail, diff = %d", receivedFramesDiffCellular);
                    receivedFramesDiffCellular = 0;
                }

                if (framesPerBuffer > audioDeviceCellularBuffer.size()) {
                    audioDeviceCellularBuffer.resize(framesPerBuffer, 0);
                }

                memcpy(outputBuffer, &audioDeviceCellularBuffer[0], framesPerBuffer * sizeof(std::int16_t));
            }
            return framesPerBuffer;
        };

        audioDeviceCellularCallback =
            [this](const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer) -> std::int32_t {
            if (inputBuffer != nullptr) {
                cpp_freertos::LockGuard lock(cellularMutex);
                receivedFramesDiffCellular++;

                if (framesPerBuffer > audioDeviceCellularBuffer.size()) {
                    audioDeviceCellularBuffer.resize(framesPerBuffer, 0);
                }

                auto rangeStart = static_cast<const std::uint16_t *>(inputBuffer);
                auto rangeEnd   = rangeStart + framesPerBuffer;
                std::copy(rangeStart, rangeEnd, std::begin(audioDeviceCellularBuffer));
            }

            if (outputBuffer != nullptr) {
                cpp_freertos::LockGuard lock(audioMutex);
                receivedFramesDiffAudio--;

                if (receivedFramesDiffAudio != 0) {
                    LOG_FATAL("Audio router synchronization fail, diff = %d", receivedFramesDiffAudio);
                    receivedFramesDiffAudio = 0;
                }

                if (framesPerBuffer > audioDeviceBuffer.size()) {
                    audioDeviceBuffer.resize(framesPerBuffer, 0);
                }

                memcpy(outputBuffer, &audioDeviceBuffer[0], framesPerBuffer * sizeof(std::int16_t));
            }
            return framesPerBuffer;
        };

        audioDeviceBuffer.resize(INPUT_BUFFER_START_SIZE, 0);
        audioDeviceCellularBuffer.resize(INPUT_BUFFER_START_SIZE, 0);

        constexpr audio::Gain defaultRoutingEarspeakerGain       = 20;
        constexpr audio::Volume defaultRoutingEarspeakerVolume   = 10;
        constexpr audio::Gain defaultRoutingSpeakerphoneGain     = 20;


@@ 276,20 197,21 @@ namespace audio
            return RetCode::UnsupportedProfile;
        }

        audioDevice =
            bsp::AudioDevice::Create(currentProfile->GetAudioDeviceType(), audioDeviceCallback).value_or(nullptr);
        audioDevice = bsp::AudioDevice::Create(currentProfile->GetAudioDeviceType(), nullptr).value_or(nullptr);
        if (audioDevice == nullptr) {
            LOG_ERROR("Error creating AudioDevice");
            return RetCode::Failed;
        }

        audioDeviceCellular =
            bsp::AudioDevice::Create(bsp::AudioDevice::Type::Cellular, audioDeviceCellularCallback).value_or(nullptr);
        audioDeviceCellular = bsp::AudioDevice::Create(bsp::AudioDevice::Type::Cellular, nullptr).value_or(nullptr);
        if (audioDeviceCellular == nullptr) {
            LOG_ERROR("Error creating AudioDeviceCellular");
            return RetCode::Failed;
        }

        audioDevice->source.connect(audioDeviceCellular->sink, *dataStreamIn);
        audioDeviceCellular->source.connect(audioDevice->sink, *dataStreamOut);

        switch (state) {
        case State::Idle:
        case State::Paused:

M module-audio/Audio/Operation/RouterOperation.hpp => module-audio/Audio/Operation/RouterOperation.hpp +0 -14
@@ 47,22 47,8 @@ namespace audio
        bool Mute(bool enable);

        bool muteEnable = false;

        std::unique_ptr<Encoder> enc;

        std::unique_ptr<bsp::AudioDevice> audioDeviceCellular;

        AudioCallback audioDeviceCallback         = nullptr;
        AudioCallback audioDeviceCellularCallback = nullptr;

        std::vector<std::int16_t> audioDeviceBuffer;
        std::vector<std::int16_t> audioDeviceCellularBuffer;

        cpp_freertos::MutexStandard audioMutex;
        cpp_freertos::MutexStandard cellularMutex;

        int receivedFramesDiffAudio    = 0;
        int receivedFramesDiffCellular = 0;
    };

} // namespace audio

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

#include "Stream.hpp"

#include <macros.h>

#include <algorithm>
#include <iterator>

using namespace audio;

Stream::Stream(Allocator &allocator, std::size_t blockSize, unsigned int bufferingSize)
    : _allocator(allocator), _blockSize(blockSize), _blockCount(bufferingSize),
      _buffer(_allocator.allocate(_blockSize * _blockCount)), _emptyBuffer(_allocator.allocate(_blockSize)),
      _dataStart(_buffer.get(), _blockSize * _blockCount, _buffer.get(), _blockSize), _dataEnd(_dataStart),
      _peekPosition(_dataStart), _writeReservationPosition(_dataStart)
{
    std::fill(_emptyBuffer.get(), _emptyBuffer.get() + blockSize, 0);
}

bool Stream::push(void *data, std::size_t dataSize)
{
    /// wrapper - no synchronization needed
    return push(Span{.data = static_cast<std::uint8_t *>(data), .dataSize = dataSize});
}

bool Stream::push(const Span &span)
{
    LockGuard lock();

    /// sanity - do not store buffers different than internal block size
    if (span.dataSize != _blockSize) {
        return false;
    }

    /// write reservation in progress
    if (_dataEnd != _writeReservationPosition) {
        return false;
    }

    /// no space left
    if (isFull()) {
        broadcastEvent(Event::StreamOverflow);
        return false;
    }

    auto nextDataBlock = *_dataEnd;
    std::copy(span.data, span.dataEnd(), nextDataBlock.data);

    _dataEnd++;
    _blocksUsed++;
    _writeReservationPosition = _dataEnd;

    broadcastStateEvents();

    return true;
}

bool Stream::push()
{
    /// wrapper - no synchronization needed
    return push(getNullSpan());
}

bool Stream::pop(Span &span)
{
    LockGuard lock();

    /// sanity - do not store buffers different than internal block size
    if (span.dataSize != _blockSize) {
        return false;
    }

    /// peek in progress
    if (_dataStart != _peekPosition) {
        return false;
    }

    if (isEmpty()) {
        span = getNullSpan();
        broadcastEvent(Event::StreamUnderFlow);
        return false;
    }

    std::copy((*_dataStart).data, (*_dataStart).dataEnd(), span.data);

    _dataStart++;
    _blocksUsed--;
    _peekPosition = _dataStart;

    broadcastStateEvents();
    return true;
}

void Stream::consume()
{
    LockGuard lock();

    _blocksUsed -= _peekCount;
    _peekCount = 0;
    _dataStart = _peekPosition;

    broadcastStateEvents();
}

bool Stream::peek(Span &span)
{
    LockGuard lock();

    if (getPeekedCount() < getUsedBlockCount()) {
        span = *_peekPosition++;
        _peekCount++;
        return true;
    }

    span = getNullSpan();
    broadcastEvent(Event::StreamUnderFlow);
    return false;
}

void Stream::unpeek()
{
    LockGuard lock();

    _peekPosition = _dataStart;
    _peekCount    = 0;
}

bool Stream::reserve(Span &span)
{
    LockGuard lock();

    if (getBlockCount() - getUsedBlockCount() > _reserveCount) {
        span = *++_writeReservationPosition;
        _reserveCount++;
        return true;
    }

    broadcastEvent(Event::StreamOverflow);
    return false;
}

void Stream::commit()
{
    LockGuard lock();

    _blocksUsed += _reserveCount;
    _reserveCount = 0;
    _dataEnd      = _writeReservationPosition;

    broadcastStateEvents();
}

void Stream::release()
{
    LockGuard lock();

    _reserveCount             = 0;
    _writeReservationPosition = _dataEnd;
}

std::size_t Stream::getBlockSize() const noexcept
{
    LockGuard lock();

    return _blockSize;
}

void Stream::registerListener(EventListener *listener)
{
    LockGuard lock();

    listeners.push_back(std::ref(listener));
}

void Stream::unregisterListeners(Stream::EventListener *listener)
{
    LockGuard lock();

    auto it = std::find(listeners.begin(), listeners.end(), listener);
    if (it != listeners.end()) {
        listeners.erase(it);
    }
}

void Stream::broadcastEvent(Event event)
{
    auto eventMode = isIRQ() ? EventSourceMode::ISR : EventSourceMode::Thread;

    for (auto listener : listeners) {
        listener->onEvent(this, event, eventMode);
    }
}

void Stream::broadcastStateEvents()
{
    if (_blocksUsed == (getBlockCount() / 2)) {
        broadcastEvent(Event::StreamHalfUsed);
    }

    else if (isEmpty()) {
        broadcastEvent(Event::StreamEmpty);
    }

    else if (isFull()) {
        broadcastEvent(Event::StreamFull);
    }
}

std::size_t Stream::getBlockCount() const noexcept
{
    return _blockCount;
}

std::size_t Stream::getUsedBlockCount() const noexcept
{
    return _blocksUsed;
}

std::size_t Stream::getPeekedCount() const noexcept
{
    return _peekCount;
}

std::size_t Stream::getReservedCount() const noexcept
{
    return _reserveCount;
}

bool Stream::isEmpty() const noexcept
{
    LockGuard lock();
    return getUsedBlockCount() == 0;
}

bool Stream::isFull() const noexcept
{
    LockGuard lock();
    return getUsedBlockCount() == getBlockCount();
}

bool Stream::blocksAvailable() const noexcept
{
    return !isEmpty();
}

Stream::UniqueStreamBuffer StandardStreamAllocator::allocate(std::size_t size)
{
    return std::make_unique<uint8_t[]>(size);
}

Stream::UniqueStreamBuffer NonCacheableStreamAllocator::allocate(std::size_t size)
{
    return std::unique_ptr<std::uint8_t[], std::function<void(uint8_t[])>>(
        allocator.allocate(size), [this, size](std::uint8_t ptr[]) { allocator.deallocate(ptr, size); });
}

Stream::RawBlockIterator::RawBlockIterator(std::uint8_t *bufStart,
                                           std::size_t bufSize,
                                           std::uint8_t *ptr,
                                           std::size_t stepSize)
    : _bufStart(bufStart), _bufEnd(bufStart + bufSize), _curPos(ptr), _stepSize(stepSize)
{}

Stream::RawBlockIterator &Stream::RawBlockIterator::operator++()
{
    _curPos += _stepSize;
    if (_curPos == _bufEnd) {
        _curPos = _bufStart;
    }

    return *this;
}

bool Stream::RawBlockIterator::operator==(const Stream::RawBlockIterator &rhs)
{
    return _curPos == rhs._curPos;
}

bool Stream::RawBlockIterator::operator!=(const Stream::RawBlockIterator &rhs)
{
    return !operator==(rhs);
}

Stream::RawBlockIterator Stream::RawBlockIterator::operator++(int)
{
    RawBlockIterator tmp(*this);
    operator++();
    return tmp;
}

Stream::RawBlockIterator &Stream::RawBlockIterator::operator--()
{
    if (_curPos == _bufStart) {
        _curPos = _bufEnd - _stepSize;
    }
    else {
        _curPos -= _stepSize;
    }

    return *this;
}

Stream::RawBlockIterator Stream::RawBlockIterator::operator--(int)
{
    RawBlockIterator tmp(*this);
    operator--();
    return tmp;
}

Stream::Span Stream::RawBlockIterator::operator*()
{
    return Stream::Span{.data = _curPos, .dataSize = _stepSize};
}

std::uint8_t *Stream::Span::dataEnd() const noexcept
{
    return data + dataSize;
}

Stream::Span Stream::getNullSpan() const noexcept
{
    return Span{.data = _emptyBuffer.get(), .dataSize = _blockSize};
}

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

#pragma once

#include <memory/NonCachedMemAllocator.hpp>
#include <CriticalSectionGuard.hpp>

#include <algorithm>
#include <cstdint>
#include <functional>
#include <list>
#include <memory>
#include <utility>

namespace audio
{
    class Stream
    {
      public:
        using UniqueStreamBuffer = std::unique_ptr<std::uint8_t[], std::function<void(uint8_t[])>>;

        struct Span
        {
            std::uint8_t *data   = nullptr;
            std::size_t dataSize = 0;

            std::uint8_t *dataEnd() const noexcept;
        };

        class RawBlockIterator
        {
          public:
            RawBlockIterator(std::uint8_t *bufStart, std::size_t bufSize, std::uint8_t *ptr, std::size_t stepSize);

            bool operator==(const RawBlockIterator &rhs);
            bool operator!=(const RawBlockIterator &rhs);
            RawBlockIterator &operator++();
            RawBlockIterator &operator--();
            RawBlockIterator operator++(int);
            RawBlockIterator operator--(int);
            Span operator*();

          private:
            std::uint8_t *_bufStart = nullptr;
            std::uint8_t *_bufEnd   = nullptr;
            std::uint8_t *_curPos   = nullptr;
            std::size_t _stepSize   = 0;
        };

        class Allocator
        {
          public:
            virtual UniqueStreamBuffer allocate(std::size_t size) = 0;
        };

        enum class Event
        {
            NoEvent,
            StreamFull,
            StreamHalfUsed,
            StreamEmpty,
            StreamOverflow,
            StreamUnderFlow
        };

        enum class EventSourceMode
        {
            ISR,
            Thread
        };

        class EventListener
        {
          public:
            virtual void onEvent(Stream *stream, Event event, EventSourceMode source) = 0;
        };

        static constexpr auto defaultBufferingSize = 4U;

        Stream(Allocator &allocator, std::size_t blockSize, unsigned int bufferingSize = defaultBufferingSize);

        /// push
        bool push(void *data, std::size_t dataSize);
        bool push(const Span &span);
        bool push();

        /// pop
        bool pop(Span &span);

        /// zero copy write
        bool reserve(Span &span);
        void commit();
        void release();

        /// zero copy read
        bool peek(Span &span);
        void consume();
        void unpeek();

        /// get empty data span
        Span getNullSpan() const noexcept;

        [[nodiscard]] std::size_t getBlockSize() const noexcept;
        [[nodiscard]] std::size_t getBlockCount() const noexcept;
        [[nodiscard]] std::size_t getUsedBlockCount() const noexcept;
        [[nodiscard]] std::size_t getPeekedCount() const noexcept;
        [[nodiscard]] std::size_t getReservedCount() const noexcept;
        [[nodiscard]] bool isEmpty() const noexcept;
        [[nodiscard]] bool isFull() const noexcept;
        [[nodiscard]] bool blocksAvailable() const noexcept;

        void registerListener(EventListener *listener);
        void unregisterListeners(EventListener *listener);

      private:
        using LockGuard = cpp_freertos::CriticalSectionGuard;

        void broadcastEvent(Event event);
        void broadcastStateEvents();

        Allocator &_allocator;
        std::size_t _blockSize    = 0;
        std::size_t _blockCount   = 0;
        std::size_t _blocksUsed   = 0;
        std::size_t _peekCount    = 0;
        std::size_t _reserveCount = 0;
        UniqueStreamBuffer _buffer;
        UniqueStreamBuffer _emptyBuffer;
        std::list<EventListener *> listeners;

        RawBlockIterator _dataStart;
        RawBlockIterator _dataEnd;
        RawBlockIterator _peekPosition;
        RawBlockIterator _writeReservationPosition;
    };

    class StandardStreamAllocator : public Stream::Allocator
    {
      public:
        Stream::UniqueStreamBuffer allocate(std::size_t size);
    };

    class NonCacheableStreamAllocator : public Stream::Allocator
    {
      public:
        NonCacheableStreamAllocator() = default;
        Stream::UniqueStreamBuffer allocate(std::size_t size);

      private:
        NonCachedMemAllocator<uint8_t> allocator;
    };

} // namespace audio

namespace std
{
    template <> struct iterator_traits<audio::Stream::RawBlockIterator>
    {
        using iterator_category = std::forward_iterator_tag;
        using value_type        = audio::Stream::Span;
        using difference_type   = std::size_t;
        using pointer           = audio::Stream::Span *;
        using reference         = audio::Stream::Span &;
    };
}; // namespace std

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

#include "StreamQueuedEventsListener.hpp"

using namespace audio;

StreamQueuedEventsListener::StreamQueuedEventsListener(std::shared_ptr<Queue> eventsQueue) : queue(eventsQueue)
{}

void StreamQueuedEventsListener::onEvent(Stream *stream, Stream::Event event, Stream::EventSourceMode source)
{
    portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
    EventStorage newStorage                = {stream, event};

    if (source == Stream::EventSourceMode::ISR) {
        queue->EnqueueFromISR(&newStorage, &xHigherPriorityTaskWoken);
        if (xHigherPriorityTaskWoken) {
            taskYIELD();
        }
    }
    else if (!queue->Enqueue(&newStorage)) {
        LOG_ERROR("Queue full.");
    }
}

StreamQueuedEventsListener::queuedEvent StreamQueuedEventsListener::waitForEvent()
{
    EventStorage queueStorage;
    if (queue->Dequeue(&queueStorage)) {
        return std::make_pair(queueStorage.stream, queueStorage.event);
    }
    return std::make_pair(nullptr, Stream::Event::NoEvent);
}

std::size_t StreamQueuedEventsListener::getEventsCount() const
{
    return queue->NumItems();
}

StreamQueuedEventsListener::queuedEvent StreamQueuedEventsListener::getEvent()
{
    EventStorage queueStorage;

    if (queue->Dequeue(&queueStorage, 0)) {
        return std::make_pair(queueStorage.stream, queueStorage.event);
    }

    return std::make_pair(nullptr, Stream::Event::NoEvent);
}

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

#pragma once

#include "Stream.hpp"

#include <queue.hpp>

#include <cstdint>
#include <string>
#include <utility>

namespace audio
{
    using namespace cpp_freertos;

    class StreamQueuedEventsListener : public Stream::EventListener
    {
      private:
        struct EventStorage
        {
            Stream *stream      = nullptr;
            Stream::Event event = Stream::Event::NoEvent;
        };

      public:
        using queueInfo                           = std::pair<QueueHandle_t, std::string>;
        using queuedEvent                         = std::pair<Stream *, Stream::Event>;
        static constexpr auto listenerElementSize = sizeof(EventStorage);

        StreamQueuedEventsListener(std::shared_ptr<Queue> eventsQueue);

        void onEvent(Stream *stream, Stream::Event event, Stream::EventSourceMode source);

        queuedEvent waitForEvent();
        queuedEvent getEvent();

        std::size_t getEventsCount() const;

      private:
        std::shared_ptr<Queue> queue;
    };

}; // namespace audio

R module-audio/Audio/decoder/decoder.cpp => module-audio/Audio/decoder/Decoder.cpp +23 -7
@@ 3,7 3,7 @@

#include <cstdio>
#include <Utils.hpp>
#include "decoder.hpp"
#include "Decoder.hpp"
#include "decoderMP3.hpp"
#include "decoderFLAC.hpp"
#include "decoderWAV.hpp"


@@ 15,7 15,7 @@
namespace audio
{

    decoder::decoder(const char *fileName)
    Decoder::Decoder(const char *fileName)
        : filePath(fileName), workerBuffer(std::make_unique<int16_t[]>(workerBufferSize)), tag(std::make_unique<Tags>())
    {



@@ 29,14 29,18 @@ namespace audio
        std::rewind(fd);
    }

    decoder::~decoder()
    Decoder::~Decoder()
    {
        if (audioWorker) {
            audioWorker->close();
        }

        if (fd) {
            std::fclose(fd);
        }
    }

    std::unique_ptr<Tags> decoder::fetchTags()
    std::unique_ptr<Tags> Decoder::fetchTags()
    {
        if (fd) {
            auto inPos = std::ftell(fd);


@@ 79,9 83,9 @@ namespace audio
        return std::make_unique<Tags>(*tag);
    }

    std::unique_ptr<decoder> decoder::Create(const char *file)
    std::unique_ptr<Decoder> Decoder::Create(const char *file)
    {
        std::unique_ptr<decoder> dec;
        std::unique_ptr<Decoder> dec;
        if ((strstr(file, ".wav") != NULL) || (strstr(file, ".WAV") != NULL)) {
            dec = std::make_unique<decoderWAV>(file);
        }


@@ 103,7 107,7 @@ namespace audio
        }
    }

    void decoder::convertmono2stereo(int16_t *pcm, uint32_t samplecount)
    void Decoder::convertmono2stereo(int16_t *pcm, uint32_t samplecount)
    {
        uint32_t i = 0, j = 0;



@@ 117,4 121,16 @@ namespace audio
        memcpy(pcm, &workerBuffer[0], samplecount * 2 * sizeof(int16_t));
    }

    void Decoder::startDecodingWorker(Stream &audioStream, DecoderWorker::EndOfFileCallback endOfFileCallback)
    {
        if (!audioWorker) {
            audioWorker = std::make_unique<DecoderWorker>(audioStream, this, endOfFileCallback);
            audioWorker->init();
            audioWorker->run();
        }
        else {
            LOG_DEBUG("AudioWorker already running.");
        }
    }

} // namespace audio

R module-audio/Audio/decoder/decoder.hpp => module-audio/Audio/decoder/Decoder.hpp +14 -5
@@ 9,7 9,12 @@
#include <optional>
#include <cstring>

#include <log/log.hpp>

#include "Audio/Stream.hpp"
#include "Audio/Endpoint.hpp"
#include "Audio/AudioCommon.hpp"
#include "DecoderWorker.hpp"
namespace audio
{
    namespace channel


@@ 65,16 70,18 @@ namespace audio
        }
    };

    class decoder
    class Decoder : public Source
    {

      public:
        decoder(const char *fileName);
        Decoder(const char *fileName);

        virtual ~decoder();
        virtual ~Decoder();

        virtual uint32_t decode(uint32_t samplesToRead, int16_t *pcmData) = 0;

        void startDecodingWorker(Stream &audioStream, DecoderWorker::EndOfFileCallback endOfFileCallback);

        std::unique_ptr<Tags> fetchTags();

        // Range 0 - 1


@@ 96,7 103,7 @@ namespace audio
        }

        // Factory method
        static std::unique_ptr<decoder> Create(const char *file);
        static std::unique_ptr<Decoder> Create(const char *file);

      protected:
        virtual void fetchTagsSpecific(){};


@@ 116,7 123,9 @@ namespace audio
        std::unique_ptr<int16_t[]> workerBuffer;
        std::unique_ptr<Tags> tag;
        bool isInitialized = false;

        // decoding worker
        std::unique_ptr<DecoderWorker> audioWorker;
    };

} // namespace audio


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

#include "DecoderWorker.hpp"
#include "Audio/decoder/Decoder.hpp"

audio::DecoderWorker::DecoderWorker(Stream &audioStreamOut, Decoder *decoder, EndOfFileCallback endOfFileCallback)
    : sys::Worker(DecoderWorker::workerName, DecoderWorker::workerPriority), audioStreamOut(audioStreamOut),
      decoder(decoder), endOfFileCallback(endOfFileCallback),
      bufferSize(audioStreamOut.getBlockSize() / sizeof(BufferInternalType))
{}

audio::DecoderWorker::~DecoderWorker()
{
    audioStreamOut.unregisterListeners(queueListener.get());
}

auto audio::DecoderWorker::init(std::list<sys::WorkerQueueInfo> queues) -> bool
{
    std::list<sys::WorkerQueueInfo> list{
        {listenerQueueName, StreamQueuedEventsListener::listenerElementSize, listenerQueueCapacity}};

    auto isSuccessful = Worker::init(list);

    queueListener = std::make_unique<StreamQueuedEventsListener>(getQueueByName(listenerQueueName));
    if (!queueListener) {
        return false;
    }

    audioStreamOut.registerListener(queueListener.get());

    decoderBuffer = std::make_unique<BufferInternalType[]>(bufferSize);
    if (!decoderBuffer) {
        return false;
    }

    return isSuccessful;
}

bool audio::DecoderWorker::handleMessage(uint32_t queueID)
{
    auto queue = queues[queueID];
    if (queue->GetQueueName() == listenerQueueName && queueListener) {
        auto event = queueListener->getEvent();

        switch (event.second) {
        case Stream::Event::StreamOverflow:
            break;
        case Stream::Event::StreamUnderFlow:
            break;
        case Stream::Event::NoEvent:
            break;
        case Stream::Event::StreamFull:
            break;
        case Stream::Event::StreamHalfUsed:
            [[fallthrough]];
        case Stream::Event::StreamEmpty:
            auto samplesRead = 0;

            while (!audioStreamOut.isFull()) {
                samplesRead = decoder->decode(bufferSize, decoderBuffer.get());

                if (samplesRead == 0) {
                    endOfFileCallback();
                    break;
                }

                if (!audioStreamOut.push(decoderBuffer.get(), samplesRead * sizeof(BufferInternalType))) {
                    LOG_FATAL("Decoder failed to push to stream.");
                    break;
                }
            }
        }
    }
    return true;
}

A module-audio/Audio/decoder/DecoderWorker.hpp => module-audio/Audio/decoder/DecoderWorker.hpp +39 -0
@@ 0,0 1,39 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <Service/Worker.hpp>
#include "Audio/StreamQueuedEventsListener.hpp"

namespace audio
{
    class Decoder;
    class DecoderWorker : public sys::Worker
    {
      public:
        using EndOfFileCallback = std::function<void()>;

        DecoderWorker(Stream &audioStreamOut, Decoder *decoder, EndOfFileCallback endOfFileCallback);
        ~DecoderWorker() override;

        virtual auto init(std::list<sys::WorkerQueueInfo> queues = std::list<sys::WorkerQueueInfo>()) -> bool override;
        virtual auto handleMessage(uint32_t queueID) -> bool override;

      private:
        using BufferInternalType = int16_t;

        static constexpr auto workerName            = "DecoderWorker";
        static constexpr auto workerPriority        = static_cast<UBaseType_t>(sys::ServicePriority::Idle);
        static constexpr auto listenerQueueName     = "DecoderWorkerQueue";
        static constexpr auto listenerQueueCapacity = 1024;

        Stream &audioStreamOut;
        Decoder *decoder = nullptr;
        EndOfFileCallback endOfFileCallback;
        std::unique_ptr<StreamQueuedEventsListener> queueListener;

        const int bufferSize;
        std::unique_ptr<BufferInternalType[]> decoderBuffer;
    };
} // namespace audio

M module-audio/Audio/decoder/decoderFLAC.cpp => module-audio/Audio/decoder/decoderFLAC.cpp +1 -1
@@ 15,7 15,7 @@
namespace audio
{

    decoderFLAC::decoderFLAC(const char *fileName) : decoder(fileName)
    decoderFLAC::decoderFLAC(const char *fileName) : Decoder(fileName)
    {

        if (fileSize == 0) {

M module-audio/Audio/decoder/decoderFLAC.hpp => module-audio/Audio/decoder/decoderFLAC.hpp +2 -2
@@ 3,13 3,13 @@

#pragma once

#include "decoder.hpp"
#include "Decoder.hpp"
#include "dr_flac.h"

namespace audio
{

    class decoderFLAC : public decoder
    class decoderFLAC : public Decoder
    {

      public:

M module-audio/Audio/decoder/decoderMP3.cpp => module-audio/Audio/decoder/decoderMP3.cpp +1 -1
@@ 13,7 13,7 @@
namespace audio
{

    decoderMP3::decoderMP3(const char *fileName) : decoder(fileName)
    decoderMP3::decoderMP3(const char *fileName) : Decoder(fileName)
    {

        if (fileSize == 0) {

M module-audio/Audio/decoder/decoderMP3.hpp => module-audio/Audio/decoder/decoderMP3.hpp +2 -4
@@ 6,7 6,7 @@
#include "minimp3/minimp3.h"

#include <cstring>
#include "decoder.hpp"
#include "Decoder.hpp"

extern "C"
{


@@ 16,14 16,12 @@ extern "C"
namespace audio
{

    class decoderMP3 : public decoder
    class decoderMP3 : public Decoder
    {

      public:
        decoderMP3(const char *fileName);

        ~decoderMP3() = default;

        uint32_t decode(uint32_t samplesToRead, int16_t *pcmData) override;

        void setPosition(float pos) override;

M module-audio/Audio/decoder/decoderWAV.cpp => module-audio/Audio/decoder/decoderWAV.cpp +3 -1
@@ 3,10 3,12 @@

#include "decoderWAV.hpp"

#include "Audio/AudioCommon.hpp"

namespace audio
{

    decoderWAV::decoderWAV(const char *fileName) : decoder(fileName)
    decoderWAV::decoderWAV(const char *fileName) : Decoder(fileName)
    {

        if (fileSize == 0) {

M module-audio/Audio/decoder/decoderWAV.hpp => module-audio/Audio/decoder/decoderWAV.hpp +2 -5
@@ 3,21 3,18 @@

#pragma once

#include "decoder.hpp"
#include "Decoder.hpp"
#include <vector>

namespace audio
{

    class decoderWAV : public decoder
    class decoderWAV : public Decoder
    {

      public:
        decoderWAV(const char *fileName);

        ~decoderWAV()
        {}

        uint32_t decode(uint32_t samplesToRead, int16_t *pcmData) override;

        void setPosition(float pos) override;

M module-audio/Audio/test/CMakeLists.txt => module-audio/Audio/test/CMakeLists.txt +9 -0
@@ 10,4 10,13 @@ add_catch2_executable(
        module-audio
)

add_gtest_executable(
    NAME
        audio-stream
    SRCS
        unittest_stream.cpp
    LIBS
        module-audio
)

file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/testfiles" DESTINATION "${CMAKE_BINARY_DIR}")

M module-audio/Audio/test/unittest_audio.cpp => module-audio/Audio/test/unittest_audio.cpp +2 -2
@@ 5,7 5,7 @@

#include <catch2/catch.hpp>

#include "Audio/decoder/decoder.hpp"
#include "Audio/decoder/Decoder.hpp"

#include "Audio/decoder/decoderMP3.hpp"
#include "Audio/decoder/decoderFLAC.hpp"


@@ 24,7 24,7 @@ TEST_CASE("Test audio tags")
    {
        std::vector<std::string> testExtensions = {"flac", "wav", "mp3"};
        for (auto ext : testExtensions) {
            auto dec = audio::decoder::Create(("testfiles/audio." + ext).c_str());
            auto dec = audio::Decoder::Create(("testfiles/audio." + ext).c_str());
            REQUIRE(dec);
            auto tags = dec->fetchTags();
            REQUIRE(tags);

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

#include <gtest/gtest.h>

#include <Audio/Stream.hpp>

#include <cstdint>
#include <cstring>

constexpr std::size_t defaultBlockSize = 64U;
constexpr std::size_t defaultBuffering = 4U;

using namespace audio;

static std::uint8_t testData[defaultBuffering][defaultBlockSize];
static std::uint8_t emptyBlock[defaultBlockSize];

#include <iostream>

static void initTestData()
{
    auto fillbuf = [](std::uint8_t *b, std::size_t s, unsigned step) {
        std::uint8_t v = 0;
        for (unsigned int i = 0; i < s; ++i, v += step) {
            b[i] = v; // & (UINT8_MAX - 1);
        }
    };

    fillbuf(testData[0], defaultBlockSize, 1);
    fillbuf(testData[1], defaultBlockSize, 3);
    fillbuf(testData[2], defaultBlockSize, 7);
    fillbuf(testData[3], defaultBlockSize, 13);
    fillbuf(emptyBlock, defaultBlockSize, 0);
}

static void printBuf(std::uint8_t *buf, std::size_t s)
{
    for (unsigned int i = 0; i < s; i++) {
        std::cout << static_cast<unsigned int>(buf[i]) << " ";
    }

    std::cout << std::endl;
}

[[maybe_unused]] static void printBuf(Stream::Span s)
{
    printBuf(s.data, s.dataSize);
}

TEST(Stream, Init)
{
    StandardStreamAllocator a;
    constexpr auto bufferingSize = 2U;
    Stream s(a, defaultBlockSize, bufferingSize);

    EXPECT_EQ(s.getBlockCount(), bufferingSize);
    EXPECT_EQ(s.getBlockSize(), defaultBlockSize);
    EXPECT_EQ(s.getUsedBlockCount(), 0);
}

TEST(Stream, Push)
{
    StandardStreamAllocator a;
    Stream s(a, defaultBlockSize);
    auto block = testData[0];

    EXPECT_TRUE(s.push(block, defaultBlockSize));
    EXPECT_TRUE(s.push(block, defaultBlockSize));
    EXPECT_TRUE(s.push(block, defaultBlockSize));
    EXPECT_TRUE(s.push(block, defaultBlockSize));
    EXPECT_EQ(s.getUsedBlockCount(), 4);
    EXPECT_FALSE(s.push(block, defaultBlockSize));
}

TEST(Stream, PushPop)
{
    StandardStreamAllocator a;
    Stream s(a, defaultBlockSize);

    initTestData();

    EXPECT_TRUE(s.push(testData[0], defaultBlockSize));
    EXPECT_EQ(s.getUsedBlockCount(), 1);

    {
        std::uint8_t buf[defaultBlockSize];
        Stream::Span popped = {.data = buf, .dataSize = defaultBlockSize};
        EXPECT_TRUE(s.pop(popped));
        ASSERT_EQ(popped.dataSize, defaultBlockSize);
        ASSERT_EQ(popped.data, buf);
        ASSERT_EQ(memcmp(popped.data, testData[0], defaultBlockSize), 0);
    }

    {
        std::uint8_t buf[defaultBlockSize];
        Stream::Span popped = {.data = buf, .dataSize = defaultBlockSize};
        EXPECT_FALSE(s.pop(popped));
        ASSERT_EQ(popped.dataSize, defaultBlockSize);
        ASSERT_NE(popped.data, buf);
        ASSERT_EQ(memcmp(popped.data, emptyBlock, defaultBlockSize), 0);
    }

    {
        for (unsigned int i = 0; i < s.getBlockCount(); ++i) {
            ASSERT_TRUE(s.push(testData[i], defaultBlockSize));
        }

        for (unsigned int i = 0; i < s.getBlockCount(); ++i) {
            std::uint8_t buf[defaultBlockSize];
            Stream::Span popped = {.data = buf, .dataSize = defaultBlockSize};
            EXPECT_TRUE(s.pop(popped));
            ASSERT_EQ(popped.dataSize, defaultBlockSize);
            ASSERT_EQ(popped.data, buf);
            ASSERT_EQ(memcmp(popped.data, testData[i], defaultBlockSize), 0);
        }
    }

    ASSERT_EQ(s.getUsedBlockCount(), 0);
}

TEST(Stream, Peek)
{
    StandardStreamAllocator a;
    Stream s(a, defaultBlockSize);

    initTestData();

    {
        Stream::Span span;

        EXPECT_FALSE(s.peek(span));
        ASSERT_EQ(memcmp(span.data, emptyBlock, span.dataSize), 0);
    }

    {
        Stream::Span span;
        Stream::Span popped;

        EXPECT_TRUE(s.push(testData[0], defaultBlockSize));
        ASSERT_EQ(s.getPeekedCount(), 0);
        ASSERT_EQ(s.getUsedBlockCount(), 1);
        EXPECT_TRUE(s.peek(span));
        ASSERT_EQ(memcmp(span.data, testData[0], defaultBlockSize), 0);
        EXPECT_EQ(s.getUsedBlockCount(), 1);
        EXPECT_FALSE(s.pop(popped));
        EXPECT_EQ(s.getUsedBlockCount(), 1);

        s.unpeek();
        EXPECT_TRUE(s.peek(span));
        ASSERT_EQ(memcmp(span.data, testData[0], defaultBlockSize), 0);
        s.consume();
        EXPECT_EQ(s.getUsedBlockCount(), 0);
    }
}

TEST(Stream, GreedyPeek)
{
    StandardStreamAllocator a;
    Stream s(a, defaultBlockSize);
    Stream::Span span;

    initTestData();

    for (unsigned int i = 0; i < s.getBlockCount(); ++i) {
        ASSERT_TRUE(s.push(testData[i], defaultBlockSize));
    }

    ASSERT_EQ(s.getUsedBlockCount(), defaultBuffering);

    for (unsigned int i = 0; i < s.getBlockCount(); ++i) {
        EXPECT_EQ(s.getPeekedCount(), i);
        EXPECT_TRUE(s.peek(span));
        EXPECT_EQ(s.getPeekedCount(), i + 1);
        ASSERT_EQ(span.dataSize, defaultBlockSize);
        ASSERT_EQ(memcmp(span.data, testData[i], defaultBlockSize), 0);
    }

    ASSERT_FALSE(s.peek(span));
}

TEST(Stream, Reserve)
{
    StandardStreamAllocator a;
    Stream s(a, defaultBlockSize);
    Stream::Span span;

    EXPECT_EQ(s.getReservedCount(), 0);
    EXPECT_TRUE(s.reserve(span));
    EXPECT_EQ(s.getReservedCount(), 1);
    EXPECT_EQ(s.getUsedBlockCount(), 0);

    s.release();
    EXPECT_EQ(s.getReservedCount(), 0);
    EXPECT_EQ(s.getUsedBlockCount(), 0);

    for (unsigned int i = 0; i < s.getBlockCount(); ++i) {
        EXPECT_EQ(s.getReservedCount(), i);
        EXPECT_TRUE(s.reserve(span));
        EXPECT_EQ(s.getReservedCount(), i + 1);
    }

    EXPECT_FALSE(s.reserve(span));

    s.commit();
    EXPECT_EQ(s.getUsedBlockCount(), s.getBlockCount());
}

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

M module-audio/CMakeLists.txt => module-audio/CMakeLists.txt +6 -2
@@ 5,7 5,8 @@ project(module-audio VERSION 1.0


set(SOURCES
        "${CMAKE_CURRENT_SOURCE_DIR}/Audio/decoder/decoder.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/Audio/decoder/Decoder.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/Audio/decoder/DecoderWorker.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/Audio/decoder/decoderFLAC.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/Audio/decoder/decoderMP3.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/Audio/decoder/decoderWAV.cpp"


@@ 17,13 18,16 @@ set(SOURCES
        "${CMAKE_CURRENT_SOURCE_DIR}/Audio/Audio.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/Audio/AudioMux.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/Audio/AudioCommon.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/Audio/Endpoint.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/Audio/Stream.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/Audio/StreamQueuedEventsListener.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/Audio/Operation/Operation.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/Audio/Operation/PlaybackOperation.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/Audio/Operation/RecorderOperation.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/Audio/Operation/RouterOperation.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/Audio/Operation/IdleOperation.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/Audio/Profiles/Profile.cpp"
        )
)

if(NOT ${PROJECT_TARGET} STREQUAL "TARGET_Linux")
    include(targets/Target_Cross.cmake)

M module-bsp/board/rt1051/bsp/audio/RT1051Audiocodec.cpp => module-bsp/board/rt1051/bsp/audio/RT1051Audiocodec.cpp +39 -184
@@ 20,12 20,10 @@ namespace bsp
    std::shared_ptr<drivers::DriverDMA> RT1051Audiocodec::dma;
    std::unique_ptr<drivers::DriverDMAHandle> RT1051Audiocodec::rxDMAHandle;
    std::unique_ptr<drivers::DriverDMAHandle> RT1051Audiocodec::txDMAHandle;
    sai_config_t RT1051Audiocodec::config                                  = {};
    std::uint32_t RT1051Audiocodec::mclkSourceClockHz                      = 0;
    sai_edma_handle_t RT1051Audiocodec::txHandle                           = {};
    sai_edma_handle_t RT1051Audiocodec::rxHandle                           = {};
    int16_t RT1051Audiocodec::inBuffer[CODEC_CHANNEL_PCM_BUFFER_SIZE * 2]  = {};
    int16_t RT1051Audiocodec::outBuffer[CODEC_CHANNEL_PCM_BUFFER_SIZE * 2] = {};
    sai_config_t RT1051Audiocodec::config             = {};
    std::uint32_t RT1051Audiocodec::mclkSourceClockHz = 0;
    sai_edma_handle_t RT1051Audiocodec::txHandle      = {};
    sai_edma_handle_t RT1051Audiocodec::rxHandle      = {};

    RT1051Audiocodec::RT1051Audiocodec(bsp::AudioDevice::audioCallback_t callback)
        : AudioDevice(callback), saiInFormat{}, saiOutFormat{}, codecParams{}, codec{}


@@ 106,16 104,6 @@ namespace bsp
        InStop();
        OutStop();

        if (outWorkerThread) {
            xTaskNotify(outWorkerThread, static_cast<std::uint32_t>(TransferState::Close), eSetBits);
            outWorkerThread = nullptr;
        }

        if (inWorkerThread) {
            xTaskNotify(inWorkerThread, static_cast<std::uint32_t>(TransferState::Close), eSetBits);
            inWorkerThread = nullptr;
        }

        state = State::Stopped;
        vTaskDelay(codecSettleTime);



@@ 224,10 212,6 @@ namespace bsp
    void RT1051Audiocodec::InStart()
    {
        sai_transfer_format_t sai_format = {0};
        sai_transfer_t xfer              = {0};

        saiInFormat.data     = (uint8_t *)inBuffer;
        saiInFormat.dataSize = CODEC_CHANNEL_PCM_BUFFER_SIZE * saiInFormat.bitWidth / 8;

        /* Configure the audio format */
        sai_format.bitWidth           = saiInFormat.bitWidth;


@@ 255,22 239,21 @@ namespace bsp
        /* Reset SAI Rx internal logic */
        SAI_RxSoftwareReset(BOARD_AUDIOCODEC_SAIx, kSAI_ResetTypeSoftware);

        xfer.data     = saiInFormat.data;
        xfer.dataSize = saiInFormat.dataSize;
        SAI_TransferReceiveEDMA(BOARD_AUDIOCODEC_SAIx, &rxHandle, &xfer);

        if (xTaskCreate(inAudioCodecWorkerTask, "inaudiocodec", 1024, this, 0, &inWorkerThread) != pdPASS) {
            LOG_ERROR("Error during creating input audiocodec task");
        if (!source.isConnected()) {
            LOG_FATAL("No output stream connected!");
            return;
        }

        /// initiate first read
        audio::Stream::Span dataSpan;
        source.getStream()->reserve(dataSpan);
        auto xfer = sai_transfer_t{.data = dataSpan.data, .dataSize = dataSpan.dataSize};
        SAI_TransferReceiveEDMA(BOARD_AUDIOCODEC_SAIx, &rxHandle, &xfer);
    }

    void RT1051Audiocodec::OutStart()
    {
        sai_transfer_format_t sai_format = {0};
        sai_transfer_t xfer              = {0};

        saiOutFormat.data     = (uint8_t *)outBuffer;
        saiOutFormat.dataSize = CODEC_CHANNEL_PCM_BUFFER_SIZE * saiInFormat.bitWidth / 8;

        /* Configure the audio format */
        sai_format.bitWidth           = saiOutFormat.bitWidth;


@@ 297,16 280,14 @@ namespace bsp
        /* Reset SAI Tx internal logic */
        SAI_TxSoftwareReset(BOARD_AUDIOCODEC_SAIx, kSAI_ResetTypeSoftware);

        xfer.data     = saiOutFormat.data;
        xfer.dataSize = saiOutFormat.dataSize;
        SAI_TransferSendEDMA(BOARD_AUDIOCODEC_SAIx, &txHandle, &xfer);

        if (xTaskCreate(outAudioCodecWorkerTask, "outaudiocodec", 1024, this, 0, &outWorkerThread) != pdPASS) {
            LOG_ERROR("Error during creating  output audiocodec task");
        if (!sink.isConnected()) {
            LOG_FATAL("No input stream connected!");
            return;
        }

        // Fill out buffer with data
        GetAudioCallback()(nullptr, outBuffer, RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE * 2);
        auto nullSpan = sink.getStream()->getNullSpan();
        auto xfer     = sai_transfer_t{.data = nullSpan.data, .dataSize = nullSpan.dataSize};
        SAI_TransferSendEDMA(BOARD_AUDIOCODEC_SAIx, &txHandle, &xfer);
    }

    void RT1051Audiocodec::OutStop()


@@ 327,168 308,42 @@ namespace bsp
        memset(&rxHandle, 0, sizeof(rxHandle));
    }

    void inAudioCodecWorkerTask(void *pvp)
    {
        std::uint32_t ulNotificationValue = 0;
        RT1051Audiocodec *inst            = reinterpret_cast<RT1051Audiocodec *>(pvp);

        while (true) {
            xTaskNotifyWait(0x00,                 /* Don't clear any bits on entry. */
                            UINT32_MAX,           /* Clear all bits on exit. */
                            &ulNotificationValue, /* Receives the notification value. */
                            portMAX_DELAY);       /* Block indefinitely. */
            {
                if (ulNotificationValue & static_cast<std::uint32_t>(RT1051Audiocodec::TransferState::Close)) {
                    break;
                }

                if (ulNotificationValue & static_cast<std::uint32_t>(RT1051Audiocodec::TransferState::HalfTransfer)) {
                    auto framesFetched = inst->GetAudioCallback()(
                        inst->inBuffer, nullptr, RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE);

                    if (framesFetched < RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE) {
                        memset(&inst->inBuffer[framesFetched],
                               0,
                               RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE - framesFetched);
                    }
                }

                if (ulNotificationValue & static_cast<std::uint32_t>(RT1051Audiocodec::TransferState::FullTransfer)) {
                    auto framesFetched =
                        inst->GetAudioCallback()(&inst->inBuffer[RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE],
                                                 nullptr,
                                                 RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE);

                    if (framesFetched < RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE) {
                        memset(&inst->inBuffer[RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE + framesFetched],
                               0,
                               RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE - framesFetched);
                    }
                }
            }
        }

        vTaskDelete(nullptr);
    }

    void outAudioCodecWorkerTask(void *pvp)
    {
        std::uint32_t ulNotificationValue = 0;
        RT1051Audiocodec *inst            = reinterpret_cast<RT1051Audiocodec *>(pvp);

        while (true) {
            xTaskNotifyWait(0x00,                 /* Don't clear any bits on entry. */
                            UINT32_MAX,           /* Clear all bits on exit. */
                            &ulNotificationValue, /* Receives the notification value. */
                            portMAX_DELAY);       /* Block indefinitely. */
            {
                if (ulNotificationValue & static_cast<std::uint32_t>(RT1051Audiocodec::TransferState::Close)) {
                    break;
                }

                if (ulNotificationValue & static_cast<std::uint32_t>(RT1051Audiocodec::TransferState::HalfTransfer)) {
                    auto framesFetched = inst->GetAudioCallback()(
                        nullptr, inst->outBuffer, RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE);

                    if (framesFetched < RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE) {
                        memset(&inst->outBuffer[framesFetched],
                               0,
                               RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE - framesFetched);
                    }
                }

                if (ulNotificationValue & static_cast<std::uint32_t>(RT1051Audiocodec::TransferState::FullTransfer)) {
                    auto framesFetched =
                        inst->GetAudioCallback()(nullptr,
                                                 &inst->outBuffer[RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE],
                                                 RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE);

                    if (framesFetched < RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE) {
                        memset(&inst->outBuffer[RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE + framesFetched],
                               0,
                               RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE - framesFetched);
                    }
                }
            }
        }

        vTaskDelete(nullptr);
    }

    void rxAudioCodecCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData)
    {
        static RT1051Audiocodec::TransferState state = RT1051Audiocodec::TransferState::HalfTransfer;
        RT1051Audiocodec *inst                       = (RT1051Audiocodec *)userData;
        sai_transfer_t xfer                          = {0};
        BaseType_t xHigherPriorityTaskWoken          = pdFALSE;
        audio::Stream::Span dataSpan;
        auto self    = static_cast<RT1051Audiocodec *>(userData);
        auto &source = self->source;

        if (inst->state == RT1051Audiocodec::State::Stopped) {
        /// exit if not connected to the stream
        if (!source.isConnected()) {
            return;
        }

        if (state == RT1051Audiocodec::TransferState::HalfTransfer) {

            xfer.dataSize = inst->saiInFormat.dataSize;
            xfer.data     = inst->saiInFormat.data + (inst->saiInFormat.dataSize);
            SAI_TransferReceiveEDMA(BOARD_AUDIOCODEC_SAIx, &inst->rxHandle, &xfer);

            /* Notify the task that the transmission is complete. */
            if (inst->inWorkerThread) {
                xTaskNotifyFromISR(
                    inst->inWorkerThread, static_cast<uint32_t>(state), eSetBits, &xHigherPriorityTaskWoken);
            }
            state = RT1051Audiocodec::TransferState::FullTransfer;
        }
        else {

            xfer.dataSize = inst->saiInFormat.dataSize;
            xfer.data     = inst->saiInFormat.data;
            SAI_TransferReceiveEDMA(BOARD_AUDIOCODEC_SAIx, &inst->rxHandle, &xfer);

            /* Notify the task that the transmission is complete. */
            xTaskNotifyFromISR(inst->inWorkerThread, static_cast<uint32_t>(state), eSetBits, &xHigherPriorityTaskWoken);
        /// reserve space for the next read commiting previously reserved block before
        source.getStream()->commit();
        source.getStream()->reserve(dataSpan);

            state = RT1051Audiocodec::TransferState::HalfTransfer;
        }

        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
        sai_transfer_t xfer{.data = dataSpan.data, .dataSize = dataSpan.dataSize};
        SAI_TransferReceiveEDMA(BOARD_AUDIOCODEC_SAIx, &self->rxHandle, &xfer);
    }

    void txAudioCodecCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData)
    {
        static RT1051Audiocodec::TransferState state = RT1051Audiocodec::TransferState::HalfTransfer;
        RT1051Audiocodec *inst                       = (RT1051Audiocodec *)userData;
        sai_transfer_t xfer                          = {0};
        BaseType_t xHigherPriorityTaskWoken          = pdFALSE;
        audio::Stream::Span dataSpan;
        auto self  = static_cast<RT1051Audiocodec *>(userData);
        auto &sink = self->sink;

        if (inst->state == RT1051Audiocodec::State::Stopped) {
        /// exit if not connected to the stream
        if (!sink.isConnected()) {
            return;
        }

        if (state == RT1051Audiocodec::TransferState::HalfTransfer) {

            xfer.dataSize = inst->saiOutFormat.dataSize;
            xfer.data     = inst->saiOutFormat.data + (inst->saiOutFormat.dataSize);
            SAI_TransferSendEDMA(BOARD_AUDIOCODEC_SAIx, &inst->txHandle, &xfer);

            /* Notify the task that the transmission is complete. */
            xTaskNotifyFromISR(
                inst->outWorkerThread, static_cast<uint32_t>(state), eSetBits, &xHigherPriorityTaskWoken);

            state = RT1051Audiocodec::TransferState::FullTransfer;
        }
        else {
            xfer.dataSize = inst->saiOutFormat.dataSize;
            xfer.data     = inst->saiOutFormat.data;
            SAI_TransferSendEDMA(BOARD_AUDIOCODEC_SAIx, &inst->txHandle, &xfer);

            /* Notify the task that the transmission is complete. */
            xTaskNotifyFromISR(
                inst->outWorkerThread, static_cast<uint32_t>(state), eSetBits, &xHigherPriorityTaskWoken);
            state = RT1051Audiocodec::TransferState::HalfTransfer;
        }
        /// pop previous read and peek next
        sink.getStream()->consume();
        sink.getStream()->peek(dataSpan);

        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
        sai_transfer_t xfer{.data = dataSpan.data, .dataSize = dataSpan.dataSize};
        SAI_TransferSendEDMA(BOARD_AUDIOCODEC_SAIx, &self->txHandle, &xfer);
    }

} // namespace bsp

M module-bsp/board/rt1051/bsp/audio/RT1051Audiocodec.hpp => module-bsp/board/rt1051/bsp/audio/RT1051Audiocodec.hpp +2 -24
@@ 5,6 5,7 @@

#include "bsp/audio/bsp_audio.hpp"
#include "fsl_sai_edma.h"

#include "FreeRTOS.h"
#include "task.h"
#include "macros.h"


@@ 22,8 23,6 @@ namespace bsp

    void txAudioCodecCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData);
    void rxAudioCodecCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData);
    void inAudioCodecWorkerTask(void *pvp);
    void outAudioCodecWorkerTask(void *pvp);

    class RT1051Audiocodec : public AudioDevice
    {


@@ 34,8 33,6 @@ namespace bsp

        friend void txAudioCodecCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData);
        friend void rxAudioCodecCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData);
        friend void inAudioCodecWorkerTask(void *pvp);
        friend void outAudioCodecWorkerTask(void *pvp);

        RT1051Audiocodec(AudioDevice::audioCallback_t callback);
        virtual ~RT1051Audiocodec();


@@ 51,8 48,7 @@ namespace bsp
        cpp_freertos::MutexStandard mutex;

      private:
        static const uint32_t CODEC_CHANNEL_PCM_BUFFER_SIZE = 1024;
        const static TickType_t codecSettleTime             = 20 * portTICK_PERIOD_MS;
        constexpr static TickType_t codecSettleTime = 20 * portTICK_PERIOD_MS;

        enum class State
        {


@@ 60,21 56,11 @@ namespace bsp
            Stopped
        };

        /*! @brief Internals state of Rx/Tx callback, needed for double buffering technique */
        enum class TransferState
        {
            HalfTransfer = 1 << 0,
            FullTransfer = 1 << 1,
            Close        = 1 << 2,
        };

        struct SAIFormat
        {
            uint32_t sampleRate_Hz;   /*!< Sample rate of audio data */
            uint32_t bitWidth;        /*!< Data length of audio data, usually 8/16/24/32 bits */
            sai_mono_stereo_t stereo; /*!< Mono or stereo */
            uint8_t *data;            /*!< Data start address to transfer. */
            size_t dataSize;          /*!< Transfer size. */
        };

        static sai_config_t config;


@@ 83,8 69,6 @@ namespace bsp
        State state = State::Stopped;
        SAIFormat saiInFormat;
        SAIFormat saiOutFormat;
        TaskHandle_t inWorkerThread  = nullptr;
        TaskHandle_t outWorkerThread = nullptr;
        CodecParamsMAX98090 codecParams;
        CodecMAX98090 codec;



@@ 98,12 82,6 @@ namespace bsp
        static AT_NONCACHEABLE_SECTION_INIT(sai_edma_handle_t txHandle);
        static AT_NONCACHEABLE_SECTION_INIT(sai_edma_handle_t rxHandle);

        // CODEC_CHANNEL_PCM_BUFFER_SIZE * 2 for double buffering
        static ALIGN_(4) int16_t inBuffer[CODEC_CHANNEL_PCM_BUFFER_SIZE * 2];

        // CODEC_CHANNEL_PCM_BUFFER_SIZE * 2 for double buffering
        static ALIGN_(4) int16_t outBuffer[CODEC_CHANNEL_PCM_BUFFER_SIZE * 2];

        void OutStart();
        void InStart();
        void OutStop();

M module-bsp/board/rt1051/bsp/audio/RT1051CellularAudio.cpp => module-bsp/board/rt1051/bsp/audio/RT1051CellularAudio.cpp +37 -184
@@ 15,10 15,8 @@ namespace bsp

    using namespace drivers;

    sai_edma_handle_t RT1051CellularAudio::txHandle                           = {};
    sai_edma_handle_t RT1051CellularAudio::rxHandle                           = {};
    int16_t RT1051CellularAudio::inBuffer[CODEC_CHANNEL_PCM_BUFFER_SIZE * 2]  = {};
    int16_t RT1051CellularAudio::outBuffer[CODEC_CHANNEL_PCM_BUFFER_SIZE * 2] = {};
    sai_edma_handle_t RT1051CellularAudio::txHandle = {};
    sai_edma_handle_t RT1051CellularAudio::rxHandle = {};

    RT1051CellularAudio::RT1051CellularAudio(bsp::AudioDevice::audioCallback_t callback)
        : AudioDevice(callback), saiInFormat{}, saiOutFormat{}, config{}


@@ 90,16 88,6 @@ namespace bsp
        InStop();
        OutStop();

        if (outWorkerThread) {
            xTaskNotify(outWorkerThread, static_cast<std::uint32_t>(TransferState::Close), eSetBits);
            outWorkerThread = nullptr;
        }

        if (inWorkerThread) {
            xTaskNotify(inWorkerThread, static_cast<std::uint32_t>(TransferState::Close), eSetBits);
            inWorkerThread = nullptr;
        }

        currentFormat = {};
        state         = State::Stopped;



@@ 185,10 173,6 @@ namespace bsp
    void RT1051CellularAudio::InStart()
    {
        sai_transfer_format_t sai_format = {0};
        sai_transfer_t xfer              = {0};

        saiInFormat.data     = (uint8_t *)inBuffer;
        saiInFormat.dataSize = CODEC_CHANNEL_PCM_BUFFER_SIZE * saiInFormat.bitWidth / 8;

        /* Configure the audio format */
        sai_format.bitWidth           = saiInFormat.bitWidth;


@@ 216,22 200,21 @@ namespace bsp
        /* Reset SAI Rx internal logic */
        SAI_RxSoftwareReset(BOARD_CELLULAR_AUDIO_SAIx, kSAI_ResetTypeSoftware);

        xfer.data     = saiInFormat.data;
        xfer.dataSize = saiInFormat.dataSize;
        SAI_TransferReceiveEDMA(BOARD_CELLULAR_AUDIO_SAIx, &rxHandle, &xfer);

        if (xTaskCreate(inCellularWorkerTask, "incellaudio", 512, this, 0, &inWorkerThread) != pdPASS) {
            LOG_ERROR("Error during creating input cellular audio task");
        if (!source.isConnected()) {
            LOG_FATAL("No output stream connected!");
            return;
        }

        /// initiate first read
        audio::Stream::Span dataSpan;
        source.getStream()->reserve(dataSpan);
        auto xfer = sai_transfer_t{.data = dataSpan.data, .dataSize = dataSpan.dataSize};
        SAI_TransferReceiveEDMA(BOARD_CELLULAR_AUDIO_SAIx, &rxHandle, &xfer);
    }

    void RT1051CellularAudio::OutStart()
    {
        sai_transfer_format_t sai_format = {0};
        sai_transfer_t xfer              = {0};

        saiOutFormat.data     = (uint8_t *)outBuffer;
        saiOutFormat.dataSize = CODEC_CHANNEL_PCM_BUFFER_SIZE * saiInFormat.bitWidth / 8;

        /* Configure the audio format */
        sai_format.bitWidth           = saiOutFormat.bitWidth;


@@ 259,16 242,14 @@ namespace bsp
        /* Reset SAI Tx internal logic */
        SAI_TxSoftwareReset(BOARD_CELLULAR_AUDIO_SAIx, kSAI_ResetTypeSoftware);

        xfer.data     = saiOutFormat.data;
        xfer.dataSize = saiOutFormat.dataSize;
        SAI_TransferSendEDMA(BOARD_CELLULAR_AUDIO_SAIx, &txHandle, &xfer);

        if (xTaskCreate(outCellularWorkerTask, "outcellaudio", 512, this, 0, &outWorkerThread) != pdPASS) {
            LOG_ERROR("Error during creating  output cellular audio task");
        if (!sink.isConnected()) {
            LOG_FATAL("No input stream connected!");
            return;
        }

        // Fill out buffer with data
        GetAudioCallback()(nullptr, outBuffer, RT1051CellularAudio::CODEC_CHANNEL_PCM_BUFFER_SIZE * 2);
        auto nullSpan = sink.getStream()->getNullSpan();
        auto xfer     = sai_transfer_t{.data = nullSpan.data, .dataSize = nullSpan.dataSize};
        SAI_TransferSendEDMA(BOARD_CELLULAR_AUDIO_SAIx, &txHandle, &xfer);
    }

    void RT1051CellularAudio::OutStop()


@@ 289,170 270,42 @@ namespace bsp
        memset(&rxHandle, 0, sizeof(rxHandle));
    }

    void inCellularWorkerTask(void *pvp)
    {
        std::uint32_t ulNotificationValue = 0;
        RT1051CellularAudio *inst         = reinterpret_cast<RT1051CellularAudio *>(pvp);

        while (true) {
            xTaskNotifyWait(0x00,                 /* Don't clear any bits on entry. */
                            UINT32_MAX,           /* Clear all bits on exit. */
                            &ulNotificationValue, /* Receives the notification value. */
                            portMAX_DELAY);       /* Block indefinitely. */
            {
                if (ulNotificationValue & static_cast<std::uint32_t>(RT1051CellularAudio::TransferState::Close)) {
                    LOG_DEBUG("Rx worker: received close event");
                    break;
                }

                if (ulNotificationValue & static_cast<uint32_t>(RT1051CellularAudio::TransferState::HalfTransfer)) {
                    auto framesFetched = inst->GetAudioCallback()(
                        inst->inBuffer, nullptr, RT1051CellularAudio::CODEC_CHANNEL_PCM_BUFFER_SIZE);

                    if (framesFetched < RT1051CellularAudio::CODEC_CHANNEL_PCM_BUFFER_SIZE) {
                        memset(&inst->inBuffer[framesFetched],
                               0,
                               RT1051CellularAudio::CODEC_CHANNEL_PCM_BUFFER_SIZE - framesFetched);
                    }
                }

                if (ulNotificationValue & static_cast<uint32_t>(RT1051CellularAudio::TransferState::FullTransfer)) {
                    auto framesFetched =
                        inst->GetAudioCallback()(&inst->inBuffer[RT1051CellularAudio::CODEC_CHANNEL_PCM_BUFFER_SIZE],
                                                 nullptr,
                                                 RT1051CellularAudio::CODEC_CHANNEL_PCM_BUFFER_SIZE);

                    if (framesFetched < RT1051CellularAudio::CODEC_CHANNEL_PCM_BUFFER_SIZE) {
                        memset(&inst->inBuffer[RT1051CellularAudio::CODEC_CHANNEL_PCM_BUFFER_SIZE + framesFetched],
                               0,
                               RT1051CellularAudio::CODEC_CHANNEL_PCM_BUFFER_SIZE - framesFetched);
                    }
                }
            }
        }

        LOG_INFO("Killing Rx cellular audio codec worker thread");
        vTaskDelete(nullptr);
    }

    void outCellularWorkerTask(void *pvp)
    {
        std::uint32_t ulNotificationValue = 0;

        RT1051CellularAudio *inst = reinterpret_cast<RT1051CellularAudio *>(pvp);

        while (true) {
            xTaskNotifyWait(0x00,                 /* Don't clear any bits on entry. */
                            UINT32_MAX,           /* Clear all bits on exit. */
                            &ulNotificationValue, /* Receives the notification value. */
                            portMAX_DELAY);       /* Block indefinitely. */
            {
                if (ulNotificationValue & static_cast<std::uint32_t>(RT1051CellularAudio::TransferState::Close)) {
                    LOG_DEBUG("Tx worker: received close event");
                    break;
                }

                if (ulNotificationValue & static_cast<uint32_t>(RT1051CellularAudio::TransferState::HalfTransfer)) {
                    auto framesFetched = inst->GetAudioCallback()(
                        nullptr, inst->outBuffer, RT1051CellularAudio::CODEC_CHANNEL_PCM_BUFFER_SIZE);

                    if (framesFetched < RT1051CellularAudio::CODEC_CHANNEL_PCM_BUFFER_SIZE) {
                        memset(&inst->outBuffer[framesFetched],
                               0,
                               RT1051CellularAudio::CODEC_CHANNEL_PCM_BUFFER_SIZE - framesFetched);
                    }
                }

                if (ulNotificationValue & static_cast<uint32_t>(RT1051CellularAudio::TransferState::FullTransfer)) {
                    auto framesFetched =
                        inst->GetAudioCallback()(nullptr,
                                                 &inst->outBuffer[RT1051CellularAudio::CODEC_CHANNEL_PCM_BUFFER_SIZE],
                                                 RT1051CellularAudio::CODEC_CHANNEL_PCM_BUFFER_SIZE);

                    if (framesFetched < RT1051CellularAudio::CODEC_CHANNEL_PCM_BUFFER_SIZE) {
                        memset(&inst->outBuffer[RT1051CellularAudio::CODEC_CHANNEL_PCM_BUFFER_SIZE + framesFetched],
                               0,
                               RT1051CellularAudio::CODEC_CHANNEL_PCM_BUFFER_SIZE - framesFetched);
                    }
                }
            }
        }

        LOG_INFO("Killing Tx audio codec worker thread");
        vTaskDelete(nullptr);
    }

    void rxCellularCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData)
    {
        static RT1051CellularAudio::TransferState state = RT1051CellularAudio::TransferState::HalfTransfer;
        RT1051CellularAudio *inst                       = (RT1051CellularAudio *)userData;
        sai_transfer_t xfer                             = {0};
        BaseType_t xHigherPriorityTaskWoken             = pdFALSE;
        audio::Stream::Span dataSpan;
        auto self    = static_cast<RT1051CellularAudio *>(userData);
        auto &source = self->source;

        if (inst->state == RT1051CellularAudio::State::Stopped) {
        /// exit if not connected to the stream
        if (!source.isConnected()) {
            return;
        }

        if (state == RT1051CellularAudio::TransferState::HalfTransfer) {
        /// reserve space for the next read commiting previously reserved block before
        source.getStream()->commit();
        source.getStream()->reserve(dataSpan);

            xfer.dataSize = inst->saiInFormat.dataSize;
            xfer.data     = inst->saiInFormat.data + (inst->saiInFormat.dataSize);
            SAI_TransferReceiveEDMA(BOARD_CELLULAR_AUDIO_SAIx, &inst->rxHandle, &xfer);

            /* Notify the task that the transmission is complete. */
            xTaskNotifyFromISR(
                inst->inWorkerThread, static_cast<std::uint32_t>(state), eSetBits, &xHigherPriorityTaskWoken);
            state = RT1051CellularAudio::TransferState::FullTransfer;
        }
        else {

            xfer.dataSize = inst->saiInFormat.dataSize;
            xfer.data     = inst->saiInFormat.data;
            SAI_TransferReceiveEDMA(BOARD_CELLULAR_AUDIO_SAIx, &inst->rxHandle, &xfer);

            /* Notify the task that the transmission is complete. */
            xTaskNotifyFromISR(
                inst->inWorkerThread, static_cast<std::uint32_t>(state), eSetBits, &xHigherPriorityTaskWoken);
            state = RT1051CellularAudio::TransferState::HalfTransfer;
        }

        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
        sai_transfer_t xfer{.data = dataSpan.data, .dataSize = dataSpan.dataSize};
        SAI_TransferReceiveEDMA(BOARD_CELLULAR_AUDIO_SAIx, &self->rxHandle, &xfer);
    }

    void txCellularCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData)
    {
        static RT1051CellularAudio::TransferState state = RT1051CellularAudio::TransferState::HalfTransfer;
        RT1051CellularAudio *inst                       = (RT1051CellularAudio *)userData;
        sai_transfer_t xfer                             = {0};
        BaseType_t xHigherPriorityTaskWoken             = pdFALSE;
        audio::Stream::Span dataSpan;
        auto self  = static_cast<RT1051CellularAudio *>(userData);
        auto &sink = self->sink;

        if (inst->state == RT1051CellularAudio::State::Stopped) {
        /// exit if not connected to the stream
        if (!sink.isConnected()) {
            return;
        }

        if (state == RT1051CellularAudio::TransferState::HalfTransfer) {

            xfer.dataSize = inst->saiOutFormat.dataSize;
            xfer.data     = inst->saiOutFormat.data + (inst->saiOutFormat.dataSize);
            SAI_TransferSendEDMA(BOARD_CELLULAR_AUDIO_SAIx, &inst->txHandle, &xfer);

            /* Notify the task that the transmission is complete. */
            xTaskNotifyFromISR(
                inst->outWorkerThread, static_cast<std::uint32_t>(state), eSetBits, &xHigherPriorityTaskWoken);
            state = RT1051CellularAudio::TransferState::FullTransfer;
        }
        else {
            xfer.dataSize = inst->saiOutFormat.dataSize;
            xfer.data     = inst->saiOutFormat.data;
            SAI_TransferSendEDMA(BOARD_CELLULAR_AUDIO_SAIx, &inst->txHandle, &xfer);

            /* Notify the task that the transmission is complete. */
            xTaskNotifyFromISR(
                inst->outWorkerThread, static_cast<std::uint32_t>(state), eSetBits, &xHigherPriorityTaskWoken);
            state = RT1051CellularAudio::TransferState::HalfTransfer;
        }
        /// pop previous read and peek next
        sink.getStream()->consume();
        sink.getStream()->peek(dataSpan);

        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
        sai_transfer_t xfer{.data = dataSpan.data, .dataSize = dataSpan.dataSize};
        SAI_TransferSendEDMA(BOARD_CELLULAR_AUDIO_SAIx, &self->txHandle, &xfer);
    }

} // namespace bsp

M module-bsp/board/rt1051/bsp/audio/RT1051CellularAudio.hpp => module-bsp/board/rt1051/bsp/audio/RT1051CellularAudio.hpp +1 -24
@@ 6,6 6,7 @@

#include "bsp/audio/bsp_audio.hpp"
#include "fsl_sai_edma.h"

#include "FreeRTOS.h"
#include "task.h"
#include "macros.h"


@@ 21,8 22,6 @@ namespace bsp

    void txCellularCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData);
    void rxCellularCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData);
    void inCellularWorkerTask(void *pvp);
    void outCellularWorkerTask(void *pvp);

    class RT1051CellularAudio : public AudioDevice
    {


@@ 30,8 29,6 @@ namespace bsp
      public:
        friend void txCellularCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData);
        friend void rxCellularCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData);
        friend void inCellularWorkerTask(void *pvp);
        friend void outCellularWorkerTask(void *pvp);

        RT1051CellularAudio(AudioDevice::audioCallback_t callback);
        virtual ~RT1051CellularAudio();


@@ 47,29 44,17 @@ namespace bsp
        cpp_freertos::MutexStandard mutex;

      private:
        static const uint32_t CODEC_CHANNEL_PCM_BUFFER_SIZE = 1024;

        enum class State
        {
            Running,
            Stopped
        };

        /*! @brief Internals state of Rx/Tx callback, needed for double buffering technique */
        enum class TransferState
        {
            HalfTransfer = 1 << 0,
            FullTransfer = 1 << 1,
            Close        = 1 << 2,
        };

        struct SAIFormat
        {
            uint32_t sampleRate_Hz;   /*!< Sample rate of audio data */
            uint32_t bitWidth;        /*!< Data length of audio data, usually 8/16/24/32 bits */
            sai_mono_stereo_t stereo; /*!< Mono or stereo */
            uint8_t *data;            /*!< Data start address to transfer. */
            size_t dataSize;          /*!< Transfer size. */
        };

        State state = State::Stopped;


@@ 77,8 62,6 @@ namespace bsp
        SAIFormat saiOutFormat;
        uint32_t mclkSourceClockHz = 0;
        sai_config_t config;
        TaskHandle_t inWorkerThread  = nullptr;
        TaskHandle_t outWorkerThread = nullptr;

        // M.P: It is important to destroy these drivers in specific order
        std::shared_ptr<drivers::DriverPLL> pll;


@@ 90,12 73,6 @@ namespace bsp
        static AT_NONCACHEABLE_SECTION_INIT(sai_edma_handle_t txHandle);
        static AT_NONCACHEABLE_SECTION_INIT(sai_edma_handle_t rxHandle);

        // CODEC_CHANNEL_PCM_BUFFER_SIZE * 2 for double buffering
        static ALIGN_(4) int16_t inBuffer[CODEC_CHANNEL_PCM_BUFFER_SIZE * 2];

        // CODEC_CHANNEL_PCM_BUFFER_SIZE * 2 for double buffering
        static ALIGN_(4) int16_t outBuffer[CODEC_CHANNEL_PCM_BUFFER_SIZE * 2];

        void Init();
        void Deinit();
        void OutStart();

M module-bsp/bsp/audio/bsp_audio.hpp => module-bsp/bsp/audio/bsp_audio.hpp +100 -65
@@ 1,34 1,42 @@
#pragma once

#include <Audio/Endpoint.hpp>

#include <optional>
#include <memory>
#include <functional>

namespace bsp {
namespace bsp
{

    class AudioDevice {
    class AudioDevice
    {

    public:
        enum class RetCode {
        	Success = 0,
        	Failure
      public:
        enum class RetCode
        {
            Success = 0,
            Failure
        };

    	enum class Type {
        enum class Type
        {
            None,
            Audiocodec,
            Cellular,
            Bluetooth
        };

        enum class InputPath{
        enum class InputPath
        {
            Headphones,
            Microphone,
            BluetoothHSP,
            None
        };

        enum class OutputPath{
        enum class OutputPath
        {
            Headphones,
            HeadphonesMono,
            Earspeaker,


@@ 39,52 47,54 @@ namespace bsp {
            None
        };

        enum class Flags {
            OutputMono = 1 << 0,
        enum class Flags
        {
            OutputMono   = 1 << 0,
            OutputStereo = 1 << 1,
            InputLeft = 1 << 2,
            InputRight = 1 << 3,
            InputStereo = 1 << 4
            InputLeft    = 1 << 2,
            InputRight   = 1 << 3,
            InputStereo  = 1 << 4
        };

        using Format = struct {
            uint32_t sampleRate_Hz = 0;   /*!< Sample rate of audio data */
            uint32_t bitWidth = 0;        /*!< Data length of audio data, usually 8/16/24/32 bits */
            uint32_t flags = 0;           /*!< In/Out configuration flags */
            float outputVolume = 0.0f;
            float inputGain = 0.0f;
            InputPath inputPath = InputPath::None;
            OutputPath outputPath = OutputPath::None;
        using Format = struct
        {
            uint32_t sampleRate_Hz = 0; /*!< Sample rate of audio data */
            uint32_t bitWidth      = 0; /*!< Data length of audio data, usually 8/16/24/32 bits */
            uint32_t flags         = 0; /*!< In/Out configuration flags */
            float outputVolume     = 0.0f;
            float inputGain        = 0.0f;
            InputPath inputPath    = InputPath::None;
            OutputPath outputPath  = OutputPath::None;
        };

        /**
        * User defined callback.
        * It will be invoked when opened stream needs more frames to process( outputBuffer will be != NULL) or if requested frames count
        * are available to user( inputBuffer will be != NULL).
        * From this callback you can safely use file operations, system calls etc This is because audiostream
        * callbacks are not invoked from IRQ context.
        *
        * If there is more data to process or read user should return:
        *  'AudiostreamCallbackContinue'
        *  if there is no more data to process or read user should return:
        *  'AudiostreamCallbackComplete'
        *  this will close stream and clean up all internally allocated resources.
        *  In case of error return:
        *  'AudiostreamCallbackAbort'
        *  this has the same effect as AudiostreamCallbackComplete.
        *
        * @param stream[in] - pointer to valid stream
        * @param inputBuffer[in] - pointer to buffer where user should copy PCM data
        * @param outputBuffer[out] - pointer to buffer containing valid PCM data
        * @param framesPerBuffer[in] - how many frames user should copy or read from buffer
        * @param userData[in] - user specified data
        * @return audiostream_callback_err_t
        */
        using audioCallback_t = std::function<int32_t(const void *inputBuffer,
                                                      void *outputBuffer,
                                                      unsigned long framesPerBuffer)>;

        explicit AudioDevice(audioCallback_t callback) : callback(callback) {}
         * User defined callback.
         * It will be invoked when opened stream needs more frames to process( outputBuffer will be != NULL) or if
         * requested frames count are available to user( inputBuffer will be != NULL). From this callback you can safely
         * use file operations, system calls etc This is because audiostream callbacks are not invoked from IRQ context.
         *
         * If there is more data to process or read user should return:
         *  'AudiostreamCallbackContinue'
         *  if there is no more data to process or read user should return:
         *  'AudiostreamCallbackComplete'
         *  this will close stream and clean up all internally allocated resources.
         *  In case of error return:
         *  'AudiostreamCallbackAbort'
         *  this has the same effect as AudiostreamCallbackComplete.
         *
         * @param stream[in] - pointer to valid stream
         * @param inputBuffer[in] - pointer to buffer where user should copy PCM data
         * @param outputBuffer[out] - pointer to buffer containing valid PCM data
         * @param framesPerBuffer[in] - how many frames user should copy or read from buffer
         * @param userData[in] - user specified data
         * @return audiostream_callback_err_t
         */
        using audioCallback_t =
            std::function<int32_t(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer)>;

        explicit AudioDevice(audioCallback_t callback) : callback(callback)
        {}

        AudioDevice() = delete;

        virtual ~AudioDevice() = default;


@@ 92,26 102,51 @@ namespace bsp {
        static std::optional<std::unique_ptr<AudioDevice>> Create(Type type, audioCallback_t callback);

        virtual RetCode Start(const Format &format) = 0;
        virtual RetCode Stop() = 0;
        virtual RetCode Stop()                      = 0;

        virtual RetCode OutputVolumeCtrl(float vol) = 0;
        virtual RetCode InputGainCtrl(float gain) = 0;
        virtual RetCode OutputVolumeCtrl(float vol)           = 0;
        virtual RetCode InputGainCtrl(float gain)             = 0;
        virtual RetCode OutputPathCtrl(OutputPath outputPath) = 0;
        virtual RetCode InputPathCtrl(InputPath inputPath) = 0;
        virtual bool IsFormatSupported(const Format& format) = 0;

        float GetOutputVolume() const noexcept { return currentFormat.outputVolume; }
        float GetInputGain() const noexcept { return currentFormat.inputGain; }
        OutputPath GetOutputPath() const noexcept { return currentFormat.outputPath; }
        InputPath GetInputPath() const noexcept { return currentFormat.inputPath; }

        Format GetCurrentFormat() const noexcept { return currentFormat; }
        audioCallback_t GetAudioCallback() { return callback; }

    protected:
        virtual RetCode InputPathCtrl(InputPath inputPath)    = 0;
        virtual bool IsFormatSupported(const Format &format)  = 0;

        float GetOutputVolume() const noexcept
        {
            return currentFormat.outputVolume;
        }

        float GetInputGain() const noexcept
        {
            return currentFormat.inputGain;
        }

        OutputPath GetOutputPath() const noexcept
        {
            return currentFormat.outputPath;
        }

        InputPath GetInputPath() const noexcept
        {
            return currentFormat.inputPath;
        }

        Format GetCurrentFormat() const noexcept
        {
            return currentFormat;
        }

        audioCallback_t GetAudioCallback()
        {
            return callback;
        }

        audio::Sink sink;
        audio::Source source;

      protected:
        Format currentFormat;
        audioCallback_t callback = nullptr;

        bool isInitialized = false;
    };
}
} // namespace bsp

M module-os/CMakeLists.txt => module-os/CMakeLists.txt +2 -0
@@ 28,6 28,8 @@ set(SOURCES
        ${CMAKE_CURRENT_SOURCE_DIR}/FreeRTOS/timers.c

        ${CMAKE_CURRENT_SOURCE_DIR}/memory/usermem.c

        ${CMAKE_CURRENT_SOURCE_DIR}/CriticalSectionGuard.cpp
        )

#Substitute FreeRTOS SystemvView sources if enabled

A module-os/CriticalSectionGuard.cpp => module-os/CriticalSectionGuard.cpp +34 -0
@@ 0,0 1,34 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "CriticalSectionGuard.hpp"

#include <macros.h>

extern "C"
{
#include <FreeRTOS.h>
#include <portmacro.h>
}

using namespace cpp_freertos;

CriticalSectionGuard::CriticalSectionGuard()
{
    if (isIRQ()) {
        savedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
    }
    else {
        vPortEnterCritical();
    }
}

CriticalSectionGuard::~CriticalSectionGuard()
{
    if (isIRQ()) {
        portCLEAR_INTERRUPT_MASK_FROM_ISR(savedInterruptStatus);
    }
    else {
        vPortExitCritical();
    }
}

A module-os/CriticalSectionGuard.hpp => module-os/CriticalSectionGuard.hpp +28 -0
@@ 0,0 1,28 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

extern "C"
{
#include <FreeRTOS.h>
#include <portmacro.h>
}

namespace cpp_freertos
{
    class CriticalSectionGuard
    {
      public:
        CriticalSectionGuard();
        ~CriticalSectionGuard();
        CriticalSectionGuard(const CriticalSectionGuard &) = delete;
        CriticalSectionGuard(CriticalSectionGuard &&)      = delete;

        CriticalSectionGuard &operator=(const CriticalSectionGuard &) = delete;
        CriticalSectionGuard &operator=(CriticalSectionGuard &&) = delete;

      private:
        UBaseType_t savedInterruptStatus;
    };
}; // namespace cpp_freertos

A module-os/memory/NonCachedMemAllocator.hpp => module-os/memory/NonCachedMemAllocator.hpp +36 -0
@@ 0,0 1,36 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <cstdint>
#include <cassert>

#include <FreeRTOS.h>

template <typename T> struct NonCachedMemAllocator
{
    using value_type = T;

    T *allocate(std::size_t num);
    void deallocate(T *ptr, std::size_t num);

    NonCachedMemAllocator()                              = default;
    NonCachedMemAllocator(const NonCachedMemAllocator &) = default;
    NonCachedMemAllocator(NonCachedMemAllocator &&)      = default;

    NonCachedMemAllocator &operator=(const NonCachedMemAllocator &) = default;
    NonCachedMemAllocator &operator=(NonCachedMemAllocator &&) = default;
};

template <typename T> T *NonCachedMemAllocator<T>::allocate(std::size_t num)
{
    T *ptr = reinterpret_cast<T *>(pvPortMalloc(sizeof(T) * num));
    return ptr;
}

template <typename T> void NonCachedMemAllocator<T>::deallocate(T *ptr, std::size_t)
{
    assert(ptr != nullptr);
    vPortFree(ptr);
}

M module-services/service-audio/AudioServiceAPI.cpp => module-services/service-audio/AudioServiceAPI.cpp +1 -1
@@ 5,7 5,7 @@
#include "service-audio/ServiceAudio.hpp"
#include "service-audio/AudioMessage.hpp"

#include <Audio/decoder/decoder.hpp>
#include <Audio/decoder/Decoder.hpp>
#include <Service/Bus.hpp>
#include <Service/Common.hpp>
#include <log/log.hpp>

M module-services/service-audio/service-audio/AudioMessage.hpp => module-services/service-audio/service-audio/AudioMessage.hpp +1 -1
@@ 4,7 4,7 @@
#pragma once

#include <Audio/AudioCommon.hpp>
#include <Audio/decoder/decoder.hpp>
#include <Audio/decoder/Decoder.hpp>
#include <MessageType.hpp>
#include <Service/Message.hpp>


M module-services/service-audio/service-audio/AudioServiceAPI.hpp => module-services/service-audio/service-audio/AudioServiceAPI.hpp +1 -1
@@ 7,7 7,7 @@

#include <Audio/AudioCommon.hpp>
#include <Audio/Profiles/Profile.hpp>
#include <Audio/decoder/decoder.hpp>
#include <Audio/decoder/Decoder.hpp>

#include <memory>
#include <optional>

M test/CMakeLists.txt => test/CMakeLists.txt +53 -9
@@ 14,15 14,59 @@ add_custom_target(unittests)

set(ROOT_TEST_DIR ${CMAKE_CURRENT_SOURCE_DIR} PARENT_SCOPE)

function(add_gtest_executable TESTNAME TESTSRCS)
    add_executable(${TESTNAME} EXCLUDE_FROM_ALL ${TESTSRCS})
function(add_gtest_executable)
    cmake_parse_arguments(
        _TEST_ARGS
        ""
        "NAME"
        "SRCS;INCLUDE;LIBS;DEFS;DEPS"
        ${ARGN}
    )

    if(NOT _TEST_ARGS_NAME)
        message(FATAL_ERROR "You must provide a test name")
    endif(NOT _TEST_ARGS_NAME)
    set(_TESTNAME "googletest-${_TEST_ARGS_NAME}")

    if(NOT _TEST_ARGS_SRCS)
	message(FATAL_ERROR "You must provide test sources for ${_TESTNAME}")
    endif(NOT _TEST_ARGS_SRCS)

    add_executable(${_TESTNAME} EXCLUDE_FROM_ALL ${_TEST_ARGS_SRCS})

    target_compile_options(${_TESTNAME} PUBLIC "-fsanitize=address")
    target_link_options(${_TESTNAME} PUBLIC "-fsanitize=address")

    # disable logs in unit tests
    if (NOT ${ENABLE_TEST_LOGS})
        target_sources(${_TESTNAME} PRIVATE ${ROOT_TEST_DIR}/mock-logs.cpp)
        target_sources(${_TESTNAME} PRIVATE ${ROOT_TEST_DIR}/mock-freertos-tls.cpp)
    endif (NOT ${ENABLE_TEST_LOGS})

    target_link_libraries(${TESTNAME} gtest_main gmock)
    target_compile_options(${TESTNAME} PUBLIC "-pthread")
    target_link_options(${TESTNAME} PUBLIC "-pthread")
    target_link_libraries(${_TESTNAME} PRIVATE gtest_main gmock)
    foreach(lib ${_TEST_ARGS_LIBS})
        target_link_libraries(${_TESTNAME} PRIVATE ${lib})
    endforeach(lib)

    add_dependencies(check ${TESTNAME})
    gtest_add_tests(${TESTNAME} "" AUTO)
    foreach(include ${_TEST_ARGS_INCLUDE})
        target_include_directories(${_TESTNAME} PRIVATE ${include})
    endforeach(include)

    foreach(def ${_TEST_ARGS_DEFS})
        target_compile_definitions(${_TESTNAME} PRIVATE ${def})
    endforeach(def)

    foreach(dep ${_TEST_ARGS_DEPS})
        add_dependencies(${_TESTNAME}  ${dep})
    endforeach(dep)

    target_compile_options(${_TESTNAME} PUBLIC "-pthread")
    target_link_options(${_TESTNAME} PUBLIC "-pthread")

    add_dependencies(unittests ${_TESTNAME})
    add_dependencies(check ${_TESTNAME})
    
    gtest_add_tests(${_TESTNAME} "" AUTO)
endfunction()

function(add_catch2_executable)


@@ 50,8 94,8 @@ function(add_catch2_executable)

    # disable logs in unit tests
    if (NOT ${ENABLE_TEST_LOGS})
    	target_sources(${_TESTNAME} PRIVATE ${ROOT_TEST_DIR}/mock-logs.cpp)
    	target_sources(${_TESTNAME} PRIVATE ${ROOT_TEST_DIR}/mock-freertos-tls.cpp)
        target_sources(${_TESTNAME} PRIVATE ${ROOT_TEST_DIR}/mock-logs.cpp)
        target_sources(${_TESTNAME} PRIVATE ${ROOT_TEST_DIR}/mock-freertos-tls.cpp)
    endif (NOT ${ENABLE_TEST_LOGS})

    target_link_libraries(${_TESTNAME} PRIVATE Catch2::Catch2)