~aleteoryx/muditaos

40bf381eca9c7146f205a4b5dd3f369dd2b0e317 — Marcin Smoczyński 5 years ago 7dee85f
[EGD-5086] Fix voice not starting when calling

Due to a race condition between source and sink voice is not always
starting when calling. Introduce audio stream connections to avoid
race condition and improve handling of audio start and stop operations.

Signed-off-by: Marcin Smoczyński <smoczynski.marcin@gmail.com>
32 files changed, 640 insertions(+), 470 deletions(-)

M module-audio/Audio/BluetoothProxyAudio.cpp
M module-audio/Audio/BluetoothProxyAudio.hpp
M module-audio/Audio/Endpoint.cpp
M module-audio/Audio/Endpoint.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
M module-audio/Audio/Profiles/ProfilePlaybackBluetoothA2DP.hpp
M module-audio/Audio/Stream.cpp
M module-audio/Audio/decoder/Decoder.cpp
M module-audio/Audio/decoder/Decoder.hpp
M module-audio/Audio/decoder/DecoderWorker.cpp
M module-audio/Audio/decoder/DecoderWorker.hpp
M module-audio/Audio/decoder/decoderWAV.hpp
M module-bsp/board/linux/audio/LinuxCellularAudio.cpp
M module-bsp/board/linux/audio/LinuxCellularAudio.hpp
M module-bsp/board/linux/audio/linux_audiocodec.cpp
M module-bsp/board/linux/audio/linux_audiocodec.hpp
M module-bsp/board/rt1051/bsp/audio/RT1051Audiocodec.cpp
M module-bsp/board/rt1051/bsp/audio/RT1051Audiocodec.hpp
D module-bsp/board/rt1051/bsp/audio/RT1051BluetoothAudio.cpp
D module-bsp/board/rt1051/bsp/audio/RT1051BluetoothAudio.hpp
M module-bsp/board/rt1051/bsp/audio/RT1051CellularAudio.cpp
M module-bsp/board/rt1051/bsp/audio/RT1051CellularAudio.hpp
A module-bsp/board/rt1051/bsp/audio/SAIAudioDevice.cpp
A module-bsp/board/rt1051/bsp/audio/SAIAudioDevice.hpp
M module-bsp/bsp/audio/bsp_audio.cpp
M module-bsp/bsp/audio/bsp_audio.hpp
M module-bsp/targets/Target_RT1051.cmake
M module-sys/Service/Worker.cpp
M module-sys/Service/Worker.hpp
M module-audio/Audio/BluetoothProxyAudio.cpp => module-audio/Audio/BluetoothProxyAudio.cpp +18 -0
@@ 72,4 72,22 @@ namespace bsp
    {
        Stop();
    }

    void BluetoothProxyAudio::onDataReceive()
    {}

    void BluetoothProxyAudio::onDataSend()
    {}

    void BluetoothProxyAudio::enableInput()
    {}

    void BluetoothProxyAudio::enableOutput()
    {}

    void BluetoothProxyAudio::disableInput()
    {}

    void BluetoothProxyAudio::disableOutput()
    {}
} // namespace bsp

M module-audio/Audio/BluetoothProxyAudio.hpp => module-audio/Audio/BluetoothProxyAudio.hpp +7 -0
@@ 28,6 28,13 @@ namespace bsp
        AudioDevice::RetCode InputPathCtrl(InputPath inputPath) final;
        bool IsFormatSupported(const Format &format) final;

        void onDataReceive() final;
        void onDataSend() final;
        void enableInput() final;
        void enableOutput() final;
        void disableInput() final;
        void disableOutput() final;

      private:
        audio::Stream &dataStreamOut;
        audio::Stream &dataStreamIn;

M module-audio/Audio/Endpoint.cpp => module-audio/Audio/Endpoint.cpp +72 -14
@@ 7,18 7,21 @@

using namespace audio;

void Endpoint::setStream(Stream &stream)
Endpoint::Endpoint(const Capabilities &caps) : _caps(caps)
{}

const Endpoint::Capabilities &Endpoint::getCapabilities() const noexcept
{
    assert(_stream == nullptr);
    _stream = &stream;
    return _caps;
}

Stream *Endpoint::getStream() const noexcept
void Endpoint::connectStream(Stream &stream)
{
    return _stream;
    assert(_stream == nullptr);
    _stream = &stream;
}

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


@@ 29,16 32,71 @@ bool Endpoint::isConnected() const noexcept
    return _stream != nullptr;
}

void Source::connect(Sink &sink, Stream &stream)
StreamConnection::StreamConnection(Source *source, Sink *sink, Stream *stream)
    : _sink(sink), _source(source), _stream(stream)
{
    assert(_sink != nullptr);
    assert(_source != nullptr);
    assert(_stream != nullptr);

    _sink->connectStream(*_stream);
    _source->connectStream(*_stream);
}

StreamConnection::~StreamConnection()
{
    destroy();
}

void StreamConnection::destroy()
{
    disable();
    _sink->disconnectStream();
    _source->disconnectStream();
}

void StreamConnection::enable()
{
    connectedSink = &sink;
    connectedSink->setStream(stream);
    setStream(stream);
    if (enabled) {
        return;
    }

    _stream->reset();
    _sink->enableOutput();
    _source->enableInput();

    enabled = true;
}

void Source::disconnectStream()
void StreamConnection::disable()
{
    unsetStream();
    connectedSink->unsetStream();
    connectedSink = nullptr;
    if (!enabled) {
        return;
    }

    _source->disableInput();
    _sink->disableOutput();
    _stream->reset();

    enabled = false;
}

bool StreamConnection::isEnabled() const noexcept
{
    return enabled;
}

Source *StreamConnection::getSource() const noexcept
{
    return _source;
}

Sink *StreamConnection::getSink() const noexcept
{
    return _sink;
}

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

