~aleteoryx/muditaos

3c5649df95f31381f314a766b23748f57f5c4ee1 — Pawel.Paprocki 4 years ago 57a50e1
[EGD-6624] Audio support for linux simulator

When using simulator user should hear audio output to test apps like
Music Player.

Co-authored-by: Tomasz Krosnowski <tomasz.krosnowski@mudita.com>
Co-authored-by: Piotr Tański <piotr.tanski@mudita.com>
Co-authored-by: Marcin Smoczyński <marcin.smoczynski@mudita.com>
Tested-by: Marcin Smoczyński <marcin.smoczynski@mudita.com>
M module-audio/Audio/AbstractStream.hpp => module-audio/Audio/AbstractStream.hpp +1 -1
@@ 215,7 215,7 @@ namespace audio
        [[nodiscard]] virtual auto getInputTraits() const noexcept -> Traits = 0;

        /**
         * @brief Get the traits of the stream's input.
         * @brief Get the traits of the stream's output.
         *
         * @return Traits
         */

M module-audio/Audio/AudioDevice.hpp => module-audio/Audio/AudioDevice.hpp +7 -0
@@ 43,7 43,14 @@ namespace audio
            return RetCode::Success;
        }

        /// Set device output volume
        /// @param vol desired volume from 0 to 10
        /// @return RetCode::Success if OK, or RetCode::Failure otherwise
        virtual RetCode setOutputVolume(float vol) = 0;

        /// Set device input gain
        /// @param gain desired input gain from 0 to 100
        /// @return RetCode::Success if OK, or RetCode::Failure otherwise
        virtual RetCode setInputGain(float gain)   = 0;

        auto getSinkFormat() -> AudioFormat override

M module-audio/board/linux/CMakeLists.txt => module-audio/board/linux/CMakeLists.txt +1 -0
@@ 3,6 3,7 @@

set(AUDIO_LINUX_SOURCES
	LinuxAudioPlatform.cpp
	LinuxAudioDevice.cpp
)

add_library(${AUDIO_BOARD_LIBRARY} STATIC ${AUDIO_LINUX_SOURCES})

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

#include "LinuxAudioDevice.hpp"
#include <Audio/Stream.hpp>
#include <module-utils/log/log.hpp>

namespace audio
{
    namespace
    {
        class PortAudio
        {
          public:
            PortAudio();
            PortAudio(const PortAudio &) = delete;
            PortAudio(PortAudio &&)      = delete;
            PortAudio &operator=(const PortAudio &) = delete;
            PortAudio &operator=(PortAudio &&) = delete;
            ~PortAudio() noexcept;
        };

        PortAudio::PortAudio()
        {
            if (const auto errorCode = Pa_Initialize(); errorCode == paNoError) {
                LOG_INFO("Portaudio initialized successfully");
            }
            else {
                LOG_ERROR("Error (code %d) initiializing Portaudio: %s", errorCode, Pa_GetErrorText(errorCode));
            }
        }

        PortAudio::~PortAudio() noexcept
        {
            if (const auto errorCode = Pa_Terminate(); errorCode == paNoError) {
                LOG_INFO("Portaudio terminated successfully");
            }
            else {
                LOG_ERROR("Error (code %d) while terminating Portaudio: %s", errorCode, Pa_GetErrorText(errorCode));
            }
        }

        PortAudio portAudio;
    } // namespace

    LinuxAudioDevice::LinuxAudioDevice()
        : supportedFormats(
              audio::AudioFormat::makeMatrix(supportedSampleRates, supportedBitWidths, supportedChannelModes))
    {}

    LinuxAudioDevice::~LinuxAudioDevice()
    {
        if (stream != nullptr) {
            closeStream();
        }
    }

    void LinuxAudioDevice::closeStream()
    {
        if (const auto errorCode = Pa_AbortStream(stream); errorCode != paNoError) {
            LOG_ERROR("Error (code %d) while stopping Portaudio stream: %s", errorCode, Pa_GetErrorText(errorCode));
        }
        if (const auto errorCode = Pa_CloseStream(stream); errorCode != paNoError) {
            LOG_ERROR("Error (code %d) while closing Portaudio stream: %s", errorCode, Pa_GetErrorText(errorCode));
        }
    }

    auto LinuxAudioDevice::Start() -> RetCode
    {
        if (!isSinkConnected()) {
            return AudioDevice::RetCode::Failure;
        }
        return AudioDevice::RetCode::Success;
    }

