~aleteoryx/muditaos

a3e512a89b698b4d8a8f8046521c327ab2eec147 — Marcin Smoczyński 4 years ago 5913999
[EGD-6674] Change negotiation of audio block size

Replace faulty implementation which produced ambiguous results with one
which is suited for both phone calls and music playback requirements,
including A2DP and HSP Bluetooth profiles.

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

M module-audio/Audio/AudioDevice.hpp
M module-audio/Audio/AudioFormat.cpp
M module-audio/Audio/AudioFormat.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/RecorderOperation.cpp
M module-audio/Audio/Operation/RouterOperation.cpp
M module-audio/Audio/Operation/RouterOperation.hpp
M module-audio/Audio/Profiles/Profile.cpp
M module-audio/Audio/Profiles/Profile.hpp
M module-audio/Audio/Profiles/ProfileIdle.hpp
M module-audio/Audio/Profiles/ProfilePlaybackBluetoothA2DP.hpp
M module-audio/Audio/Profiles/ProfilePlaybackHeadphones.hpp
M module-audio/Audio/Profiles/ProfilePlaybackLoudspeaker.hpp
M module-audio/Audio/Profiles/ProfileRecordingBluetoothHSP.hpp
M module-audio/Audio/Profiles/ProfileRecordingHeadphones.hpp
M module-audio/Audio/Profiles/ProfileRecordingOnBoardMic.hpp
M module-audio/Audio/Profiles/ProfileRoutingBluetoothHSP.hpp
M module-audio/Audio/Profiles/ProfileRoutingEarspeaker.hpp
M module-audio/Audio/Profiles/ProfileRoutingHeadphones.hpp
M module-audio/Audio/Profiles/ProfileRoutingLoudspeaker.hpp
M module-audio/Audio/StreamFactory.cpp
M module-audio/Audio/StreamFactory.hpp
M module-audio/Audio/decoder/Decoder.cpp
M module-audio/Audio/decoder/Decoder.hpp
A module-audio/Audio/test/MockEndpoint.hpp
M module-audio/Audio/test/TestEndpoint.cpp
M module-audio/Audio/test/TestEndpoint.hpp
M module-audio/Audio/test/unittest_format.cpp
M module-audio/Audio/test/unittest_stream.cpp
M module-audio/board/rt1051/RT1051AudioCodec.cpp
M module-audio/board/rt1051/RT1051AudioCodec.hpp
M module-audio/board/rt1051/RT1051CellularAudio.cpp
M module-audio/board/rt1051/RT1051CellularAudio.hpp
M module-audio/board/rt1051/SAIAudioDevice.cpp
M module-audio/board/rt1051/SAIAudioDevice.hpp
M module-bluetooth/Bluetooth/audio/BluetoothAudioDevice.cpp
M module-bluetooth/Bluetooth/audio/BluetoothAudioDevice.hpp
M module-audio/Audio/AudioDevice.hpp => module-audio/Audio/AudioDevice.hpp +10 -16
@@ 54,7 54,7 @@ namespace audio
            None
        };

        using Format = struct
        using Configuration = 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 */


@@ 65,22 65,16 @@ namespace audio
            OutputPath outputPath  = OutputPath::None;
        };

        AudioDevice() = default;
        explicit AudioDevice(const audio::Endpoint::Capabilities &sourceCaps,
                             const audio::Endpoint::Capabilities &sinkCaps)
            : IOProxy(sourceCaps, sinkCaps)
        {}

        virtual ~AudioDevice() = default;

        virtual RetCode Start(const Format &format) = 0;
        virtual RetCode Stop()                      = 0;
        virtual RetCode Start(const Configuration &format) = 0;
        virtual RetCode Stop()                             = 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;
        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 Configuration &format) = 0;

        float GetOutputVolume() const noexcept
        {


@@ 102,13 96,13 @@ namespace audio
            return currentFormat.inputPath;
        }

        Format GetCurrentFormat() const noexcept
        Configuration GetCurrentFormat() const noexcept
        {
            return currentFormat;
        }

      protected:
        Format currentFormat;
        Configuration currentFormat;

        bool isInitialized = false;
    };

M module-audio/Audio/AudioFormat.cpp => module-audio/Audio/AudioFormat.cpp +17 -0
@@ 3,6 3,8 @@

#include "AudioFormat.hpp"

#include <integer.hpp>

using audio::AudioFormat;

auto AudioFormat::getSampleRate() const noexcept -> unsigned int


@@ 87,3 89,18 @@ auto AudioFormat::isNull() const noexcept -> bool
{
    return operator==(audio::nullFormat);
}

auto AudioFormat::bytesToMicroseconds(unsigned int bytes) const noexcept -> std::chrono::microseconds
{
    auto bytesPerSecond = getBitrate() / utils::integer::BitsInByte;
    auto duration       = std::chrono::duration<double>(static_cast<double>(bytes) / bytesPerSecond);

    return std::chrono::duration_cast<std::chrono::microseconds>(duration);
}

auto AudioFormat::microsecondsToBytes(std::chrono::microseconds duration) const noexcept -> unsigned int
{
    auto bytesPerSecond = getBitrate() / utils::integer::BitsInByte;
    auto seconds        = std::chrono::duration<double>(duration).count();
    return static_cast<unsigned int>(seconds * bytesPerSecond);
}

M module-audio/Audio/AudioFormat.hpp => module-audio/Audio/AudioFormat.hpp +4 -0
@@ 3,6 3,7 @@

#pragma once

#include <chrono>
#include <set>
#include <string>
#include <vector>


@@ 30,6 31,9 @@ namespace audio
        auto getBitrate() const noexcept -> unsigned long int;
        auto toString() const -> std::string;

        auto bytesToMicroseconds(unsigned int bytes) const noexcept -> std::chrono::microseconds;
        auto microsecondsToBytes(std::chrono::microseconds duration) const noexcept -> unsigned int;

        auto operator==(const AudioFormat &other) const -> bool;
        auto operator!=(const AudioFormat &other) const -> bool;
        auto operator>(const AudioFormat &other) const -> bool;

M module-audio/Audio/Endpoint.cpp => module-audio/Audio/Endpoint.cpp +10 -22
@@ 8,17 8,14 @@

#include <cassert> // assert

using namespace audio;

Endpoint::Endpoint(const Capabilities &caps) : _caps(caps)
{}

const Endpoint::Capabilities &Endpoint::getCapabilities() const noexcept
{
    return _caps;
}

void Endpoint::connectStream(Stream &stream)
using audio::AbstractStream;
using audio::Endpoint;
using audio::IOProxy;
using audio::Sink;
using audio::Source;
using audio::StreamConnection;

void Endpoint::connectStream(AbstractStream &stream)
{
    assert(_stream == nullptr);
    _stream = &stream;


@@ 41,16 38,7 @@ auto Endpoint::isFormatSupported(const AudioFormat &format) -> bool
    return std::find(std::begin(formats), std::end(formats), format) != std::end(formats);
}

Sink::Sink(const Capabilities &caps) : Endpoint(caps)
{}

Source::Source(const Capabilities &caps) : Endpoint(caps)
{}

IOProxy::IOProxy(const Capabilities &sourceCaps, const Capabilities &sinkCaps) : Source(sourceCaps), Sink(sinkCaps)
{}

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


@@ 114,7 102,7 @@ Sink *StreamConnection::getSink() const noexcept
    return _sink;
}

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

M module-audio/Audio/Endpoint.hpp => module-audio/Audio/Endpoint.hpp +20 -28
@@ 3,9 3,11 @@