M module-audio/Audio/Endpoint.hpp => module-audio/Audio/Endpoint.hpp +70 -9
@@ 10,26 10,87 @@ namespace audio
    class Endpoint
    {
      public:
        void setStream(Stream &stream);
        Stream *getStream() const noexcept;
        void unsetStream();
        struct Capabilities
        {
            bool usesDMA             = false;
            std::size_t maxBlockSize = 0;
        };

        Endpoint() = default;
        Endpoint(const Capabilities &caps);

        void connectStream(Stream &stream);
        void disconnectStream();
        bool isConnected() const noexcept;

      private:
        [[nodiscard]] const Capabilities &getCapabilities() const noexcept;

      protected:
        Capabilities _caps;
        Stream *_stream = nullptr;
    };

    class Sink : public Endpoint
    {};
    {
      public:
        virtual void onDataSend()    = 0;
        virtual void enableOutput()  = 0;
        virtual void disableOutput() = 0;
    };

    class Source : public Endpoint
    {
      public:
        void connect(Sink &sink, Stream &stream);
        void disconnectStream();
        virtual void onDataReceive() = 0;
        virtual void enableInput()   = 0;
        virtual void disableInput()  = 0;
    };

      private:
        Sink *connectedSink = nullptr;
    class IOProxy : public Sink, public Source
    {
      public:
        inline bool isSinkConnected() const noexcept
        {
            return Sink::isConnected();
        }

        inline bool isSourceConnected() const noexcept
        {
            return Source::isConnected();
        }

        inline void connectOutputStream(Stream &stream)
        {
            Sink::connectStream(stream);
        }

        inline void connectInputStream(Stream &stream)
        {
            Source::connectStream(stream);
        }
    };

    class StreamConnection
    {
      public:
        StreamConnection() = default;
        StreamConnection(Source *source, Sink *sink, Stream *stream);
        ~StreamConnection();

        void enable();
        void disable();
        void destroy();

        [[nodiscard]] Source *getSource() const noexcept;
        [[nodiscard]] Sink *getSink() const noexcept;
        [[nodiscard]] Stream *getStream() const noexcept;

        [[nodiscard]] bool isEnabled() const noexcept;

      private:
        bool enabled    = false;
        Sink *_sink     = nullptr;
        Source *_source = nullptr;
        Stream *_stream = nullptr;
    };
}; // namespace audio

M module-audio/Audio/Operation/PlaybackOperation.cpp => module-audio/Audio/Operation/PlaybackOperation.cpp +48 -65
@@ 16,8 16,6 @@ namespace audio
    using namespace AudioServiceMessage;
    using namespace utils;

#define PERF_STATS_ON 0

    PlaybackOperation::PlaybackOperation(const char *file, const audio::PlaybackType &playbackType, Callback callback)
        : Operation(callback, playbackType), dec(nullptr)
    {


@@ 26,6 24,13 @@ namespace audio
        AddProfile(Profile::Type::PlaybackHeadphones, playbackType, false);
        AddProfile(Profile::Type::PlaybackLoudspeaker, playbackType, true);

        endOfFileCallback = [this]() {
            state          = State::Idle;
            const auto req = AudioServiceMessage::EndOfFile(operationToken);
            serviceCallback(&req);
            return std::string();
        };

        auto defaultProfile = GetProfile(Profile::Type::PlaybackLoudspeaker);
        if (!defaultProfile) {
            throw AudioInitException("Error during initializing profile", RetCode::ProfileNotSet);


@@ 36,18 41,12 @@ namespace audio
        if (dec == nullptr) {
            throw AudioInitException("Error during initializing decoder", RetCode::FileDoesntExist);
        }
        tags = dec->fetchTags();

        auto retCode = SwitchToPriorityProfile();
        if (retCode != RetCode::Success) {
            throw AudioInitException("Failed to switch audio profile", retCode);
        }

        endOfFileCallback = [this]() {
            state          = State::Idle;
            const auto req = AudioServiceMessage::EndOfFile(operationToken);
            serviceCallback(&req);
            return std::string();
        };
    }

    audio::RetCode PlaybackOperation::Start(audio::Token token)


@@ 55,31 54,21 @@ namespace audio
        if (state == State::Active || state == State::Paused) {
            return RetCode::InvokedInIncorrectState;
        }
        operationToken = token;

        assert(dataStreamOut != nullptr);
        // create audio connection
        outputConnection = std::make_unique<StreamConnection>(dec.get(), audioDevice.get(), dataStreamOut);

        dec->startDecodingWorker(*dataStreamOut, endOfFileCallback);
        // decoder worker soft start - must be called after connection setup
        dec->startDecodingWorker(endOfFileCallback);

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

        state         = State::Active;

        if (tags->num_channel == channel::stereoSound) {
            currentProfile->SetInOutFlags(static_cast<uint32_t>(bsp::AudioDevice::Flags::OutputStereo));
        }
        else {
            currentProfile->SetInOutFlags(static_cast<uint32_t>(bsp::AudioDevice::Flags::OutputMono));
            if (currentProfile->GetOutputPath() == bsp::AudioDevice::OutputPath::Headphones) {
                currentProfile->SetOutputPath(bsp::AudioDevice::OutputPath::HeadphonesMono);
            }
        }
        // start output device and enable audio connection
        auto ret = audioDevice->Start(currentProfile->GetAudioFormat());
        outputConnection->enable();

        currentProfile->SetSampleRate(tags->sample_rate);
        // update state and token
        state          = State::Active;
        operationToken = token;

        auto ret = audioDevice->Start(currentProfile->GetAudioFormat());
        return GetDeviceError(ret);
    }



@@ 90,7 79,10 @@ namespace audio
            return audio::RetCode::DeviceFailure;
        }

        // stop playback by destroying audio connection
        outputConnection.reset();
        dec->stopDecodingWorker();

        return GetDeviceError(audioDevice->Stop());
    }



@@ 100,23 92,18 @@ namespace audio
            return RetCode::InvokedInIncorrectState;
        }
        state = State::Paused;

        dec->stopDecodingWorker();
        return GetDeviceError(audioDevice->Stop());
        outputConnection->disable();
        return audio::RetCode::Success;
    }

    audio::RetCode PlaybackOperation::Resume()
    {

        if (state == State::Active || state == State::Idle) {
            return RetCode::InvokedInIncorrectState;
        }
        state    = State::Active;

        assert(dataStreamOut != nullptr);
        dec->startDecodingWorker(*dataStreamOut, endOfFileCallback);
        auto ret = audioDevice->Start(currentProfile->GetAudioFormat());
        return GetDeviceError(ret);
        state = State::Active;
        outputConnection->enable();
        return audio::RetCode::Success;
    }

    audio::RetCode PlaybackOperation::SetOutputVolume(float vol)


