~aleteoryx/muditaos

2c084320492442a2ba0ffb230c97036f83823bb3 — Mateusz Piesta 3 years ago 3ad2e0f
[BH-1453] Fix audio on the simulator

Replaced PortAudio library with PulseAudio.
Fixed minor issues with the WorkerQueue module.
M config/bootstrap_config => config/bootstrap_config +3 -1
@@ 41,7 41,9 @@ INSTALL_PACKAGES="
        mtools \
        ninja-build \
        pkg-config \
        portaudio19-dev \
        pulseaudio \
        libpulse0 \
        libpulse-dev \
        python3-magic \
        python3-pip \
        python3-requests \

M module-audio/Audio/test/CMakeLists.txt => module-audio/Audio/test/CMakeLists.txt +1 -0
@@ 4,6 4,7 @@ add_catch2_executable(
    NAME
        audio-test
    SRCS
        DummyAudioDevice.cpp
        unittest_audio.cpp
    LIBS
        module-audio

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

#include "DummyAudioDevice.hpp"

using namespace audio;

class DummyAudioFactory : public AudioDeviceFactory
{
  public:
    std::shared_ptr<AudioDevice> createCellularAudioDevice() override
    {
        return nullptr;
    }

  protected:
    std::shared_ptr<AudioDevice> getDevice([[maybe_unused]] const audio::Profile &profile) override
    {
        if (profile.GetAudioDeviceType() == AudioDevice::Type::Audiocodec) {
            return std::make_shared<DummyAudioDevice>();
        }
        return nullptr;
    }
};

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

auto DummyAudioDevice::Start() -> AudioDevice::RetCode
{
    return AudioDevice::RetCode::Success;
}
auto DummyAudioDevice::Stop() -> AudioDevice::RetCode
{
    return AudioDevice::RetCode::Success;
}
auto DummyAudioDevice::setOutputVolume(float vol) -> AudioDevice::RetCode
{
    return AudioDevice::RetCode::Success;
}
auto DummyAudioDevice::setInputGain(float gain) -> AudioDevice::RetCode
{
    return AudioDevice::RetCode::Success;
}
auto DummyAudioDevice::getTraits() const -> Endpoint::Traits
{
    return Endpoint::Traits();
}
auto DummyAudioDevice::getSupportedFormats() -> std::vector<audio::AudioFormat>
{
    return std::vector<audio::AudioFormat>();
}
auto DummyAudioDevice::getSourceFormat() -> audio::AudioFormat
{
    return audio::AudioFormat();
}

void DummyAudioDevice::onDataSend()
{}
void DummyAudioDevice::onDataReceive()
{}
void DummyAudioDevice::enableInput()
{}
void DummyAudioDevice::enableOutput()
{}
void DummyAudioDevice::disableInput()
{}
void DummyAudioDevice::disableOutput()
{}

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

#pragma once

#include "Audio/Audio.hpp"

class DummyAudioDevice : public audio::AudioDevice
{
  public:
    auto Start() -> RetCode override;
    auto Stop() -> RetCode override;

    auto setOutputVolume(float vol) -> RetCode override;

    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;
};

M module-audio/Audio/test/unittest_audio.cpp => module-audio/Audio/test/unittest_audio.cpp +3 -6
@@ 4,13 4,7 @@
#include <catch2/catch.hpp>

#include "Audio/decoder/Decoder.hpp"

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

#include "Audio/AudioCommon.hpp"

#include "Audio/AudioMux.hpp"
#include "Audio/Audio.hpp"
#include "Audio/Operation/Operation.hpp"


@@ 18,6 12,9 @@

using namespace audio;

#include <memory>
#include <utility>

TEST_CASE("Audio Decoder")
{
    std::vector<std::string> testExtensions = {"flac", "wav", "mp3"};

M module-audio/board/linux/CMakeLists.txt => module-audio/board/linux/CMakeLists.txt +2 -0
@@ 4,10 4,12 @@
set(AUDIO_LINUX_SOURCES
	LinuxAudioPlatform.cpp
	LinuxAudioDevice.cpp
		PulseAudioWrapper.cpp
)

add_library(${AUDIO_BOARD_LIBRARY} STATIC ${AUDIO_LINUX_SOURCES})
target_include_directories(${AUDIO_BOARD_LIBRARY} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(${AUDIO_BOARD_LIBRARY}
    module-os
		pulse
)

M module-audio/board/linux/LinuxAudioDevice.cpp => module-audio/board/linux/LinuxAudioDevice.cpp +26 -122
@@ 8,64 8,15 @@

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));
            }
        }
    } // namespace

    LinuxAudioDevice::LinuxAudioDevice(const float initialVolume)
        : supportedFormats(
              audio::AudioFormat::makeMatrix(supportedSampleRates, supportedBitWidths, supportedChannelModes))
              audio::AudioFormat::makeMatrix(supportedSampleRates, supportedBitWidths, supportedChannelModes)),
          audioProxy("audioProxy", [this](const auto &data) {
              requestedBytes = data;
              onDataSend();
          })
    {
        setOutputVolume(initialVolume);

        static PortAudio portAudio;
    }

    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