#pragma once

#include "Stream.hpp"
#include "AbstractStream.hpp"
#include "AudioFormat.hpp"

#include <chrono>
#include <optional>
#include <vector>

#include <cstdint>


@@ 15,36 17,33 @@ namespace audio
    class Endpoint
    {
      public:
        struct Capabilities
        struct Traits
        {
            bool usesDMA             = false;
            std::size_t minBlockSize = 1;
            std::size_t maxBlockSize = SIZE_MAX;
            bool usesDMA = false;

            // optional constraints
            std::optional<std::size_t> blockSizeConstraint          = std::nullopt;
            std::optional<std::chrono::milliseconds> timeConstraint = std::nullopt;
        };

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

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

        [[nodiscard]] const Capabilities &getCapabilities() const noexcept;
        [[nodiscard]] virtual auto getTraits() const -> Traits = 0;

        auto isFormatSupported(const AudioFormat &format) -> bool;
        virtual auto getSupportedFormats() -> const std::vector<AudioFormat> & = 0;

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

    class Sink : public Endpoint
    {
      public:
        Sink() = default;
        explicit Sink(const Capabilities &caps);

        virtual void onDataSend()    = 0;
        virtual void enableOutput()  = 0;
        virtual void disableOutput() = 0;


@@ 53,9 52,6 @@ namespace audio
    class Source : public Endpoint
    {
      public:
        Source() = default;
        explicit Source(const Capabilities &caps);

        virtual auto getSourceFormat() -> AudioFormat;
        virtual void onDataReceive() = 0;
        virtual void enableInput()   = 0;


@@ 65,9 61,6 @@ namespace audio
    class IOProxy : public Source, public Sink
    {
      public:
        IOProxy() = default;
        IOProxy(const Capabilities &sourceCaps, const Capabilities &sinkCaps);

        inline bool isSinkConnected() const noexcept
        {
            return Sink::isConnected();


@@ 78,12 71,12 @@ namespace audio
            return Source::isConnected();
        }

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

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


@@ 103,7 96,7 @@ namespace audio
    {
      public:
        StreamConnection() = default;
        StreamConnection(Source *source, Sink *sink, Stream *stream);
        StreamConnection(Source *source, Sink *sink, AbstractStream *stream);
        ~StreamConnection();

        void enable();


@@ 112,14 105,13 @@ namespace audio

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

        [[nodiscard]] AbstractStream *getStream() const noexcept;
        [[nodiscard]] bool isEnabled() const noexcept;

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

M module-audio/Audio/Operation/PlaybackOperation.cpp => module-audio/Audio/Operation/PlaybackOperation.cpp +9 -3
@@ 53,8 53,14 @@ namespace audio
        }

        // create stream
        StreamFactory streamFactory(playbackCapabilities, playbackBufferingSize);
        dataStreamOut = streamFactory.makeStream(*dec.get(), *audioDevice.get());
        StreamFactory streamFactory(playbackTimeConstraint);
        try {
            dataStreamOut = streamFactory.makeStream(*dec, *audioDevice, currentProfile->getAudioFormat());
        }
        catch (std::invalid_argument &e) {
            LOG_FATAL("Cannot create audio stream: %s", e.what());
            return audio::RetCode::Failed;
        }

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


@@ 63,7 69,7 @@ namespace audio
        dec->startDecodingWorker(endOfFileCallback);

        // start output device and enable audio connection
        auto ret = audioDevice->Start(currentProfile->GetAudioFormat());
        auto ret = audioDevice->Start(currentProfile->GetAudioConfiguration());
        outputConnection->enable();

        // update state and token

M module-audio/Audio/Operation/PlaybackOperation.hpp => module-audio/Audio/Operation/PlaybackOperation.hpp +4 -6
@@ 10,6 10,9 @@
#include "Audio/StreamQueuedEventsListener.hpp"
#include "Audio/decoder/Decoder.hpp"

#include <chrono>
using namespace std::chrono_literals;

namespace audio::playbackDefaults
{
    constexpr audio::Volume defaultLoudspeakerVolume = 10;


@@ 39,12 42,7 @@ namespace audio
        Position GetPosition() final;

      private:
        // these values are tightly connected to the Bluetooth A2DP update interval
        static constexpr auto playbackBufferingSize = 8U;
        static constexpr auto minimumBlockSize      = 512U;
        static constexpr auto maximumBlockSize      = 512U;
        static constexpr Endpoint::Capabilities playbackCapabilities{.minBlockSize = minimumBlockSize,
                                                                     .maxBlockSize = maximumBlockSize};
        static constexpr auto playbackTimeConstraint = 10ms;

        std::unique_ptr<Stream> dataStreamOut;
        std::unique_ptr<Decoder> dec;

M module-audio/Audio/Operation/RecorderOperation.cpp => module-audio/Audio/Operation/RecorderOperation.cpp +3 -3
@@ 85,8 85,8 @@ namespace audio
        operationToken = token;
        state          = State::Active;

        if (audioDevice->IsFormatSupported(currentProfile->GetAudioFormat())) {
            auto ret = audioDevice->Start(currentProfile->GetAudioFormat());
        if (audioDevice->IsFormatSupported(currentProfile->GetAudioConfiguration())) {
            auto ret = audioDevice->Start(currentProfile->GetAudioConfiguration());
            return GetDeviceError(ret);
        }
        else {


@@ 124,7 124,7 @@ namespace audio
        }

        state    = State::Active;
        auto ret = audioDevice->Start(currentProfile->GetAudioFormat());
        auto ret = audioDevice->Start(currentProfile->GetAudioConfiguration());
        return GetDeviceError(ret);
    }


M module-audio/Audio/Operation/RouterOperation.cpp => module-audio/Audio/Operation/RouterOperation.cpp +16 -7
@@ 51,28 51,37 @@ namespace audio
        state          = State::Active;

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

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

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

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

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

        // create audio connections
        inputConnection =

M module-audio/Audio/Operation/RouterOperation.hpp => module-audio/Audio/Operation/RouterOperation.hpp +7 -6
@@ 14,13 14,16 @@

#include <mutex.hpp>

#include <memory>
#include <chrono>
#include <functional>
#include <memory>
#include <string>
#include <vector>

#include <cstdint>

using namespace std::chrono_literals;

namespace audio
{
    class RouterOperation : public Operation


@@ 56,11 59,9 @@ namespace audio
        Position GetPosition() final;

      private:
        static constexpr auto minimumBlockSize = 64U;
        static constexpr auto maximumBlockSize = 64U;
        static constexpr Endpoint::Capabilities routerCapabilities{.minBlockSize = minimumBlockSize,
                                                                   .maxBlockSize = maximumBlockSize};
        Mute mute = Mute::Disabled;
        static constexpr auto callTimeConstraint = 2ms;

        Mute mute           = Mute::Disabled;
        JackState jackState = JackState::Unplugged;

        void Mute();

M module-audio/Audio/Profiles/Profile.cpp => module-audio/Audio/Profiles/Profile.cpp +8 -8
@@ 79,39 79,39 @@ namespace audio

    Profile::Profile(const std::string &name,
                     const Type type,
                     const AudioDevice::Format &fmt,
                     const AudioDevice::Configuration &fmt,
                     AudioDevice::Type devType)
        : audioFormat(fmt), audioDeviceType(devType), name(name), type(type)
        : audioConfiguration(fmt), audioDeviceType(devType), name(name), type(type)
    {}

    void Profile::SetInputGain(Gain gain)
    {
        audioFormat.inputGain = gain;
        audioConfiguration.inputGain = gain;
    }

    void Profile::SetOutputVolume(Volume vol)
    {
        audioFormat.outputVolume = vol;
        audioConfiguration.outputVolume = vol;
    }

    void Profile::SetInputPath(AudioDevice::InputPath path)
    {
        audioFormat.inputPath = path;
        audioConfiguration.inputPath = path;
    }

    void Profile::SetOutputPath(AudioDevice::OutputPath path)
    {
        audioFormat.outputPath = path;
        audioConfiguration.outputPath = path;
    }

    void Profile::SetInOutFlags(uint32_t flags)
    {
        audioFormat.flags = flags;
        audioConfiguration.flags = flags;
    }

    void Profile::SetSampleRate(uint32_t samplerate)
    {
        audioFormat.sampleRate_Hz = samplerate;
        audioConfiguration.sampleRate_Hz = samplerate;
    }

    const std::string str(const Profile::Type &profileType)

M module-audio/Audio/Profiles/Profile.hpp => module-audio/Audio/Profiles/Profile.hpp +21 -10
@@ 60,32 60,32 @@ namespace audio

        Volume GetOutputVolume() const
        {
            return audioFormat.outputVolume;
            return audioConfiguration.outputVolume;
        }

        Gain GetInputGain() const
        {
            return audioFormat.inputGain;
            return audioConfiguration.inputGain;
        }

        uint32_t GetSampleRate()
        {
            return audioFormat.sampleRate_Hz;
            return audioConfiguration.sampleRate_Hz;
        }

        uint32_t GetInOutFlags()
        {
            return audioFormat.flags;
            return audioConfiguration.flags;
        }

        AudioDevice::OutputPath GetOutputPath() const
        {
            return audioFormat.outputPath;
            return audioConfiguration.outputPath;
        }

        AudioDevice::InputPath GetInputPath() const
        {
            return audioFormat.inputPath;
            return audioConfiguration.inputPath;
        }

        AudioDevice::Type GetAudioDeviceType() const


@@ 93,9 93,17 @@ namespace audio
            return audioDeviceType;
        }

        AudioDevice::Format GetAudioFormat()
        [[deprecated]] AudioDevice::Configuration GetAudioConfiguration()
        {
            return audioFormat;
            return audioConfiguration;
        }

        auto getAudioFormat() const noexcept
        {
            auto isStereo = (audioConfiguration.flags & static_cast<uint32_t>(AudioDevice::Flags::OutputStereo)) != 0 ||
                            (audioConfiguration.flags & static_cast<uint32_t>(AudioDevice::Flags::InputStereo)) != 0;
            auto channels = isStereo ? 2U : 1U;
            return AudioFormat(audioConfiguration.sampleRate_Hz, audioConfiguration.bitWidth, channels);
        }

        const std::string &GetName() const


@@ 109,9 117,12 @@ namespace audio
        }

      protected:
        Profile(const std::string &name, const Type type, const AudioDevice::Format &fmt, AudioDevice::Type devType);
        Profile(const std::string &name,
                const Type type,
                const AudioDevice::Configuration &fmt,
                AudioDevice::Type devType);

        AudioDevice::Format audioFormat{};
        AudioDevice::Configuration audioConfiguration{};
        AudioDevice::Type audioDeviceType = AudioDevice::Type::Audiocodec;

        std::string name;

M module-audio/Audio/Profiles/ProfileIdle.hpp => module-audio/Audio/Profiles/ProfileIdle.hpp +1 -1
@@ 11,7 11,7 @@ namespace audio
    class ProfileIdle : public Profile
    {
      public:
        ProfileIdle() : Profile("Idle", Type::Idle, AudioDevice::Format{}, AudioDevice::Type::None)
        ProfileIdle() : Profile("Idle", Type::Idle, AudioDevice::Configuration{}, AudioDevice::Type::None)
        {}
    };


M module-audio/Audio/Profiles/ProfilePlaybackBluetoothA2DP.hpp => module-audio/Audio/Profiles/ProfilePlaybackBluetoothA2DP.hpp +7 -7
@@ 14,13 14,13 @@ namespace audio
        ProfilePlaybackBluetoothA2DP(Volume volume)
            : Profile("Playback Bluetooth A2DP",
                      Type::PlaybackBluetoothA2DP,
                      AudioDevice::Format{.sampleRate_Hz = 44100,
                                          .bitWidth      = 16,
                                          .flags         = 0,
                                          .outputVolume  = static_cast<float>(volume),
                                          .inputGain     = 0,
                                          .inputPath     = AudioDevice::InputPath::None,
                                          .outputPath    = AudioDevice::OutputPath::None},
                      AudioDevice::Configuration{.sampleRate_Hz = 44100,
                                                 .bitWidth      = 16,
                                                 .flags         = 0,
                                                 .outputVolume  = static_cast<float>(volume),
                                                 .inputGain     = 0,
                                                 .inputPath     = AudioDevice::InputPath::None,
                                                 .outputPath    = AudioDevice::OutputPath::None},
                      AudioDevice::Type::Bluetooth)
        {}
    };

M module-audio/Audio/Profiles/ProfilePlaybackHeadphones.hpp => module-audio/Audio/Profiles/ProfilePlaybackHeadphones.hpp +7 -7
@@ 13,13 13,13 @@ namespace audio
        ProfilePlaybackHeadphones(Volume volume)
            : Profile("Playback Headphones",
                      Type::PlaybackHeadphones,
                      AudioDevice::Format{.sampleRate_Hz = 0,
                                          .bitWidth      = 16,
                                          .flags         = 0,
                                          .outputVolume  = static_cast<float>(volume),
                                          .inputGain     = 0,
                                          .inputPath     = AudioDevice::InputPath::None,
                                          .outputPath    = AudioDevice::OutputPath::Headphones},
                      AudioDevice::Configuration{.sampleRate_Hz = 0,
                                                 .bitWidth      = 16,
                                                 .flags         = 0,
                                                 .outputVolume  = static_cast<float>(volume),
                                                 .inputGain     = 0,
                                                 .inputPath     = AudioDevice::InputPath::None,
                                                 .outputPath    = AudioDevice::OutputPath::Headphones},
                      AudioDevice::Type::Audiocodec)
        {}
    };

M module-audio/Audio/Profiles/ProfilePlaybackLoudspeaker.hpp => module-audio/Audio/Profiles/ProfilePlaybackLoudspeaker.hpp +7 -7
@@ 14,13 14,13 @@ namespace audio
        ProfilePlaybackLoudspeaker(Volume volume)
            : Profile("Playback Loudspeaker",
                      Type::PlaybackLoudspeaker,
                      AudioDevice::Format{.sampleRate_Hz = 0,
                                          .bitWidth      = 16,
                                          .flags         = 0,
                                          .outputVolume  = static_cast<float>(volume),
                                          .inputGain     = 0,
                                          .inputPath     = AudioDevice::InputPath::None,
                                          .outputPath    = AudioDevice::OutputPath::Loudspeaker},
                      AudioDevice::Configuration{.sampleRate_Hz = 0,
                                                 .bitWidth      = 16,
                                                 .flags         = 0,
                                                 .outputVolume  = static_cast<float>(volume),
                                                 .inputGain     = 0,
                                                 .inputPath     = AudioDevice::InputPath::None,
                                                 .outputPath    = AudioDevice::OutputPath::Loudspeaker},
                      AudioDevice::Type::Audiocodec)
        {}
    };

M module-audio/Audio/Profiles/ProfileRecordingBluetoothHSP.hpp => module-audio/Audio/Profiles/ProfileRecordingBluetoothHSP.hpp +9 -8
@@ 14,14 14,15 @@ namespace audio
        ProfileRecordingBluetoothHSP(Gain gain)
            : Profile("Recording Bluetooth HSP",
                      Type::RecordingHeadphones,
                      AudioDevice::Format{.sampleRate_Hz = 8000,
                                          .bitWidth      = 16,
                                          .flags         = static_cast<uint32_t>(
                                              AudioDevice::Flags::InputLeft), // microphone use left audio channel
                                          .outputVolume = 0,
                                          .inputGain    = static_cast<float>(gain),
                                          .inputPath    = AudioDevice::InputPath::None,
                                          .outputPath   = AudioDevice::OutputPath::None},
                      AudioDevice::Configuration{
                          .sampleRate_Hz = 8000,
                          .bitWidth      = 16,
                          .flags =
                              static_cast<uint32_t>(AudioDevice::Flags::InputLeft), // microphone use left audio channel
                          .outputVolume = 0,
                          .inputGain    = static_cast<float>(gain),
                          .inputPath    = AudioDevice::InputPath::None,
                          .outputPath   = AudioDevice::OutputPath::None},
                      AudioDevice::Type::Bluetooth)
        {}
    };