@@ 159,41 146,40 @@ namespace audio

    audio::RetCode PlaybackOperation::SwitchProfile(const Profile::Type type)
    {
        uint32_t currentSampleRate = currentProfile->GetSampleRate();
        uint32_t currentInOutFlags = currentProfile->GetInOutFlags();

        auto ret = GetProfile(type);
        if (ret) {
            currentProfile = ret;
        }
        else {
        auto newProfile = GetProfile(type);
        if (newProfile == nullptr) {
            return RetCode::UnsupportedProfile;
        }

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

        audioDevice = CreateDevice(currentProfile->GetAudioDeviceType(), audioCallback).value_or(nullptr);
        /// profile change - (re)create output device; stop audio first by
        /// killing audio connection
        outputConnection.reset();
        audioDevice.reset();
        audioDevice = CreateDevice(newProfile->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);
        // adjust new profile with information from file's tags
        newProfile->SetSampleRate(tags->sample_rate);
        if (tags->num_channel == channel::stereoSound) {
            newProfile->SetInOutFlags(static_cast<uint32_t>(bsp::AudioDevice::Flags::OutputStereo));
        }
        else {
            newProfile->SetInOutFlags(static_cast<uint32_t>(bsp::AudioDevice::Flags::OutputMono));
            if (newProfile->GetOutputPath() == bsp::AudioDevice::OutputPath::Headphones) {
                newProfile->SetOutputPath(bsp::AudioDevice::OutputPath::HeadphonesMono);
            }
        }

        switch (state) {
        case State::Idle:
        case State::Paused:
            break;
        // store profile
        currentProfile = newProfile;

        case State::Active:
        if (state == State::Active) {
            // playback in progress, restart
            state = State::Idle;
            Start(operationToken);
            break;
        }

        return audio::RetCode::Success;


@@ 201,10 187,7 @@ namespace audio

    PlaybackOperation::~PlaybackOperation()
    {
        dec->stopDecodingWorker();
        Stop();
        dataStreamOut->reset();
        dataStreamIn->reset();
    }

} // namespace audio

M module-audio/Audio/Operation/PlaybackOperation.hpp => module-audio/Audio/Operation/PlaybackOperation.hpp +1 -0
@@ 41,6 41,7 @@ namespace audio
      private:
        std::unique_ptr<Decoder> dec;
        std::unique_ptr<Tags> tags;
        std::unique_ptr<StreamConnection> outputConnection = nullptr;

        DecoderWorker::EndOfFileCallback endOfFileCallback;
    };

M module-audio/Audio/Operation/RouterOperation.cpp => module-audio/Audio/Operation/RouterOperation.cpp +56 -36
@@ 49,23 49,37 @@ namespace audio
        operationToken = token;
        state          = State::Active;

        if (audioDevice->IsFormatSupported(currentProfile->GetAudioFormat())) {
            auto ret = audioDevice->Start(currentProfile->GetAudioFormat());
            if (ret != bsp::AudioDevice::RetCode::Success) {
                LOG_ERROR("Start error: %s", audio::str(audio::RetCode::DeviceFailure).c_str());
            }
        // check if audio devices support desired audio format
        if (!audioDevice->IsFormatSupported(currentProfile->GetAudioFormat())) {
            return RetCode::InvalidFormat;
        }
        else {

        if (!audioDeviceCellular->IsFormatSupported(currentProfile->GetAudioFormat())) {
            return RetCode::InvalidFormat;
        }

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

        if (auto ret = audioDeviceCellular->Start(currentProfile->GetAudioFormat());
            ret != bsp::AudioDevice::RetCode::Success) {
            return GetDeviceError(ret);
        }

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

        // enable audio connections
        inputConnection->enable();
        outputConnection->enable();

        return audio::RetCode::Success;
    }

    audio::RetCode RouterOperation::Stop()


@@ 75,10 89,14 @@ namespace audio
        }

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

        audioDevice->Stop();
        audioDeviceCellular->Stop();
        dataStreamOut->reset();
        dataStreamIn->reset();

        audioDevice.reset();
        audioDeviceCellular.reset();

        return RetCode::Success;
    }


@@ 90,8 108,8 @@ namespace audio
        }

        state = State::Paused;
        audioDevice->Stop();
        audioDeviceCellular->Stop();
        outputConnection->disable();
        inputConnection->disable();
        return RetCode::Success;
    }



@@ 102,8 120,8 @@ namespace audio
        }

        state = State::Active;
        audioDevice->Start(currentProfile->GetAudioFormat());
        audioDeviceCellular->Start(currentProfile->GetAudioFormat());
        inputConnection->enable();
        outputConnection->enable();
        return RetCode::Success;
    }



@@ 141,41 159,38 @@ namespace audio

    audio::RetCode RouterOperation::SwitchProfile(const audio::Profile::Type type)
    {
        auto ret = GetProfile(type);
        auto newProfile     = GetProfile(type);
        auto callInProgress = state == State::Active;

        if (ret) {
            if (currentProfile && currentProfile->GetType() == ret->GetType()) {
                return RetCode::Success;
            }
            currentProfile = ret;
        }
        else {
        if (newProfile == nullptr) {
            return RetCode::UnsupportedProfile;
        }

        audioDevice = CreateDevice(currentProfile->GetAudioDeviceType(), nullptr).value_or(nullptr);
        if (currentProfile && currentProfile->GetType() == newProfile->GetType()) {
            return RetCode::Success;
        }

        if (callInProgress) {
            Stop();
        }

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

        audioDeviceCellular = CreateDevice(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);
        // store new profile
        currentProfile = newProfile;

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

        case State::Active:
            state = State::Idle;
            Start(operationToken);
            break;
        if (callInProgress) {
            return Start(operationToken);
        }

        return RetCode::Success;


@@ 192,4 207,9 @@ namespace audio
        return 0.0;
    }

    RouterOperation::~RouterOperation()
    {
        Stop();
    }

} // namespace audio

M module-audio/Audio/Operation/RouterOperation.hpp => module-audio/Audio/Operation/RouterOperation.hpp +4 -1
@@ 8,6 8,7 @@
#include <Audio/encoder/Encoder.hpp>
#include <Audio/AudioCommon.hpp>
#include <Audio/Profiles/Profile.hpp>
#include <Audio/Endpoint.hpp>
#include <bsp/audio/bsp_audio.hpp>
#include <mutex.hpp>



@@ 28,7 29,7 @@ namespace audio

      public:
        RouterOperation(const char *file, AudioServiceMessage::Callback callback);
        ~RouterOperation() = default;
        ~RouterOperation();

        audio::RetCode Start(audio::Token token) final;
        audio::RetCode Stop() final;


@@ 47,6 48,8 @@ namespace audio
        bool muteEnable = false;
        std::unique_ptr<Encoder> enc;
        std::unique_ptr<bsp::AudioDevice> audioDeviceCellular;
        std::unique_ptr<StreamConnection> outputConnection;
        std::unique_ptr<StreamConnection> inputConnection;
    };

} // namespace audio

M module-audio/Audio/Profiles/ProfilePlaybackBluetoothA2DP.hpp => module-audio/Audio/Profiles/ProfilePlaybackBluetoothA2DP.hpp +0 -1
@@ 4,7 4,6 @@
#pragma once

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

namespace audio
{

M module-audio/Audio/Stream.cpp => module-audio/Audio/Stream.cpp +15 -13
@@ 27,7 27,7 @@ bool Stream::push(void *data, std::size_t dataSize)

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

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


@@ 65,7 65,7 @@ bool Stream::push()

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

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


@@ 95,7 95,7 @@ bool Stream::pop(Span &span)

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

    _blocksUsed -= _peekCount;
    _peekCount = 0;


@@ 106,7 106,7 @@ void Stream::consume()

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