@@ 113,10 64,20 @@ namespace audio
    void LinuxAudioDevice::onDataSend()
    {
        audio::Stream::Span dataSpan;
        if (!isSinkConnected()) {
            return;
        }
        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)]);
        scaleVolume(dataSpan);
        pulseAudioWrapper->insert(dataSpan);
        Sink::_stream->consume();

        if (pulseAudioWrapper->bytes() >= requestedBytes) {
            pulseAudioWrapper->consume();
        }
        else {
            audioProxy.post(requestedBytes);
        }
    }

    void LinuxAudioDevice::onDataReceive()


@@ 127,39 88,15 @@ namespace audio

    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;
        }
        currentFormat = Sink::_stream->getOutputTraits().format;

        pulseAudioWrapper = std::make_unique<PulseAudioWrapper>(
            [this](const std::size_t size) { audioProxy.post(size); }, currentFormat);
    }

    void LinuxAudioDevice::disableInput()


@@ 167,50 104,17 @@ namespace audio

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

        closeStream();
        stream        = nullptr;
        close         = true;
        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
    void LinuxAudioDevice::scaleVolume(audio::AbstractStream::Span data)
    {
        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);
        const auto samplesBeg = reinterpret_cast<std::int16_t *>(data.data);
        const auto samplesEnd = samplesBeg + data.dataSize / 2;
        std::for_each(samplesBeg, samplesEnd, [this](auto &sample) { sample *= volumeFactor; });
    }
} // namespace audio

M module-audio/board/linux/LinuxAudioDevice.hpp => module-audio/board/linux/LinuxAudioDevice.hpp +12 -20
@@ 3,21 3,21 @@

#pragma once

#include <module-bsp/WorkerQueue.hpp>
#include <Audio/AbstractStream.hpp>
#include <Audio/AudioDevice.hpp>
#include <Audio/AudioFormat.hpp>
#include <Audio/codec.hpp>
#include "PulseAudioWrapper.hpp"

#include <portaudio.h>

#include <deque>
#include <variant>

namespace audio
{
    class LinuxAudioDevice : public audio::AudioDevice
    {
      public:
        LinuxAudioDevice(const float initialVolume);
        virtual ~LinuxAudioDevice();
        explicit LinuxAudioDevice(const float initialVolume);

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


@@ 45,15 45,8 @@ namespace audio
        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();
        using AudioProxy = WorkerQueue<std::size_t>;
        void scaleVolume(audio::AbstractStream::Span data);

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


@@ 62,13 55,12 @@ namespace audio
        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;

        std::unique_ptr<PulseAudioWrapper> pulseAudioWrapper;
        AudioProxy audioProxy;
        std::size_t requestedBytes{};
        bool close{false};
    };

} // namespace audio

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

#include "PulseAudioWrapper.hpp"
#include <pthread.h>
#include <iostream>
#include <utility>

namespace audio
{
    PulseAudioWrapper::PulseAudioWrapper(WriteCallback write_cb, AudioFormat audio_format)
        : write_cb{std::move(write_cb)}, audio_format(audio_format)
    {
        /// PulseAudio requests at least SampleRate bytes of data to start playback
        /// Due to how module-audio's streams operate we can load a bit more data to the cache than needed.
        /// Doubling the buffer size is cheap and simple solution.
        cache.reserve(audio_format.getSampleRate() * 2);

        pthread_create(
            &tid,
            nullptr,
            [](void *arg) -> void * {
                /// Mask all available signals to not interfere with the FreeRTOS simulator port
                sigset_t set;
                sigfillset(&set);
                pthread_sigmask(SIG_SETMASK, &set, NULL);

                auto *inst = static_cast<PulseAudioWrapper *>(arg);
                return inst->worker();
            },
            this);
    }