M module-audio/Audio/Profiles/ProfileRecordingHeadphones.hpp => module-audio/Audio/Profiles/ProfileRecordingHeadphones.hpp +9 -8
@@ 13,14 13,15 @@ namespace audio
        ProfileRecordingHeadphones(Gain gain)
            : Profile("Recording Headset",
                      Type::RecordingHeadphones,
                      AudioDevice::Format{.sampleRate_Hz = 44100,
                                          .bitWidth      = 16,
                                          .flags         = static_cast<uint32_t>(
                                              AudioDevice::Flags::InputLeft), // microphone use left audio channel
                                          .outputVolume = 0,
                                          .inputGain    = static_cast<float>(gain),
                                          .inputPath    = AudioDevice::InputPath::Headphones,
                                          .outputPath   = AudioDevice::OutputPath::None},
                      AudioDevice::Configuration{
                          .sampleRate_Hz = 44100,
                          .bitWidth      = 16,
                          .flags =
                              static_cast<uint32_t>(AudioDevice::Flags::InputLeft), // microphone use left audio channel
                          .outputVolume = 0,
                          .inputGain    = static_cast<float>(gain),
                          .inputPath    = AudioDevice::InputPath::Headphones,
                          .outputPath   = AudioDevice::OutputPath::None},
                      AudioDevice::Type::Audiocodec)
        {}
    };