    if (getPeekedCount() < getUsedBlockCount()) {
        span = *_peekPosition++;


@@ 121,7 121,7 @@ bool Stream::peek(Span &span)

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

    _peekPosition = _dataStart;
    _peekCount    = 0;


@@ 129,7 129,7 @@ void Stream::unpeek()

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

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


@@ 143,7 143,7 @@ bool Stream::reserve(Span &span)

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

    _blocksUsed += _reserveCount;
    _reserveCount = 0;


@@ 154,7 154,7 @@ void Stream::commit()

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

    _reserveCount             = 0;
    _writeReservationPosition = _dataEnd;


@@ 162,21 162,21 @@ void Stream::release()

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

    return _blockSize;
}

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

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

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

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


@@ 230,13 230,13 @@ std::size_t Stream::getReservedCount() const noexcept

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

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



@@ 247,6 247,8 @@ bool Stream::blocksAvailable() const noexcept

void Stream::reset()
{
    LockGuard lock;

    _dataStart                = {_buffer.get(), _blockSize * _blockCount, _buffer.get(), _blockSize};
    _dataEnd                  = _dataStart;
    _peekPosition             = _dataStart;

M module-audio/Audio/decoder/Decoder.cpp => module-audio/Audio/decoder/Decoder.cpp +18 -2
@@ 121,10 121,11 @@ namespace audio
        memcpy(pcm, &workerBuffer[0], samplecount * 2 * sizeof(int16_t));
    }

    void Decoder::startDecodingWorker(Stream &audioStream, DecoderWorker::EndOfFileCallback endOfFileCallback)
    void Decoder::startDecodingWorker(DecoderWorker::EndOfFileCallback endOfFileCallback)
    {
        assert(_stream != nullptr);
        if (!audioWorker) {
            audioWorker = std::make_unique<DecoderWorker>(audioStream, this, endOfFileCallback);
            audioWorker = std::make_unique<DecoderWorker>(_stream, this, endOfFileCallback);
            audioWorker->init();
            audioWorker->run();
        }


@@ 141,4 142,19 @@ namespace audio
        audioWorker = nullptr;
    }

    void Decoder::onDataReceive()
    {
        audioWorker->enablePlayback();
    }

    void Decoder::enableInput()
    {
        audioWorker->enablePlayback();
    }

    void Decoder::disableInput()
    {
        audioWorker->disablePlayback();
    }

} // namespace audio

M module-audio/Audio/decoder/Decoder.hpp => module-audio/Audio/decoder/Decoder.hpp +8 -3
@@ 80,9 80,6 @@ namespace audio

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

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

        std::unique_ptr<Tags> fetchTags();

        // Range 0 - 1


@@ 103,6 100,13 @@ namespace audio
            return position;
        }

        void onDataReceive() override;
        void enableInput() override;
        void disableInput() override;

        void startDecodingWorker(DecoderWorker::EndOfFileCallback endOfFileCallback);
        void stopDecodingWorker();

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



@@ 127,6 131,7 @@ namespace audio

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

} // namespace audio

M module-audio/Audio/decoder/DecoderWorker.cpp => module-audio/Audio/decoder/DecoderWorker.cpp +62 -19
@@ 4,15 4,15 @@
#include "DecoderWorker.hpp"
#include "Audio/decoder/Decoder.hpp"

audio::DecoderWorker::DecoderWorker(Stream &audioStreamOut, Decoder *decoder, EndOfFileCallback endOfFileCallback)
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))
      bufferSize(audioStreamOut->getBlockSize() / sizeof(BufferInternalType))
{}

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

auto audio::DecoderWorker::init(std::list<sys::WorkerQueueInfo> queues) -> bool


@@ 27,7 27,7 @@ auto audio::DecoderWorker::init(std::list<sys::WorkerQueueInfo> queues) -> bool
        return false;
    }

    audioStreamOut.registerListener(queueListener.get());
    audioStreamOut->registerListener(queueListener.get());

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


@@ 39,8 39,9 @@ auto audio::DecoderWorker::init(std::list<sys::WorkerQueueInfo> queues) -> bool

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

        switch (event.second) {


@@ 55,22 56,64 @@ bool audio::DecoderWorker::handleMessage(uint32_t queueID)
        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;
                }
            pushAudioData();
        }
    }
    else if (queueName == SERVICE_QUEUE_NAME) {
        auto &serviceQueue = getServiceQueue();
        sys::WorkerCommand cmd;

                if (!audioStreamOut.push(decoderBuffer.get(), samplesRead * sizeof(BufferInternalType))) {
                    LOG_FATAL("Decoder failed to push to stream.");
                    break;
                }
        if (serviceQueue.Dequeue(&cmd)) {
            switch (static_cast<Command>(cmd.command)) {
            case Command::EnablePlayback: {
                playbackEnabled = true;
                stateSemaphore.Give();
                pushAudioData();
                break;
            }
            case Command::DisablePlayback: {
                playbackEnabled = false;
                stateSemaphore.Give();
            }
            }
        }
    }

    return true;
}

void audio::DecoderWorker::pushAudioData()
{
    auto samplesRead = 0;

    while (!audioStreamOut->isFull() && playbackEnabled) {
        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;
        }
    }
}

bool audio::DecoderWorker::enablePlayback()
{
    return sendCommand({.command = static_cast<uint32_t>(Command::EnablePlayback), .data = nullptr}) &&
           stateChangeWait();
}

bool audio::DecoderWorker::disablePlayback()
{
    return sendCommand({.command = static_cast<uint32_t>(Command::DisablePlayback), .data = nullptr}) &&
           stateChangeWait();
}

bool audio::DecoderWorker::stateChangeWait()
{
    return stateSemaphore.Take();
}

M module-audio/Audio/decoder/DecoderWorker.hpp => module-audio/Audio/decoder/DecoderWorker.hpp +20 -5
@@ 3,9 3,11 @@

#pragma once

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

#include <Service/Worker.hpp>
#include <semaphore.hpp>

namespace audio
{
    class Decoder;


@@ 13,14 15,25 @@ namespace audio
    {
      public:
        using EndOfFileCallback = std::function<void()>;
        enum class Command
        {
            EnablePlayback,
            DisablePlayback,
        };

        DecoderWorker(Stream &audioStreamOut, Decoder *decoder, EndOfFileCallback endOfFileCallback);
        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;

        auto enablePlayback() -> bool;
        auto disablePlayback() -> bool;

      private:
        virtual auto handleMessage(uint32_t queueID) -> bool override;
        void pushAudioData();
        bool stateChangeWait();

        using BufferInternalType = int16_t;

        static constexpr auto workerName            = "DecoderWorker";


@@ 28,10 41,12 @@ namespace audio
        static constexpr auto listenerQueueName     = "DecoderWorkerQueue";
        static constexpr auto listenerQueueCapacity = 1024;

        Stream &audioStreamOut;
        Decoder *decoder = nullptr;
        Stream *audioStreamOut = nullptr;
        Decoder *decoder       = nullptr;
        EndOfFileCallback endOfFileCallback;
        std::unique_ptr<StreamQueuedEventsListener> queueListener;
        bool playbackEnabled = false;
        cpp_freertos::BinarySemaphore stateSemaphore;

        const int bufferSize;
        std::unique_ptr<BufferInternalType[]> decoderBuffer;

M module-audio/Audio/decoder/decoderWAV.hpp => module-audio/Audio/decoder/decoderWAV.hpp +0 -1
@@ 44,4 44,3 @@ namespace audio
    };

} // namespace audio