    void *PulseAudioWrapper::worker()
    {
        mainloop     = pa_mainloop_new();
        mainloop_api = pa_mainloop_get_api(mainloop);
        context      = pa_context_new(mainloop_api, "PureOSContext");
        pa_context_set_state_callback(
            context,
            [](pa_context *, void *arg) {
                auto *inst = static_cast<PulseAudioWrapper *>(arg);
                inst->context_state_callback();
            },
            this);
        pa_context_connect(context, nullptr, {}, nullptr);

        int ret = 1;
        if (pa_mainloop_run(mainloop, &ret) < 0) {
            fprintf(stderr, "pa_mainloop_run() failed.\n");
            std::abort();
        }

        return nullptr;
    }

    void PulseAudioWrapper::context_state_callback()
    {
        assert(context);

        switch (pa_context_get_state(context)) {
        case PA_CONTEXT_CONNECTING:
        case PA_CONTEXT_AUTHORIZING:
        case PA_CONTEXT_SETTING_NAME:
            break;

        case PA_CONTEXT_READY: {
            int r;
            pa_sample_spec sample_spec;
            sample_spec.channels = audio_format.getChannels();
            sample_spec.rate     = audio_format.getSampleRate();
            sample_spec.format   = PA_SAMPLE_S16LE;

            if ((stream = pa_stream_new(context, "PureOSStream", &sample_spec, nullptr)) == nullptr) {
                fprintf(stderr, "pa_stream_new() failed: %s\n", pa_strerror(pa_context_errno(context)));
                return;
            }

            pa_stream_set_write_callback(
                stream,
                [](pa_stream *, size_t length, void *arg) {
                    auto *inst = static_cast<PulseAudioWrapper *>(arg);
                    inst->stream_write_cb(length);
                },
                this);

            if ((r = pa_stream_connect_playback(stream, nullptr, nullptr, {}, nullptr, nullptr)) < 0) {
                fprintf(stderr, "pa_stream_connect_playback() failed: %s\n", pa_strerror(pa_context_errno(context)));
                return;
            }

            break;
        }

        case PA_CONTEXT_TERMINATED:
            quit(0);
            fprintf(stderr, "PulseAudio connection terminated.\n");
            break;

        case PA_CONTEXT_FAILED:
        default:
            fprintf(stderr, "Connection failure: %s\n", pa_strerror(pa_context_errno(context)));
            quit(1);
        }
    }

    void PulseAudioWrapper::stream_write_cb(size_t length)
    {
        write_cb(length);
    }

    PulseAudioWrapper::~PulseAudioWrapper()
    {
        quit();
        pthread_join(tid, nullptr);

        if (stream != nullptr) {
            pa_stream_unref(stream);
        }

        if (context != nullptr) {
            pa_context_unref(context);
            context = nullptr;
        }

        if (mainloop != nullptr) {
            pa_mainloop_free(mainloop);
            mainloop     = nullptr;
            mainloop_api = nullptr;
        }
    }

    void PulseAudioWrapper::quit(int ret)
    {
        if (mainloop_api != nullptr) {
            mainloop_api->quit(mainloop_api, ret);
        }
    }

    void PulseAudioWrapper::insert(audio::AbstractStream::Span span)
    {
        std::copy(span.data, span.dataEnd(), &cache[cache_pos]);
        cache_pos += span.dataSize;
    }

    void PulseAudioWrapper::consume()
    {
        if (pa_stream_write(stream, cache.data(), cache_pos, NULL, 0, PA_SEEK_RELATIVE) < 0) {
            fprintf(stderr, "pa_stream_write() failed\n");
            return;
        }
        cache_pos = 0;
    }

    std::size_t PulseAudioWrapper::bytes() const
    {
        return cache_pos;
    }
} // namespace audio

A module-audio/board/linux/PulseAudioWrapper.hpp => module-audio/board/linux/PulseAudioWrapper.hpp +47 -0
@@ 0,0 1,47 @@
// 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 <pulse/pulseaudio.h>
#include <Audio/AudioFormat.hpp>
#include <Audio/AbstractStream.hpp>

#include <cstdint>
#include <functional>
#include <deque>
#include <vector>