M module-audio/Audio/Profiles/ProfileRecordingOnBoardMic.hpp => module-audio/Audio/Profiles/ProfileRecordingOnBoardMic.hpp +9 -8
@@ 13,14 13,15 @@ namespace audio
        ProfileRecordingOnBoardMic(Gain gain)
            : Profile("Recording On Board Microphone",
                      Type::RecordingBuiltInMic,
                      AudioDevice::Format{.sampleRate_Hz = 44100,
                                          .bitWidth      = 16,
                                          .flags         = static_cast<uint32_t>(
                                              AudioDevice::Flags::InputLeft), // microphone use left audio channel
                                          .outputVolume = 0,
                                          .inputGain    = static_cast<float>(gain),
                                          .inputPath    = AudioDevice::InputPath::Microphone,
                                          .outputPath   = AudioDevice::OutputPath::None},
                      AudioDevice::Configuration{
                          .sampleRate_Hz = 44100,
                          .bitWidth      = 16,
                          .flags =
                              static_cast<uint32_t>(AudioDevice::Flags::InputLeft), // microphone use left audio channel
                          .outputVolume = 0,
                          .inputGain    = static_cast<float>(gain),
                          .inputPath    = AudioDevice::InputPath::Microphone,
                          .outputPath   = AudioDevice::OutputPath::None},
                      AudioDevice::Type::Audiocodec)
        {}
    };

M module-audio/Audio/Profiles/ProfileRoutingBluetoothHSP.hpp => module-audio/Audio/Profiles/ProfileRoutingBluetoothHSP.hpp +13 -13
@@ 11,19 11,19 @@ namespace audio
    {
      public:
        ProfileRoutingBluetoothHSP(Volume volume, Gain gain)
            : Profile(
                  "Routing Bluetooth HSP",
                  Type::RoutingBluetoothHSP,
                  AudioDevice::Format{.sampleRate_Hz = 8000,
                                      .bitWidth      = 16,
                                      .flags         = static_cast<uint32_t>(
                                                   AudioDevice::Flags::InputLeft) | // microphone use left audio channel
                                               static_cast<uint32_t>(AudioDevice::Flags::OutputMono),
                                      .outputVolume = static_cast<float>(volume),
                                      .inputGain    = static_cast<float>(gain),
                                      .inputPath    = AudioDevice::InputPath::None,
                                      .outputPath   = AudioDevice::OutputPath::None},
                  AudioDevice::Type::Bluetooth)
            : Profile("Routing Bluetooth HSP",
                      Type::RoutingBluetoothHSP,
                      AudioDevice::Configuration{
                          .sampleRate_Hz = 8000,
                          .bitWidth      = 16,
                          .flags         = static_cast<uint32_t>(
                                       AudioDevice::Flags::InputLeft) | // microphone use left audio channel
                                   static_cast<uint32_t>(AudioDevice::Flags::OutputMono),
                          .outputVolume = static_cast<float>(volume),
                          .inputGain    = static_cast<float>(gain),
                          .inputPath    = AudioDevice::InputPath::None,
                          .outputPath   = AudioDevice::OutputPath::None},
                      AudioDevice::Type::Bluetooth)
        {}
    };


M module-audio/Audio/Profiles/ProfileRoutingEarspeaker.hpp => module-audio/Audio/Profiles/ProfileRoutingEarspeaker.hpp +13 -13
@@ 11,19 11,19 @@ namespace audio
    {
      public:
        ProfileRoutingEarspeaker(Volume volume, Gain gain)
            : Profile(
                  "Routing Earspeaker",
                  Type::RoutingEarspeaker,
                  AudioDevice::Format{.sampleRate_Hz = 16000,
                                      .bitWidth      = 16,
                                      .flags         = static_cast<uint32_t>(
                                                   AudioDevice::Flags::InputLeft) | // microphone use left audio channel
                                               static_cast<uint32_t>(AudioDevice::Flags::OutputMono),
                                      .outputVolume = static_cast<float>(volume),
                                      .inputGain    = static_cast<float>(gain),
                                      .inputPath    = AudioDevice::InputPath::Microphone,
                                      .outputPath   = AudioDevice::OutputPath::Earspeaker},
                  AudioDevice::Type::Audiocodec)
            : Profile("Routing Earspeaker",
                      Type::RoutingEarspeaker,
                      AudioDevice::Configuration{
                          .sampleRate_Hz = 16000,
                          .bitWidth      = 16,
                          .flags         = static_cast<uint32_t>(
                                       AudioDevice::Flags::InputLeft) | // microphone use left audio channel
                                   static_cast<uint32_t>(AudioDevice::Flags::OutputMono),
                          .outputVolume = static_cast<float>(volume),
                          .inputGain    = static_cast<float>(gain),
                          .inputPath    = AudioDevice::InputPath::Microphone,
                          .outputPath   = AudioDevice::OutputPath::Earspeaker},
                      AudioDevice::Type::Audiocodec)
        {}
    };