    auto LinuxAudioDevice::Stop() -> RetCode
    {
        return AudioDevice::RetCode::Success;
    }

    auto LinuxAudioDevice::setOutputVolume(float vol) -> RetCode
    {
        constexpr auto minVolume = .0f;
        constexpr auto maxVolume = 10.0f;
        vol                      = std::clamp(vol, minVolume, maxVolume);
        volumeFactor             = 1.0f * (vol / maxVolume);
        return RetCode::Success;
    }

    auto LinuxAudioDevice::setInputGain([[maybe_unused]] float gain) -> RetCode
    {
        return RetCode::Success;
    }

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

    auto LinuxAudioDevice::getSupportedFormats() -> std::vector<audio::AudioFormat>
    {
        return supportedFormats;
    }

    auto LinuxAudioDevice::getSourceFormat() -> audio::AudioFormat
    {
        return currentFormat;
    }

    void LinuxAudioDevice::onDataSend()
    {
        audio::Stream::Span dataSpan;
        Sink::_stream->peek(dataSpan);
        auto streamData = reinterpret_cast<std::int16_t *>(dataSpan.data);
        cache.insert(cache.end(), &streamData[0], &streamData[dataSpan.dataSize / sizeof(std::int16_t)]);
        Sink::_stream->consume();
    }

    void LinuxAudioDevice::onDataReceive()
    {}

    void LinuxAudioDevice::enableInput()
    {}

    void LinuxAudioDevice::enableOutput()
    {
        LOG_INFO("Enabling audio output...");
        if (!isSinkConnected()) {
            LOG_ERROR("Output stream is not connected!");
            return;
        }

        currentFormat                = Sink::_stream->getOutputTraits().format;
        const auto numOutputChannels = currentFormat.getChannels();
        auto callback                = [](const void *input,
                           void *output,
                           unsigned long frameCount,
                           const PaStreamCallbackTimeInfo *timeInfo,
                           PaStreamCallbackFlags statusFlags,
                           void *userData) -> int {
            LinuxAudioDevice *dev = static_cast<LinuxAudioDevice *>(userData);
            return dev->streamCallback(input, output, frameCount, timeInfo, statusFlags);
        };
        auto errorCode = Pa_OpenDefaultStream(&stream,
                                              0,
                                              numOutputChannels,
                                              paInt16,
                                              currentFormat.getSampleRate(),
                                              paFramesPerBufferUnspecified,
                                              callback,
                                              this);
        if (errorCode != paNoError) {
            LOG_ERROR("Error (code %d) while creating portaudio stream: %s", errorCode, Pa_GetErrorText(errorCode));
            return;
        }
        if (errorCode = Pa_StartStream(stream); errorCode != paNoError) {
            LOG_ERROR("Error (code %d) while starting portaudio stream: %s", errorCode, Pa_GetErrorText(errorCode));
            return;
        }
    }

    void LinuxAudioDevice::disableInput()
    {}

    void LinuxAudioDevice::disableOutput()
    {
        LOG_INFO("Disabling audio output...");
        if (!isSinkConnected()) {
            LOG_ERROR("Error while stopping Linux Audio Device! Null stream.");
            return;
        }

        closeStream();
        stream        = nullptr;
        currentFormat = {};
    }

    int LinuxAudioDevice::streamCallback([[maybe_unused]] const void *input,
                                         void *output,
                                         unsigned long frameCount,
                                         [[maybe_unused]] const PaStreamCallbackTimeInfo *timeInfo,
                                         [[maybe_unused]] PaStreamCallbackFlags statusFlags)
    {
        if (!isSinkConnected()) {
            return paAbort;
        }

        const auto expectedBufferSize = frameCount * currentFormat.getChannels();
        if (!isCacheReady(expectedBufferSize)) {
            onDataSend();
        }

        const auto dataReadySize = std::min(expectedBufferSize, cache.size());
        cacheToOutputBuffer(static_cast<std::int16_t *>(output), dataReadySize);

        return paContinue;
    }

    bool LinuxAudioDevice::isCacheReady(std::size_t expectedSize) const noexcept
    {
        return cache.size() >= expectedSize;
    }

    void LinuxAudioDevice::cacheToOutputBuffer(std::int16_t *buffer, std::size_t size)
    {
        for (size_t i = 0; i < size; ++i) {
            const auto adjustedValue = static_cast<float>(cache[i]) * volumeFactor;
            *(buffer)                = static_cast<std::int16_t>(adjustedValue);
            buffer++;
        }
        cache.erase(cache.begin(), cache.begin() + size);
    }
} // namespace audio