namespace audio
{
    class PulseAudioWrapper
    {
      public:
        using WriteCallback = std::function<void(const std::size_t size)>;
        PulseAudioWrapper(WriteCallback write_cb, AudioFormat audio_format);
        ~PulseAudioWrapper();

        void insert(audio::AbstractStream::Span span);
        void consume();
        std::size_t bytes() const;

      private:
        void context_state_callback();
        void *worker();
        void stream_write_cb(size_t length);
        void quit(int ret = 0);

        pthread_t tid{};
        pa_stream *stream{nullptr};
        pa_mainloop *mainloop{nullptr};
        pa_mainloop_api *mainloop_api{nullptr};
        pa_context *context{nullptr};

        WriteCallback write_cb;
        AudioFormat audio_format;

        std::vector<std::uint8_t> cache{};
        std::uint32_t cache_pos{};
    };

} // namespace audio

R module-bsp/board/rt1051/common/WorkerQueue.hpp => module-bsp/WorkerQueue.hpp +15 -7
@@ 6,9 6,11 @@
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include <log/log.hpp>

#include <functional>
#include <utility>
#include <cstdint>

/*
 * Worker queue implementation is used to defer work from interrupt routines.


@@ 46,6 48,7 @@ template <typename Message> class WorkerQueue
    xQueueHandle queueHandle{};
    xTaskHandle taskHandle{};
    WorkerHandle workerHandle{};
    xTaskHandle callerHandle{};

    void worker();
};


@@ 59,6 62,7 @@ WorkerQueue<Message>::WorkerQueue(const char *name, WorkerHandle workerHandle, c
        [](void *pvp) {
            WorkerQueue *inst = static_cast<WorkerQueue *>(pvp);
            inst->worker();
            vTaskDelete(nullptr);
        },
        name,
        stackSize / sizeof(std::uint32_t),


@@ 69,12 73,17 @@ WorkerQueue<Message>::WorkerQueue(const char *name, WorkerHandle workerHandle, c

template <typename Message> WorkerQueue<Message>::~WorkerQueue()
{
    if (queueHandle && taskHandle) {
        const InternalMessage killMsg{.kill = true};
    if ((queueHandle != nullptr) && (taskHandle != nullptr)) {
        const InternalMessage killMsg{{}, true};
        InternalMessage responseMsg;
        xQueueSend(queueHandle, &killMsg, pdMS_TO_TICKS(100));
        callerHandle = xTaskGetCurrentTaskHandle();
        xQueueReset(queueHandle);
        if (xQueueSend(queueHandle, &killMsg, pdMS_TO_TICKS(500)) != pdPASS) {
            LOG_FATAL("xQueueSend error will result in aborting worker thread");
        }

        /// Wait 500ms for a response from the worker. If it does not arrive, kill it.
        if (const auto result = xQueueReceive(queueHandle, &responseMsg, pdMS_TO_TICKS(500)); result != pdTRUE) {
        if (const auto result = ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(500)); result == pdFALSE) {
            vTaskDelete(taskHandle);
        }
        vQueueDelete(queueHandle);


@@ 87,9 96,8 @@ template <typename Message> void WorkerQueue<Message>::worker()
        xQueueReceive(queueHandle, &msg, portMAX_DELAY);

        if (msg.kill) {
            InternalMessage killMsg{.kill = true};
            xQueueSend(queueHandle, &killMsg, pdMS_TO_TICKS(100));
            vTaskDelete(nullptr);
            xTaskNotifyGive(callerHandle);
            return;
        }
        else {
            workerHandle(msg.msg);

M module-bsp/board/rt1051/bellpx/hal/battery_charger/BatteryCharger.cpp => module-bsp/board/rt1051/bellpx/hal/battery_charger/BatteryCharger.cpp +1 -1
@@ 2,7 2,7 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "BatteryChargerIRQ.hpp"
#include "common/WorkerQueue.hpp"
#include "WorkerQueue.hpp"
#include "common/soc_scaler.hpp"

#include "FreeRTOS.h"

M module-bsp/board/rt1051/puretx/hal/battery_charger/BatteryCharger.cpp => module-bsp/board/rt1051/puretx/hal/battery_charger/BatteryCharger.cpp +1 -1
@@ 2,7 2,7 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "BatteryChargerIRQ.hpp"
#include "common/WorkerQueue.hpp"
#include "WorkerQueue.hpp"
#include "common/soc_scaler.hpp"

#if ENABLE_CURRENT_MEASUREMENT_SCOPE