M module-audio/Audio/Profiles/ProfileRoutingHeadphones.hpp => module-audio/Audio/Profiles/ProfileRoutingHeadphones.hpp +13 -13
@@ 11,19 11,19 @@ namespace audio
    {
      public:
        ProfileRoutingHeadphones(Volume volume, Gain gain)
            : Profile(
                  "Routing Headset",
                  Type::RoutingHeadphones,
                  AudioDevice::Format{.sampleRate_Hz = 16000,
                                      .bitWidth      = 16,
                                      .flags         = static_cast<uint32_t>(
                                                   AudioDevice::Flags::InputLeft) | // microphone use left audio channel
                                               static_cast<uint32_t>(AudioDevice::Flags::OutputMono),
                                      .outputVolume = static_cast<float>(volume),
                                      .inputGain    = static_cast<float>(gain),
                                      .inputPath    = AudioDevice::InputPath::Headphones,
                                      .outputPath   = AudioDevice::OutputPath::Headphones},
                  AudioDevice::Type::Audiocodec)
            : Profile("Routing Headset",
                      Type::RoutingHeadphones,
                      AudioDevice::Configuration{
                          .sampleRate_Hz = 16000,
                          .bitWidth      = 16,
                          .flags         = static_cast<uint32_t>(
                                       AudioDevice::Flags::InputLeft) | // microphone use left audio channel
                                   static_cast<uint32_t>(AudioDevice::Flags::OutputMono),
                          .outputVolume = static_cast<float>(volume),
                          .inputGain    = static_cast<float>(gain),
                          .inputPath    = AudioDevice::InputPath::Headphones,
                          .outputPath   = AudioDevice::OutputPath::Headphones},
                      AudioDevice::Type::Audiocodec)
        {}
    };


M module-audio/Audio/Profiles/ProfileRoutingLoudspeaker.hpp => module-audio/Audio/Profiles/ProfileRoutingLoudspeaker.hpp +13 -13
@@ 11,19 11,19 @@ namespace audio
    {
      public:
        ProfileRoutingLoudspeaker(Volume volume, Gain gain)
            : Profile(
                  "Routing Speakerphone",
                  Type::RoutingLoudspeaker,
                  AudioDevice::Format{.sampleRate_Hz = 16000,
                                      .bitWidth      = 16,
                                      .flags         = static_cast<uint32_t>(
                                                   AudioDevice::Flags::InputLeft) | // microphone use left audio channel
                                               static_cast<uint32_t>(AudioDevice::Flags::OutputMono),
                                      .outputVolume = static_cast<float>(volume),
                                      .inputGain    = static_cast<float>(gain),
                                      .inputPath    = AudioDevice::InputPath::Microphone,
                                      .outputPath   = AudioDevice::OutputPath::Loudspeaker},
                  AudioDevice::Type::Audiocodec)
            : Profile("Routing Speakerphone",
                      Type::RoutingLoudspeaker,
                      AudioDevice::Configuration{
                          .sampleRate_Hz = 16000,
                          .bitWidth      = 16,
                          .flags         = static_cast<uint32_t>(
                                       AudioDevice::Flags::InputLeft) | // microphone use left audio channel
                                   static_cast<uint32_t>(AudioDevice::Flags::OutputMono),
                          .outputVolume = static_cast<float>(volume),
                          .inputGain    = static_cast<float>(gain),
                          .inputPath    = AudioDevice::InputPath::Microphone,
                          .outputPath   = AudioDevice::OutputPath::Loudspeaker},
                      AudioDevice::Type::Audiocodec)
        {}
    };


M module-audio/Audio/StreamFactory.cpp => module-audio/Audio/StreamFactory.cpp +75 -22
@@ 7,52 7,105 @@
#include <math/Math.hpp>

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

#include <cassert>
#include <cmath>

using namespace audio;
using audio::Sink;
using audio::Source;
using audio::Stream;
using audio::StreamFactory;
using namespace std::chrono_literals;

StreamFactory::StreamFactory(Endpoint::Capabilities factoryCaps, unsigned int bufferingSize)
    : caps(std::move(factoryCaps)), bufferingSize(bufferingSize)
StreamFactory::StreamFactory(std::chrono::milliseconds operationPeriodRequirement)
    : periodRequirement(operationPeriodRequirement)
{}

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

    return std::make_unique<Stream>(
        format, getAllocator(negotiatedCaps.usesDMA), negotiatedCaps.maxBlockSize, bufferingSize);
    if (streamFormat == audio::nullFormat) {
        throw std::invalid_argument("No source format provided");
    }

    if (!blockSizeConstraint.has_value()) {
        blockSizeConstraint = binary::ceilPowerOfTwo(streamFormat.microsecondsToBytes(timingConstraint));
    }

    auto blockTransferDuration =
        std::chrono::duration<double, std::milli>(streamFormat.bytesToMicroseconds(blockSizeConstraint.value()));
    streamBuffering = static_cast<unsigned int>(std::ceil(timingConstraint / blockTransferDuration)) * defaultBuffering;

    LOG_DEBUG("Creating audio stream: block size = %lu; buffering = %u",
              static_cast<unsigned long>(blockSizeConstraint.value()),
              streamBuffering);

    return std::make_unique<Stream>(streamFormat, streamAllocator, blockSizeConstraint.value(), streamBuffering);
}