M module-bsp/board/linux/audio/LinuxCellularAudio.cpp => module-bsp/board/linux/audio/LinuxCellularAudio.cpp +21 -2
@@ 95,7 95,7 @@ namespace bsp
        }

        if (inputBuffer) {
            int16_t *pBuff = reinterpret_cast<int16_t *>(const_cast<void *>(inputBuffer));
            int16_t *pBuff              = reinterpret_cast<int16_t *>(const_cast<void *>(inputBuffer));
            constexpr float scaleFactor = .1f;
            std::transform(pBuff, pBuff + framesToFetch, pBuff, [ptr, &scaleFactor](int16_t c) -> int16_t {
                return static_cast<float>(c * ptr->currentFormat.inputGain * scaleFactor);


@@ 111,7 111,7 @@ namespace bsp

            // Scale output buffer
            if (outputBuffer) {
                int16_t *pBuff = reinterpret_cast<int16_t *>(outputBuffer);
                int16_t *pBuff              = reinterpret_cast<int16_t *>(outputBuffer);
                constexpr float scaleFactor = .1f;
                std::transform(pBuff, pBuff + framesToFetch, pBuff, [ptr](int16_t c) -> int16_t {
                    return static_cast<float>(c * ptr->currentFormat.outputVolume / 10.f);


@@ 202,4 202,23 @@ namespace bsp
            return false;
        }
    }

    void LinuxCellularAudio::onDataReceive()
    {}

    void LinuxCellularAudio::onDataSend()
    {}

    void LinuxCellularAudio::enableInput()
    {}

    void LinuxCellularAudio::enableOutput()
    {}

    void LinuxCellularAudio::disableInput()
    {}

    void LinuxCellularAudio::disableOutput()
    {}

} // namespace bsp

M module-bsp/board/linux/audio/LinuxCellularAudio.hpp => module-bsp/board/linux/audio/LinuxCellularAudio.hpp +7 -0
@@ 31,6 31,13 @@ namespace bsp

        bool IsFormatSupported(const Format &format) override final;

        void onDataReceive() final;
        void onDataSend() final;
        void enableInput() final;
        void enableOutput() final;
        void disableInput() final;
        void disableOutput() final;

      private:
        PaStream *stream;


M module-bsp/board/linux/audio/linux_audiocodec.cpp => module-bsp/board/linux/audio/linux_audiocodec.cpp +21 -2
@@ 93,7 93,7 @@ namespace bsp
        }

        if (inputBuffer) {
            int16_t *pBuff = reinterpret_cast<int16_t *>(const_cast<void *>(inputBuffer));
            int16_t *pBuff              = reinterpret_cast<int16_t *>(const_cast<void *>(inputBuffer));
            constexpr float scaleFactor = .1f;
            std::transform(pBuff, pBuff + framesToFetch, pBuff, [ptr, &scaleFactor](int16_t c) -> int16_t {
                return static_cast<float>(c * ptr->currentFormat.inputGain * scaleFactor);


@@ 109,7 109,7 @@ namespace bsp

            // Scale output buffer
            if (outputBuffer) {
                int16_t *pBuff = reinterpret_cast<int16_t *>(outputBuffer);
                int16_t *pBuff              = reinterpret_cast<int16_t *>(outputBuffer);
                constexpr float scaleFactor = .1f;
                std::transform(pBuff, pBuff + framesToFetch, pBuff, [ptr, &scaleFactor](int16_t c) -> int16_t {
                    return static_cast<float>(c * ptr->currentFormat.outputVolume * scaleFactor);


@@ 200,4 200,23 @@ namespace bsp
            return false;
        }
    }

    void LinuxAudiocodec::onDataReceive()
    {}

    void LinuxAudiocodec::onDataSend()
    {}

    void LinuxAudiocodec::enableInput()
    {}

    void LinuxAudiocodec::enableOutput()
    {}

    void LinuxAudiocodec::disableInput()
    {}

    void LinuxAudiocodec::disableOutput()
    {}

} // namespace bsp

M module-bsp/board/linux/audio/linux_audiocodec.hpp => module-bsp/board/linux/audio/linux_audiocodec.hpp +7 -0
@@ 24,6 24,13 @@ namespace bsp
        AudioDevice::RetCode InputPathCtrl(InputPath inputPath) override final;
        bool IsFormatSupported(const Format &format) override final;

        void onDataReceive() final;
        void onDataSend() final;
        void enableInput() final;
        void enableOutput() final;
        void disableInput() final;
        void disableOutput() final;

      private:
        PaStream *stream;


M module-bsp/board/rt1051/bsp/audio/RT1051Audiocodec.cpp => module-bsp/board/rt1051/bsp/audio/RT1051Audiocodec.cpp +6 -57
@@ 26,7 26,8 @@ namespace bsp
    sai_edma_handle_t RT1051Audiocodec::rxHandle      = {};

    RT1051Audiocodec::RT1051Audiocodec(bsp::AudioDevice::audioCallback_t callback)
        : AudioDevice(callback), saiInFormat{}, saiOutFormat{}, codecParams{}, codec{}
        : SAIAudioDevice(callback, BOARD_AUDIOCODEC_SAIx, &rxHandle, &txHandle), saiInFormat{}, saiOutFormat{},
          codecParams{}, codec{}
    {
        isInitialized = true;
    }


@@ 238,17 239,6 @@ namespace bsp

        /* Reset SAI Rx internal logic */
        SAI_RxSoftwareReset(BOARD_AUDIOCODEC_SAIx, kSAI_ResetTypeSoftware);

        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()


@@ 279,15 269,6 @@ namespace bsp

        /* Reset SAI Tx internal logic */
        SAI_TxSoftwareReset(BOARD_AUDIOCODEC_SAIx, kSAI_ResetTypeSoftware);

        if (!sink.isConnected()) {
            LOG_FATAL("No input stream connected!");
            return;
        }

        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()


@@ 297,9 278,6 @@ namespace bsp
            SAI_TransferTerminateSendEDMA(BOARD_AUDIOCODEC_SAIx, &txHandle);
        }
        memset(&txHandle, 0, sizeof(txHandle));
        if (sink.isConnected()) {
            sink.getStream()->unpeek();
        }
    }

    void RT1051Audiocodec::InStop()


@@ 309,47 287,18 @@ namespace bsp
            SAI_TransferAbortReceiveEDMA(BOARD_AUDIOCODEC_SAIx, &rxHandle);
        }
        memset(&rxHandle, 0, sizeof(rxHandle));
        if (source.isConnected()) {
            source.getStream()->release();
        }
    }

    void rxAudioCodecCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData)
    {
        audio::Stream::Span dataSpan;
        auto self    = static_cast<RT1051Audiocodec *>(userData);
        auto &source = self->source;

        /// exit if not connected to the stream
        if (!source.isConnected()) {
            return;
        }

        /// reserve space for the next read commiting previously reserved block before
        source.getStream()->commit();
        source.getStream()->reserve(dataSpan);

        sai_transfer_t xfer{.data = dataSpan.data, .dataSize = dataSpan.dataSize};
        SAI_TransferReceiveEDMA(BOARD_AUDIOCODEC_SAIx, &self->rxHandle, &xfer);
        auto self = static_cast<RT1051Audiocodec *>(userData);
        self->onDataReceive();
    }

    void txAudioCodecCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData)
    {
        audio::Stream::Span dataSpan;
        auto self  = static_cast<RT1051Audiocodec *>(userData);
        auto &sink = self->sink;

        /// exit if not connected to the stream
        if (!sink.isConnected()) {
            return;
        }

        /// pop previous read and peek next
        sink.getStream()->consume();
        sink.getStream()->peek(dataSpan);

        sai_transfer_t xfer{.data = dataSpan.data, .dataSize = dataSpan.dataSize};
        SAI_TransferSendEDMA(BOARD_AUDIOCODEC_SAIx, &self->txHandle, &xfer);
        auto self = static_cast<RT1051Audiocodec *>(userData);
        self->onDataSend();
    }

} // namespace bsp

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