A module-audio/board/linux/LinuxAudioDevice.hpp => module-audio/board/linux/LinuxAudioDevice.hpp +73 -0
@@ 0,0 1,73 @@
// 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/AudioDevice.hpp>
#include <Audio/AudioFormat.hpp>

#include <portaudio.h>

#include <deque>

namespace audio
{
    class LinuxAudioDevice : public audio::AudioDevice
    {
      public:
        LinuxAudioDevice();
        virtual ~LinuxAudioDevice();

        auto Start() -> RetCode override;
        auto Stop() -> RetCode override;

        /// Set device output volume
        /// @param vol desired volume from 0 to 10
        /// @return RetCode::Success if OK, or RetCode::Failure otherwise
        auto setOutputVolume(float vol) -> RetCode override;

        /// Set device input gain
        /// @param gain desired input gain from 0 to 100
        /// @return RetCode::Success if OK, or RetCode::Failure otherwise
        auto setInputGain(float gain) -> RetCode override;

        auto getTraits() const -> Traits override;
        auto getSupportedFormats() -> std::vector<audio::AudioFormat> override;
        auto getSourceFormat() -> audio::AudioFormat override;

        // Endpoint control methods
        void onDataSend() override;
        void onDataReceive() override;
        void enableInput() override;
        void enableOutput() override;
        void disableInput() override;
        void disableOutput() override;

      private:
        int streamCallback(const void *input,
                           void *output,
                           unsigned long frameCount,
                           const PaStreamCallbackTimeInfo *timeInfo,
                           PaStreamCallbackFlags statusFlags);
        bool isCacheReady(std::size_t expectedSize) const noexcept;
        void cacheToOutputBuffer(std::int16_t *buffer, std::size_t size);

        void closeStream();

        constexpr static std::initializer_list<unsigned int> supportedSampleRates  = {44100, 48000};
        constexpr static std::initializer_list<unsigned int> supportedBitWidths    = {16};
        constexpr static std::initializer_list<unsigned int> supportedChannelModes = {1, 2};

        std::vector<audio::AudioFormat> supportedFormats;
        audio::AudioFormat currentFormat;

        /// pointer to portaudio stream
        PaStream *stream = nullptr;

        /// Local cache to store data read from Pure stream
        std::deque<std::int16_t> cache;

        float volumeFactor = 1.0f;
    };

} // namespace audio

M module-audio/board/linux/LinuxAudioPlatform.cpp => module-audio/board/linux/LinuxAudioPlatform.cpp +7 -3
@@ 1,8 1,9 @@
// Copyright (c) 2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <Audio/AudioPlatform.hpp>
#include <Audio/Profiles/Profile.hpp>
#include <board/linux/LinuxAudioDevice.hpp>

#include <memory>
#include <utility>


@@ 11,7 12,7 @@ using audio::AudioDevice;
using audio::AudioDeviceFactory;
using audio::AudioPlatform;

class DummyAudioFactory : public AudioDeviceFactory
class LinuxAudioFactory : public AudioDeviceFactory
{
  public:
    std::shared_ptr<AudioDevice> createCellularAudioDevice() override


@@ 22,11 23,14 @@ class DummyAudioFactory : public AudioDeviceFactory
  protected:
    std::shared_ptr<AudioDevice> getDevice([[maybe_unused]] const audio::Profile &profile) override
    {
        if (profile.GetAudioDeviceType() == AudioDevice::Type::Audiocodec) {
            return std::make_shared<audio::LinuxAudioDevice>();
        }
        return nullptr;
    }
};

std::unique_ptr<AudioDeviceFactory> AudioPlatform::GetDeviceFactory()
{
    return std::make_unique<DummyAudioFactory>();
    return std::make_unique<LinuxAudioFactory>();
}

M module-bsp/board/linux/audio/linux_audiocodec.cpp => module-bsp/board/linux/audio/linux_audiocodec.cpp +1 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "linux_audiocodec.hpp"

M module-services/service-audio/ServiceAudio.cpp => module-services/service-audio/ServiceAudio.cpp +4 -0
@@ 139,6 139,10 @@ sys::ReturnCodes ServiceAudio::DeinitHandler()

void ServiceAudio::ProcessCloseReason(sys::CloseReason closeReason)
{
    if (const auto &activeInputOpt = audioMux.GetActiveInput(); activeInputOpt.has_value()) {
        const auto activeInput = activeInputOpt.value();
        activeInput->audio->Stop();
    }
    sendCloseReadyMessage(this);
}