auto StreamFactory::negotiateCaps(std::vector<std::reference_wrapper<const Endpoint>> v) -> Endpoint::Capabilities
auto StreamFactory::getBlockSizeConstraint(std::initializer_list<audio::Endpoint::Traits> traitsList) const
    -> std::optional<std::size_t>
{
    auto negotiatedCaps = caps;
    std::optional<std::size_t> blockSize = std::nullopt;

    for (const auto &endpointRef : v) {
        auto &endpointCaps = endpointRef.get().getCapabilities();
    for (const auto &traits : traitsList) {
        if (!traits.blockSizeConstraint.has_value()) {
            continue;
        }

        negotiatedCaps.maxBlockSize = std::min(negotiatedCaps.maxBlockSize, endpointCaps.maxBlockSize);
        negotiatedCaps.minBlockSize = std::max(negotiatedCaps.minBlockSize, endpointCaps.minBlockSize);
        negotiatedCaps.usesDMA      = negotiatedCaps.usesDMA || endpointCaps.usesDMA;
        auto blockSizeCandidate = traits.blockSizeConstraint.value();
        if (blockSizeCandidate == 0) {
            throw std::invalid_argument("Invalid stream block size");
        }

        if (!blockSize.has_value()) {
            blockSize = blockSizeCandidate;
        }
        else if (blockSize != blockSizeCandidate) {
            throw std::invalid_argument("Block size mismatch");
        }
    }

    negotiatedCaps.minBlockSize = binary::ceilPowerOfTwo(negotiatedCaps.minBlockSize);
    negotiatedCaps.maxBlockSize = binary::floorPowerOfTwo(negotiatedCaps.maxBlockSize);
    return blockSize;
}

    assert(negotiatedCaps.minBlockSize <= negotiatedCaps.maxBlockSize);
auto StreamFactory::getTimingConstraints(
    std::initializer_list<std::optional<std::chrono::milliseconds>> timingConstraints) const
    -> std::chrono::milliseconds
{
    auto constraint = std::max(timingConstraints, [](const auto &left, const auto &right) {
        return right.value_or(std::chrono::milliseconds(0)).count() >
               left.value_or(std::chrono::milliseconds(0)).count();
    });

    if (!constraint.has_value()) {
        throw std::invalid_argument("At least one timing constraint must be provided");
    }

    return negotiatedCaps;
    return constraint.value();
}

auto StreamFactory::getAllocator(bool usesDMA) -> Stream::Allocator &
auto StreamFactory::negotiateAllocator(std::initializer_list<audio::Endpoint::Traits> traitsList) noexcept
    -> Stream::Allocator &
{
    if (usesDMA) {
    auto mustUseDMA =
        std::any_of(std::begin(traitsList), std::end(traitsList), [](const auto &trait) { return trait.usesDMA; });
    if (mustUseDMA) {
        LOG_DEBUG("Using fast memory allocator for audio streaming");
        return nonCacheableAlloc;
    }
    else {
        LOG_DEBUG("Using standard allocator for audio streaming");
        return stdAlloc;
    }
}

M module-audio/Audio/StreamFactory.hpp => module-audio/Audio/StreamFactory.hpp +15 -8
@@ 6,8 6,9 @@
#include "Endpoint.hpp"
#include "Stream.hpp"

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

namespace audio
{


@@ 15,18 16,24 @@ namespace audio
    class StreamFactory
    {
      public:
        explicit StreamFactory(Endpoint::Capabilities factoryCaps,
                               unsigned int bufferingSize = Stream::defaultBufferingSize);
        auto makeStream(Source &source, Sink &sink) -> std::unique_ptr<Stream>;
        StreamFactory() = default;
        explicit StreamFactory(std::chrono::milliseconds operationPeriodRequirement);
        auto makeStream(Source &source, Sink &sink, AudioFormat streamFormat) -> std::unique_ptr<Stream>;

      private:
        auto negotiateCaps(std::vector<std::reference_wrapper<const Endpoint>> v) -> Endpoint::Capabilities;
        auto getAllocator(bool usesDMA) -> Stream::Allocator &;
        static constexpr auto defaultBuffering = 2U;

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

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

        Endpoint::Capabilities caps;
        StandardStreamAllocator stdAlloc;
        NonCacheableStreamAllocator nonCacheableAlloc;
        unsigned int bufferingSize;
    };

} // namespace audio

M module-audio/Audio/decoder/Decoder.cpp => module-audio/Audio/decoder/Decoder.cpp +6 -3
@@ 14,10 14,8 @@

namespace audio
{

    Decoder::Decoder(const char *fileName)
        : Source(Endpoint::Capabilities{.maxBlockSize = workerBufferSize * sizeof(int16_t)}), filePath(fileName),
          workerBuffer(std::make_unique<int16_t[]>(workerBufferSize)), tag(std::make_unique<Tags>())
        : filePath(fileName), workerBuffer(std::make_unique<int16_t[]>(workerBufferSize)), tag(std::make_unique<Tags>())
    {

        fd = std::fopen(fileName, "r");


@@ 183,4 181,9 @@ namespace audio
        return formats;
    }

    auto Decoder::getTraits() const -> Endpoint::Traits
    {
        return Endpoint::Traits{};
    }

} // namespace audio

M module-audio/Audio/decoder/Decoder.hpp => module-audio/Audio/decoder/Decoder.hpp +4 -2
@@ 110,6 110,8 @@ namespace audio
        auto getSourceFormat() -> AudioFormat override;
        auto getSupportedFormats() -> const std::vector<AudioFormat> & override;

        auto getTraits() const -> Endpoint::Traits override;

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



@@ 122,8 124,8 @@ namespace audio

        void convertmono2stereo(int16_t *pcm, uint32_t samplecount);

        static constexpr auto workerBufferSize              = 1024 * 8;
        static constexpr Endpoint::Capabilities decoderCaps = {.usesDMA = false};
        static constexpr auto workerBufferSize        = 1024 * 8;
        static constexpr Endpoint::Traits decoderCaps = {.usesDMA = false};

        uint32_t sampleRate = 0;
        uint32_t chanNumber = 0;

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

#pragma once

#include <Audio/Endpoint.hpp>

#include <gmock/gmock.h>

namespace testing::audio
{
    class MockSink : public ::audio::Sink
    {
      public:
        MOCK_METHOD(::audio::Endpoint::Traits, getTraits, (), (const, override));
        MOCK_METHOD(const std::vector<::audio::AudioFormat> &, getSupportedFormats, (), (override));
        MOCK_METHOD(void, onDataSend, (), (override));
        MOCK_METHOD(void, enableOutput, (), (override));
        MOCK_METHOD(void, disableOutput, (), (override));
    };

    class MockSource : public ::audio::Source
    {
      public:
        MOCK_METHOD(::audio::Endpoint::Traits, getTraits, (), (const, override));
        MOCK_METHOD(const std::vector<::audio::AudioFormat> &, getSupportedFormats, (), (override));
        MOCK_METHOD(::audio::AudioFormat, getSourceFormat, (), (override));
        MOCK_METHOD(void, onDataReceive, (), (override));
        MOCK_METHOD(void, enableInput, (), (override));
        MOCK_METHOD(void, disableInput, (), (override));
    };

} // namespace testing::audio

M module-audio/Audio/test/TestEndpoint.cpp => module-audio/Audio/test/TestEndpoint.cpp +10 -0
@@ 26,6 26,11 @@ void TestSink::enableOutput()
void TestSink::disableOutput()
{}

auto TestSink::getTraits() const -> ::audio::Endpoint::Traits
{
    return testTraits;
}

TestSource::TestSource(std::vector<AudioFormat> supportedFormats, AudioFormat sourceFormat)
    : formats(std::move(supportedFormats)), sourceFormat(std::move(sourceFormat))
{}


@@ 52,3 57,8 @@ auto TestSource::getSupportedFormats() -> const std::vector<AudioFormat> &
{
    return formats;
}

auto TestSource::getTraits() const -> ::audio::Endpoint::Traits
{
    return testTraits;
}

M module-audio/Audio/test/TestEndpoint.hpp => module-audio/Audio/test/TestEndpoint.hpp +4 -0
@@ 10,6 10,8 @@

namespace audio::test
{
    constexpr inline auto testTraits = ::audio::Endpoint::Traits{};

    class TestSink : public audio::Sink
    {
      public:


@@ 19,6 21,7 @@ namespace audio::test
        void enableOutput() override;
        void disableOutput() override;
        auto getSupportedFormats() -> const std::vector<AudioFormat> & override;
        auto getTraits() const -> ::audio::Endpoint::Traits override;

      private:
        std::vector<AudioFormat> formats;


@@ 35,6 38,7 @@ namespace audio::test
        void enableInput() override;
        void disableInput() override;
        auto getSupportedFormats() -> const std::vector<AudioFormat> & override;
        auto getTraits() const -> ::audio::Endpoint::Traits override;

      private:
        std::vector<AudioFormat> formats;

M module-audio/Audio/test/unittest_format.cpp => module-audio/Audio/test/unittest_format.cpp +16 -0
@@ 79,3 79,19 @@ TEST(AudioFormat, Null)
    EXPECT_FALSE(AudioFormat(44100, 16, 0).isNull());
    EXPECT_FALSE(AudioFormat(44100, 16, 2).isNull());
}

TEST(AudioFormat, DurationToBytes)
{
    auto format = AudioFormat(44100, 16, 2);

    EXPECT_EQ(format.microsecondsToBytes(std::chrono::microseconds(0)), 0);
    EXPECT_EQ(format.microsecondsToBytes(std::chrono::seconds(1)), 176400);
}

TEST(AudioFormat, BytesToDuration)
{
    auto format = AudioFormat(8000, 8, 1);

    EXPECT_EQ(format.bytesToMicroseconds(0).count(), 0);
    EXPECT_EQ(format.bytesToMicroseconds(1).count(), 125);
}

M module-audio/Audio/test/unittest_stream.cpp => module-audio/Audio/test/unittest_stream.cpp +112 -0
@@ 7,7 7,9 @@
#include <Audio/Stream.hpp>
#include <Audio/AudioFormat.hpp>
#include <Audio/StreamProxy.hpp>
#include <Audio/StreamFactory.hpp>

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

#include <cstdint>


@@ 16,10 18,13 @@
using audio::NonCacheableStreamAllocator;
using audio::StandardStreamAllocator;
using audio::Stream;
using audio::StreamFactory;
using testing::Return;
using testing::audio::MockStream;
using testing::audio::MockStreamEventListener;

using namespace std::chrono_literals;

constexpr std::size_t defaultBlockSize = 64U;
constexpr std::size_t defaultBuffering = 4U;
constexpr audio::AudioFormat format    = audio::AudioFormat(44100, 16, 2);


@@ 406,6 411,113 @@ TEST(Proxy, Misc)
    EXPECT_FALSE(proxy.isFull());
}

TEST(Factory, BlockSizeConstraints)
{
    testing::audio::MockSink mockSink;
    testing::audio::MockSource mockSource;
    ::audio::StreamFactory factory(2ms);

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

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

    EXPECT_EQ(stream->getOutputTraits().blockSize, 512U);
    EXPECT_EQ(stream->getInputTraits().blockSize, 512U);

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

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

    EXPECT_EQ(stream->getOutputTraits().blockSize, 512U);
    EXPECT_EQ(stream->getInputTraits().blockSize, 512U);
}

TEST(Factory, BlockSizeErrors)
{
    testing::audio::MockSink mockSink;
    testing::audio::MockSource mockSource;
    ::audio::StreamFactory factory(2ms);

    EXPECT_CALL(mockSource, getTraits).WillRepeatedly(Return(::audio::Endpoint::Traits{.blockSizeConstraint = 128U}));
    EXPECT_CALL(mockSink, getTraits).WillRepeatedly(Return(::audio::Endpoint::Traits{.blockSizeConstraint = 300U}));
    EXPECT_THROW(factory.makeStream(mockSource, mockSink, format), std::invalid_argument);

    EXPECT_CALL(mockSource, getTraits).WillRepeatedly(Return(::audio::Endpoint::Traits{.blockSizeConstraint = 128U}));
    EXPECT_CALL(mockSink, getTraits).WillRepeatedly(Return(::audio::Endpoint::Traits{.blockSizeConstraint = 0}));
    EXPECT_THROW(factory.makeStream(mockSource, mockSink, format), std::invalid_argument);
}

TEST(Factory, TimeContraintsError)
{
    testing::audio::MockSink mockSink;
    testing::audio::MockSource mockSource;
    ::audio::StreamFactory factory;

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

    EXPECT_THROW(factory.makeStream(mockSource, mockSink, format), std::invalid_argument);
}

TEST(Factory, NoSourceFormat)
{
    testing::audio::MockSink mockSink;
    testing::audio::MockSource mockSource;
    ::audio::StreamFactory factory;

    EXPECT_CALL(mockSource, getTraits).WillRepeatedly(Return(::audio::Endpoint::Traits{}));
    EXPECT_CALL(mockSink, getTraits).WillRepeatedly(Return(::audio::Endpoint::Traits{}));
    EXPECT_THROW(factory.makeStream(mockSource, mockSink, ::audio::nullFormat), std::invalid_argument);
}

TEST(Factory, TimeConstraints)
{
    testing::audio::MockSink mockSink;
    testing::audio::MockSource mockSource;
    ::audio::StreamFactory factory(10ms);

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

    auto stream = factory.makeStream(mockSource, mockSink, ::audio::AudioFormat(44100, 16, 2));

    EXPECT_EQ(stream->getBlockCount(), 8);
    EXPECT_EQ(stream->getOutputTraits().blockSize, 512);
}

TEST(Factory, TimeBlockConstraints)
{
    testing::audio::MockSink mockSink;
    testing::audio::MockSource mockSource;
    ::audio::StreamFactory factory(2ms);

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

    auto stream = factory.makeStream(mockSource, mockSink, ::audio::AudioFormat(8000, 16, 1));

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

TEST(Factory, NoConstraints)
{
    testing::audio::MockSink mockSink;
    testing::audio::MockSource mockSource;
    ::audio::StreamFactory factory(2ms);

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

    auto stream = factory.makeStream(mockSource, mockSink, ::audio::AudioFormat(16000, 16, 1));

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

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

M module-audio/board/rt1051/RT1051AudioCodec.cpp => module-audio/board/rt1051/RT1051AudioCodec.cpp +9 -4
@@ 30,7 30,7 @@ namespace audio
        DeinitBsp();
    }

    CodecParamsMAX98090::InputPath RT1051AudioCodec::getCodecInputPath(const AudioDevice::Format &format)
    CodecParamsMAX98090::InputPath RT1051AudioCodec::getCodecInputPath(const AudioDevice::Configuration &format)
    {
        switch (format.inputPath) {
        case AudioDevice::InputPath::Headphones:


@@ 44,7 44,7 @@ namespace audio
        };
    }

    CodecParamsMAX98090::OutputPath RT1051AudioCodec::getCodecOutputPath(const AudioDevice::Format &format)
    CodecParamsMAX98090::OutputPath RT1051AudioCodec::getCodecOutputPath(const AudioDevice::Configuration &format)
    {
        auto mono = (format.flags & static_cast<std::uint32_t>(AudioDevice::Flags::OutputMono)) != 0;



@@ 64,7 64,7 @@ namespace audio
        }
    }

    AudioDevice::RetCode RT1051AudioCodec::Start(const AudioDevice::Format &format)
    AudioDevice::RetCode RT1051AudioCodec::Start(const AudioDevice::Configuration &format)
    {
        cpp_freertos::LockGuard lock(mutex);



@@ 180,7 180,7 @@ namespace audio
        return AudioDevice::RetCode::Success;
    }

    bool RT1051AudioCodec::IsFormatSupported(const AudioDevice::Format &format)
    bool RT1051AudioCodec::IsFormatSupported(const AudioDevice::Configuration &format)
    {

        if (CodecParamsMAX98090::ValToSampleRate(format.sampleRate_Hz) == CodecParamsMAX98090::SampleRate::Invalid) {


@@ 285,6 285,11 @@ namespace audio
        return formats;
    }

    auto RT1051AudioCodec::getTraits() const -> Traits
    {
        return Traits{.usesDMA = true};
    }

    void rxAudioCodecCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData)
    {
        auto self = static_cast<RT1051AudioCodec *>(userData);

M module-audio/board/rt1051/RT1051AudioCodec.hpp => module-audio/board/rt1051/RT1051AudioCodec.hpp +5 -4
@@ 37,14 37,15 @@ namespace audio
        RT1051AudioCodec();
        virtual ~RT1051AudioCodec();

        AudioDevice::RetCode Start(const Format &format) override final;
        AudioDevice::RetCode Start(const Configuration &format) override final;
        AudioDevice::RetCode Stop() override final;
        AudioDevice::RetCode OutputVolumeCtrl(float vol) override final;
        AudioDevice::RetCode InputGainCtrl(float gain) override final;
        AudioDevice::RetCode OutputPathCtrl(OutputPath outputPath) override final;
        AudioDevice::RetCode InputPathCtrl(InputPath inputPath) override final;
        bool IsFormatSupported(const Format &format) override final;
        bool IsFormatSupported(const Configuration &format) override final;
        auto getSupportedFormats() -> const std::vector<AudioFormat> & override final;
        auto getTraits() const -> Traits override final;

        cpp_freertos::MutexStandard mutex;



@@ 85,7 86,7 @@ namespace audio
        void OutStop();
        void InStop();

        CodecParamsMAX98090::InputPath getCodecInputPath(const AudioDevice::Format &format);
        CodecParamsMAX98090::OutputPath getCodecOutputPath(const AudioDevice::Format &format);
        CodecParamsMAX98090::InputPath getCodecInputPath(const AudioDevice::Configuration &format);
        CodecParamsMAX98090::OutputPath getCodecOutputPath(const AudioDevice::Configuration &format);
    };
} // namespace audio

M module-audio/board/rt1051/RT1051CellularAudio.cpp => module-audio/board/rt1051/RT1051CellularAudio.cpp +7 -2
@@ 30,7 30,7 @@ namespace audio
        Deinit();
    }

    AudioDevice::RetCode RT1051CellularAudio::Start(const AudioDevice::Format &format)
    AudioDevice::RetCode RT1051CellularAudio::Start(const AudioDevice::Configuration &format)
    {
        cpp_freertos::LockGuard lock(mutex);



@@ 118,7 118,7 @@ namespace audio
        return AudioDevice::RetCode::Success;
    }

    bool RT1051CellularAudio::IsFormatSupported(const AudioDevice::Format &format)
    bool RT1051CellularAudio::IsFormatSupported(const AudioDevice::Configuration &format)
    {
        return true;
    }