#pragma once

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

#include "FreeRTOS.h"


@@ 24,7 24,7 @@ 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);

    class RT1051Audiocodec : public AudioDevice
    class RT1051Audiocodec : public SAIAudioDevice
    {

      public:

D module-bsp/board/rt1051/bsp/audio/RT1051BluetoothAudio.cpp => module-bsp/board/rt1051/bsp/audio/RT1051BluetoothAudio.cpp +0 -104
@@ 1,104 0,0 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "RT1051BluetoothAudio.hpp"

namespace bsp
{
    RT1051BluetoothAudio::RT1051BluetoothAudio(AudioDevice::audioCallback_t callback) : AudioDevice(callback)
    {
        isInitialized = true;
        LOG_INFO("Device created!");
    }
    AudioDevice::RetCode RT1051BluetoothAudio::Start(const AudioDevice::Format &format)
    {
        LOG_INFO("Start");
        Init();
        if ((format.flags & static_cast<uint32_t>(AudioDevice::Flags::OutputMono)) != 0u) {
            OutStart();
        }
        else if ((format.flags & static_cast<uint32_t>(AudioDevice::Flags::OutputStereo)) != 0u) {
            OutStart();
        }
        return AudioDevice::RetCode::Success;
    }
    AudioDevice::RetCode RT1051BluetoothAudio::Stop()
    {
        return AudioDevice::RetCode::Success;
    }
    AudioDevice::RetCode RT1051BluetoothAudio::OutputVolumeCtrl(float vol)
    {
        return AudioDevice::RetCode::Success;
    }
    AudioDevice::RetCode RT1051BluetoothAudio::InputGainCtrl(float gain)
    {
        return AudioDevice::RetCode::Success;
    }
    AudioDevice::RetCode RT1051BluetoothAudio::OutputPathCtrl(AudioDevice::OutputPath outputPath)
    {
        return AudioDevice::RetCode::Success;
    }
    AudioDevice::RetCode RT1051BluetoothAudio::InputPathCtrl(AudioDevice::InputPath inputPath)
    {
        return AudioDevice::RetCode::Success;
    }
    bool RT1051BluetoothAudio::IsFormatSupported(const AudioDevice::Format &format)
    {
        return true;
    }
    void outBluetoothAudioWorkerTask(void *pvp)
    {
        auto *inst    = static_cast<RT1051BluetoothAudio *>(pvp);
        auto dataSize = inst->metadata.samplesPerFrame;
        auto fatalError = false;

        if (inst->sourceQueue == nullptr) {
            LOG_FATAL("sourceQueue nullptr");
            fatalError = true;
        }

        while (!fatalError) {
            auto framesFetched = inst->GetAudioCallback()(nullptr, inst->audioData.data.data(), dataSize);
            if (framesFetched == 0) {
                break;
            }
            else if (framesFetched < inst->audioData.data.size()) {
                std::fill(inst->audioData.data.begin() + framesFetched, inst->audioData.data.end(), 0);
            }

            if (inst->sourceQueue != nullptr) {
                xQueueSend(inst->sourceQueue, inst->audioData.data.data(), 2);
            }
            else {
                LOG_ERROR("Queue nullptr");
                vTaskDelay(2);
            }
        }

        inst->OutStop();
        inst->outWorkerThread = nullptr;
        vTaskDelete(nullptr);
    }

    void RT1051BluetoothAudio::OutStart()
    {
        if (xTaskCreate(outBluetoothAudioWorkerTask,
                        "outbluetoothaudio",
                        stackSize,
                        this,
                        tskIDLE_PRIORITY,
                        &outWorkerThread) != pdPASS) {
            LOG_ERROR("Error during creating  output bluetooth audio task");
        }
    }
    void RT1051BluetoothAudio::OutStop()
    {}

    void RT1051BluetoothAudio::Init()
    {}

    RT1051BluetoothAudio::~RT1051BluetoothAudio()
    {
        Stop();
    }
} // namespace bsp

D module-bsp/board/rt1051/bsp/audio/RT1051BluetoothAudio.hpp => module-bsp/board/rt1051/bsp/audio/RT1051BluetoothAudio.hpp +0 -42
@@ 1,42 0,0 @@
// 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 "FreeRTOS.h"
#include "macros.h"
#include "task.h"
#include "bsp/audio/bsp_audio.hpp"
#include <module-bluetooth/Bluetooth/Device.hpp>
#include <mutex.hpp>

namespace bsp
{
    class RT1051BluetoothAudio final : public AudioDevice
    {
        static constexpr uint16_t stackSize = 1024;

      public:
        RT1051BluetoothAudio(AudioDevice::audioCallback_t callback);
        ~RT1051BluetoothAudio() final;

        AudioDevice::RetCode Start(const Format &format) final;
        AudioDevice::RetCode Stop() final;
        AudioDevice::RetCode OutputVolumeCtrl(float vol) final;
        AudioDevice::RetCode InputGainCtrl(float gain) final;
        AudioDevice::RetCode OutputPathCtrl(OutputPath outputPath) final;
        AudioDevice::RetCode InputPathCtrl(InputPath inputPath) final;
        bool IsFormatSupported(const Format &format) final;

        TaskHandle_t outWorkerThread;
        AudioData_t audioData;
        QueueHandle_t sourceQueue;
        DeviceMetadata_t metadata;
        void OutStop();

      private:
        void Init();
        void OutStart();
    };

} // namespace bsp

M module-bsp/board/rt1051/bsp/audio/RT1051CellularAudio.cpp => module-bsp/board/rt1051/bsp/audio/RT1051CellularAudio.cpp +6 -56
@@ 19,7 19,8 @@ namespace bsp
    sai_edma_handle_t RT1051CellularAudio::rxHandle = {};

    RT1051CellularAudio::RT1051CellularAudio(bsp::AudioDevice::audioCallback_t callback)
        : AudioDevice(callback), saiInFormat{}, saiOutFormat{}, config{}
        : SAIAudioDevice(callback, BOARD_CELLULAR_AUDIO_SAIx, &rxHandle, &txHandle), saiInFormat{},
          saiOutFormat{}, config{}
    {
        isInitialized = true;
    }


@@ 199,17 200,6 @@ namespace bsp

        /* Reset SAI Rx internal logic */
        SAI_RxSoftwareReset(BOARD_CELLULAR_AUDIO_SAIx, kSAI_ResetTypeSoftware);

        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()


@@ 233,7 223,6 @@ namespace bsp
                                       txCellularCallback,
                                       this,
                                       reinterpret_cast<edma_handle_t *>(txDMAHandle->GetHandle()));

        SAI_TransferTxSetFormatEDMA(
            BOARD_CELLULAR_AUDIO_SAIx, &txHandle, &sai_format, mclkSourceClockHz, mclkSourceClockHz);