@@ 254,6 254,11 @@ namespace audio
        return formats;
    }

    auto RT1051CellularAudio::getTraits() const -> Traits
    {
        return Traits{.usesDMA = true};
    }

    void rxCellularCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData)
    {
        auto self = static_cast<RT1051CellularAudio *>(userData);

M module-audio/board/rt1051/RT1051CellularAudio.hpp => module-audio/board/rt1051/RT1051CellularAudio.hpp +3 -2
@@ 35,14 35,15 @@ namespace audio
        RT1051CellularAudio();
        virtual ~RT1051CellularAudio();

        AudioDevice::RetCode Start(const Format &format) override final;
        AudioDevice::RetCode Start(const Configuration &format) override final;
        AudioDevice::RetCode Stop() override final;
        AudioDevice::RetCode OutputVolumeCtrl(float vol) override final;
        AudioDevice::RetCode InputGainCtrl(float gain) override final;
        AudioDevice::RetCode OutputPathCtrl(OutputPath outputPath) override final;
        AudioDevice::RetCode InputPathCtrl(InputPath inputPath) override final;
        bool IsFormatSupported(const Format &format) override final;
        bool IsFormatSupported(const Configuration &format) override final;
        auto getSupportedFormats() -> const std::vector<AudioFormat> & override final;
        auto getTraits() const -> Traits override final;

        cpp_freertos::MutexStandard mutex;


M module-audio/board/rt1051/SAIAudioDevice.cpp => module-audio/board/rt1051/SAIAudioDevice.cpp +1 -1
@@ 8,7 8,7 @@
using namespace audio;

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

void SAIAudioDevice::initiateRxTransfer()

M module-audio/board/rt1051/SAIAudioDevice.hpp => module-audio/board/rt1051/SAIAudioDevice.hpp +0 -2
@@ 30,8 30,6 @@ namespace audio
        sai_edma_handle_t *tx = nullptr;
        bool txEnabled        = false;
        bool rxEnabled        = false;

        static constexpr Capabilities saiCapabilities = {.usesDMA = true};
    };

} // namespace audio

M module-bluetooth/Bluetooth/audio/BluetoothAudioDevice.cpp => module-bluetooth/Bluetooth/audio/BluetoothAudioDevice.cpp +10 -4
@@ 9,13 9,14 @@

#include <Audio/Stream.hpp>

#include <chrono>
#include <cassert>

using audio::AudioFormat;
using namespace bluetooth;
using namespace std::chrono_literals;

BluetoothAudioDevice::BluetoothAudioDevice(MediaContext *mediaContext)
    : AudioDevice(btCapabilities, btCapabilities), ctx(mediaContext)
BluetoothAudioDevice::BluetoothAudioDevice(MediaContext *mediaContext) : ctx(mediaContext)
{
    LOG_DEBUG("Bluetooth audio device created");
}


@@ 34,7 35,7 @@ void BluetoothAudioDevice::setMediaContext(MediaContext *mediaContext)
                                                   static_cast<unsigned>(AVDTP::sbcConfig.numChannels)}};
}

auto BluetoothAudioDevice::Start(const Format &format) -> audio::AudioDevice::RetCode
auto BluetoothAudioDevice::Start(const Configuration &format) -> audio::AudioDevice::RetCode
{
    return audio::AudioDevice::RetCode::Success;
}


@@ 71,7 72,7 @@ auto BluetoothAudioDevice::InputPathCtrl(InputPath inputPath) -> audio::AudioDev
    return audio::AudioDevice::RetCode::Success;
}

auto BluetoothAudioDevice::IsFormatSupported(const Format &format) -> bool
auto BluetoothAudioDevice::IsFormatSupported(const Configuration &format) -> bool
{
    return true;
}


@@ 136,3 137,8 @@ auto BluetoothAudioDevice::getSupportedFormats() -> const std::vector<AudioForma
{
    return formats;
}

auto BluetoothAudioDevice::getTraits() const -> Traits
{
    return Traits{.usesDMA = false, .blockSizeConstraint = 512U, .timeConstraint = 10ms};
}

M module-bluetooth/Bluetooth/audio/BluetoothAudioDevice.hpp => module-bluetooth/Bluetooth/audio/BluetoothAudioDevice.hpp +3 -4
@@ 18,15 18,16 @@ namespace bluetooth

        void setMediaContext(MediaContext *MediaContext);

        auto Start(const Format &format) -> audio::AudioDevice::RetCode override;
        auto Start(const Configuration &format) -> audio::AudioDevice::RetCode override;
        auto Stop() -> audio::AudioDevice::RetCode override;

        auto OutputVolumeCtrl(float vol) -> audio::AudioDevice::RetCode override;
        auto InputGainCtrl(float gain) -> audio::AudioDevice::RetCode override;
        auto OutputPathCtrl(OutputPath outputPath) -> audio::AudioDevice::RetCode override;
        auto InputPathCtrl(InputPath inputPath) -> audio::AudioDevice::RetCode override;
        auto IsFormatSupported(const Format &format) -> bool override;
        auto IsFormatSupported(const Configuration &format) -> bool override;
        auto getSupportedFormats() -> const std::vector<audio::AudioFormat> & override;
        auto getTraits() const -> Traits override;

        // Endpoint control methods
        void onDataSend() override;


@@ 42,8 43,6 @@ namespace bluetooth
        MediaContext *ctx  = nullptr;
        bool outputEnabled = false;
        std::vector<audio::AudioFormat> formats;

        static constexpr Capabilities btCapabilities = {.usesDMA = false, .minBlockSize = 512U, .maxBlockSize = 512U};
    };

} // namespace bluetooth