@@ 241,15 230,6 @@ namespace bsp

        /* Reset SAI Tx internal logic */
        SAI_TxSoftwareReset(BOARD_CELLULAR_AUDIO_SAIx, kSAI_ResetTypeSoftware);

        if (!sink.isConnected()) {
            LOG_FATAL("No input stream connected!");
            return;
        }

        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()


@@ 259,9 239,6 @@ namespace bsp
            SAI_TransferTerminateSendEDMA(BOARD_CELLULAR_AUDIO_SAIx, &txHandle);
        }
        memset(&txHandle, 0, sizeof(txHandle));
        if (sink.isConnected()) {
            sink.getStream()->unpeek();
        }
    }

    void RT1051CellularAudio::InStop()


@@ 271,47 248,20 @@ namespace bsp
            SAI_TransferAbortReceiveEDMA(BOARD_CELLULAR_AUDIO_SAIx, &rxHandle);
        }
        memset(&rxHandle, 0, sizeof(rxHandle));
        if (source.isConnected()) {
            source.getStream()->release();
        }
    }

    void rxCellularCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData)
    {
        audio::Stream::Span dataSpan;
        auto self    = static_cast<RT1051CellularAudio *>(userData);
        auto &source = self->source;

        /// exit if not connected to the stream
        if (!source.isConnected()) {
            return;
        }

        /// reserve space for the next read commiting previously reserved block before
        source.getStream()->commit();
        source.getStream()->reserve(dataSpan);
        auto self = static_cast<RT1051CellularAudio *>(userData);

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

    void txCellularCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData)
    {
        audio::Stream::Span dataSpan;
        auto self  = static_cast<RT1051CellularAudio *>(userData);
        auto &sink = self->sink;

        /// exit if not connected to the stream
        if (!sink.isConnected()) {
            return;
        }

        /// pop previous read and peek next
        sink.getStream()->consume();
        sink.getStream()->peek(dataSpan);
        auto self = static_cast<RT1051CellularAudio *>(userData);

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

} // namespace bsp

M module-bsp/board/rt1051/bsp/audio/RT1051CellularAudio.hpp => module-bsp/board/rt1051/bsp/audio/RT1051CellularAudio.hpp +2 -2
@@ 4,7 4,7 @@
#ifndef PUREPHONE_RT1051CELLULARAUDIO_HPP
#define PUREPHONE_RT1051CELLULARAUDIO_HPP

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

#include "FreeRTOS.h"


@@ 23,7 23,7 @@ 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);

    class RT1051CellularAudio : public AudioDevice
    class RT1051CellularAudio : public SAIAudioDevice
    {

      public:

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

#include "SAIAudioDevice.hpp"

using namespace bsp;

SAIAudioDevice::SAIAudioDevice(AudioDevice::audioCallback_t callback,
                               I2S_Type *base,
                               sai_edma_handle_t *rxHandle,
                               sai_edma_handle_t *txHandle)
    : AudioDevice(callback), _base(base), rx(rxHandle), tx(txHandle)
{}

void SAIAudioDevice::initiateRxTransfer()
{
    audio::Stream::Span dataSpan;

    Source::_stream->reserve(dataSpan);
    auto xfer = sai_transfer_t{.data = dataSpan.data, .dataSize = dataSpan.dataSize};
    SAI_TransferReceiveEDMA(_base, rx, &xfer);
}

void SAIAudioDevice::initiateTxTransfer()
{
    auto nullSpan = Sink::_stream->getNullSpan();
    auto xfer     = sai_transfer_t{.data = nullSpan.data, .dataSize = nullSpan.dataSize};
    SAI_TransferSendEDMA(_base, tx, &xfer);
}

void SAIAudioDevice::onDataSend()
{
    audio::Stream::Span dataSpan;

    if (!txEnabled || !isSinkConnected()) {
        return;
    }

    /// pop previous read and peek next
    Sink::_stream->consume();
    Sink::_stream->peek(dataSpan);

    sai_transfer_t xfer{.data = dataSpan.data, .dataSize = dataSpan.dataSize};
    SAI_TransferSendEDMA(_base, tx, &xfer);
}

void SAIAudioDevice::onDataReceive()
{
    audio::Stream::Span dataSpan;

    if (!rxEnabled || !isSourceConnected()) {
        return;
    }

    /// reserve space for the next read commiting previously reserved block before
    Source::_stream->commit();
    Source::_stream->reserve(dataSpan);

    sai_transfer_t xfer{.data = dataSpan.data, .dataSize = dataSpan.dataSize};
    SAI_TransferReceiveEDMA(_base, rx, &xfer);
}

void SAIAudioDevice::enableInput()
{
    if (!isSourceConnected()) {
        LOG_FATAL("No output stream connected!");
        return;
    }

    rxEnabled = true;

    /// initiate first read
    initiateRxTransfer();
}

void SAIAudioDevice::enableOutput()
{
    if (!isSinkConnected()) {
        LOG_FATAL("No input stream connected!");
        return;
    }

    txEnabled = true;

    /// initiate first write
    initiateTxTransfer();
}

void SAIAudioDevice::disableInput()
{
    rxEnabled = false;
}

void SAIAudioDevice::disableOutput()
{
    txEnabled = false;
}

A module-bsp/board/rt1051/bsp/audio/SAIAudioDevice.hpp => module-bsp/board/rt1051/bsp/audio/SAIAudioDevice.hpp +38 -0
@@ 0,0 1,38 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "bsp/audio/bsp_audio.hpp"

#include "fsl_sai_edma.h"

namespace bsp
{

    class SAIAudioDevice : public bsp::AudioDevice
    {
      public:
        SAIAudioDevice(AudioDevice::audioCallback_t callback,
                       I2S_Type *base,
                       sai_edma_handle_t *rxHandle,
                       sai_edma_handle_t *txHandle);

        void onDataSend() override;
        void onDataReceive() override;
        void enableInput() override;
        void enableOutput() override;
        void disableInput() override;
        void disableOutput() override;

      protected:
        void initiateRxTransfer();
        void initiateTxTransfer();
        I2S_Type *_base;
        sai_edma_handle_t *rx = nullptr;
        sai_edma_handle_t *tx = nullptr;
        bool txEnabled        = false;
        bool rxEnabled        = false;
    };

} // namespace bsp

M module-bsp/bsp/audio/bsp_audio.cpp => module-bsp/bsp/audio/bsp_audio.cpp +24 -28
@@ 4,7 4,10 @@

#include "board/rt1051/bsp/audio/RT1051Audiocodec.hpp"
#include "board/rt1051/bsp/audio/RT1051CellularAudio.hpp"
#include "board/rt1051/bsp/audio/RT1051BluetoothAudio.hpp"

#include <Audio/Stream.hpp>

#include <cassert>

#elif defined(TARGET_Linux)
#include "audio/linux_audiocodec.hpp"


@@ 13,60 16,53 @@
#error "Unsupported target"
#endif

namespace bsp{

    std::optional<std::unique_ptr<AudioDevice>> AudioDevice::Create(bsp::AudioDevice::Type type, audioCallback_t callback) {
namespace bsp
{

    std::optional<std::unique_ptr<AudioDevice>> AudioDevice::Create(bsp::AudioDevice::Type type,
                                                                    audioCallback_t callback)
    {
        std::unique_ptr<AudioDevice> inst;

        switch(type){
        switch (type) {

            case Type ::Audiocodec:
            {
        case Type ::Audiocodec: {
#if defined(TARGET_RT1051)
                inst = std::make_unique<bsp::RT1051Audiocodec>(callback);
            inst = std::make_unique<bsp::RT1051Audiocodec>(callback);
#elif defined(TARGET_Linux)
                inst = std::make_unique<bsp::LinuxAudiocodec>(callback );
            inst = std::make_unique<bsp::LinuxAudiocodec>(callback);
#else
                #error "Unsupported target"
#error "Unsupported target"
#endif

            }
                break;
        } break;


            case Type ::Bluetooth:
            {
        case Type ::Bluetooth: {
#if defined(TARGET_RT1051)
                inst = std::make_unique<bsp::RT1051BluetoothAudio>(callback);
            inst = nullptr;

#elif defined(TARGET_Linux)

#else
#error "Unsupported target"
#endif
            }
                break;
        } break;

            case Type::Cellular:
            {
        case Type::Cellular: {
#if defined(TARGET_RT1051)
                inst = std::make_unique<bsp::RT1051CellularAudio>(callback);
            inst = std::make_unique<bsp::RT1051CellularAudio>(callback);
#elif defined(TARGET_Linux)
                inst = std::make_unique<bsp::LinuxCellularAudio>(callback );
            inst = std::make_unique<bsp::LinuxCellularAudio>(callback);
#else
#error "Unsupported target"
#endif
            }
                break;

        } break;
        }

        if(inst->isInitialized){
        if (inst->isInitialized) {
            return inst;
        }

        return {};
    }

}
} // namespace bsp

M module-bsp/bsp/audio/bsp_audio.hpp => module-bsp/bsp/audio/bsp_audio.hpp +1 -4
@@ 9,7 9,7 @@
namespace bsp
{

    class AudioDevice
    class AudioDevice : public audio::IOProxy
    {

      public:


@@ 140,9 140,6 @@ namespace bsp
            return callback;
        }

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

      protected:
        Format currentFormat;
        audioCallback_t callback = nullptr;

M module-bsp/targets/Target_RT1051.cmake => module-bsp/targets/Target_RT1051.cmake +1 -1
@@ 58,7 58,7 @@ set(BOARD_SOURCES ${BOARD_SOURCES}
	"${CMAKE_CURRENT_SOURCE_DIR}/board/rt1051/bsp/battery-charger/battery_charger.cpp"
	"${CMAKE_CURRENT_SOURCE_DIR}/board/rt1051/bsp/audio/RT1051Audiocodec.cpp"
	"${CMAKE_CURRENT_SOURCE_DIR}/board/rt1051/bsp/audio/RT1051CellularAudio.cpp"
	"${CMAKE_CURRENT_SOURCE_DIR}/board/rt1051/bsp/audio/RT1051BluetoothAudio.cpp"
	"${CMAKE_CURRENT_SOURCE_DIR}/board/rt1051/bsp/audio/SAIAudioDevice.cpp"
	"${CMAKE_CURRENT_SOURCE_DIR}/board/rt1051/bsp/audio/CodecMAX98090.cpp"
	"${CMAKE_CURRENT_SOURCE_DIR}/board/rt1051/bsp/audio/qfilter.c"
	"${USB_SRC}"

M module-sys/Service/Worker.cpp => module-sys/Service/Worker.cpp +5 -0
@@ 237,6 237,11 @@ namespace sys
        return getControlQueue().Enqueue(&messageToSend, portMAX_DELAY);
    }

    bool Worker::sendCommand(WorkerCommand command)
    {
        return getServiceQueue().Enqueue(&command);
    }

    bool Worker::send(uint32_t cmd, uint32_t *data)
    {
        assert(xTaskGetCurrentTaskHandle() == runnerTask);

M module-sys/Service/Worker.hpp => module-sys/Service/Worker.hpp +3 -1
@@ 98,7 98,6 @@ namespace sys
        std::optional<size_t> controlQueueIndex;
        std::optional<size_t> serviceQueueIndex;
        WorkerQueue &getControlQueue() const;
        WorkerQueue &getServiceQueue() const;

        static constexpr std::size_t controlMessagesCount = static_cast<std::size_t>(ControlMessage::MessageCount);
        static constexpr std::size_t defaultStackSize     = 2048;


@@ 117,10 116,13 @@ namespace sys
      protected:
        virtual bool handleMessage(uint32_t queueID) = 0;

        WorkerQueue &getServiceQueue() const;

        xQueueHandle getQueueHandleByName(const std::string &qname) const;
        std::shared_ptr<WorkerQueue> getQueueByName(const std::string &qname) const;

        bool sendControlMessage(ControlMessage message);
        bool sendCommand(WorkerCommand command);
        State getState() const;

        const static uint32_t SERVICE_QUEUE_LENGTH = 10;