~aleteoryx/muditaos

36b0228de2c847f7e0f8bf7a215539609c82ebcd — Lefucjusz 11 months ago e84aa4a
[BH-2100] Fix crash when seeking in large MP3 file

* Fix of the issue that seeking in large MP3
file would cause OOM error when creating
MP3 indexes. This caused OS crash when
looping large MP3 file in Relaxation.
* Minor cleanups.
M harmony_changelog.md => harmony_changelog.md +1 -0
@@ 3,6 3,7 @@
## Unreleased

### Fixed
* Fixed crash in Relaxation when playing large MP3 file

### Added


M module-audio/Audio/Operation/PlaybackOperation.cpp => module-audio/Audio/Operation/PlaybackOperation.cpp +21 -22
@@ 1,4 1,4 @@
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2025, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/blob/master/LICENSE.md

#include "PlaybackOperation.hpp"


@@ 16,8 16,8 @@ namespace audio
    using namespace AudioServiceMessage;

    PlaybackOperation::PlaybackOperation(const std::string &filePath,
                                         const audio::PlaybackType &playbackType,
                                         const audio::PlaybackMode &playbackMode,
                                         const PlaybackType &playbackType,
                                         const PlaybackMode &playbackMode,
                                         Callback callback)
        : Operation(std::move(callback), playbackType), playbackMode(playbackMode), dec(nullptr)
    {


@@ 27,13 27,13 @@ namespace audio
        AddProfile(Profile::Type::PlaybackLoudspeaker, playbackType, true);

        endOfFileCallback = [this]() {
            if (this->playbackMode == audio::PlaybackMode::Single) {
            if (this->playbackMode == PlaybackMode::Single) {
                state          = State::Idle;
                const auto msg = AudioServiceMessage::EndOfFile(operationToken);
                serviceCallback(&msg);
            }
            else {
                dec->setPosition(playbackStartPosition);
                dec->rewind();
            }
        };



@@ 56,7 56,7 @@ namespace audio
        }
    }

    audio::RetCode PlaybackOperation::Start(audio::Token token)
    RetCode PlaybackOperation::Start(Token token)
    {
        if (state == State::Active || (state == State::Paused && outputConnection != nullptr)) {
            return RetCode::InvokedInIncorrectState;


@@ 69,7 69,7 @@ namespace audio
        }
        catch (std::invalid_argument &e) {
            LOG_FATAL("Cannot create audio stream: %s", e.what());
            return audio::RetCode::Failed;
            return RetCode::Failed;
        }

        // create audio connection


@@ 89,11 89,11 @@ namespace audio
        return GetDeviceError(ret);
    }

    audio::RetCode PlaybackOperation::Stop()
    RetCode PlaybackOperation::Stop()
    {
        state = State::Idle;
        if (!audioDevice) {
            return audio::RetCode::DeviceFailure;
            return RetCode::DeviceFailure;
        }

        // stop playback by destroying audio connection


@@ 104,20 104,20 @@ namespace audio
        return GetDeviceError(audioDevice->Stop());
    }

    audio::RetCode PlaybackOperation::Pause()
    RetCode PlaybackOperation::Pause()
    {
        if (state == State::Paused || state == State::Idle || outputConnection == nullptr) {
            return RetCode::InvokedInIncorrectState;
        }
        const auto retCode = GetDeviceError(audioDevice->Pause());
        if (retCode == audio::RetCode::Success) {
        if (retCode == RetCode::Success) {
            state = State::Paused;
            outputConnection->disable();
        }
        return retCode;
    }

    audio::RetCode PlaybackOperation::Resume()
    RetCode PlaybackOperation::Resume()
    {
        if (state == State::Active || state == State::Idle) {
            return RetCode::InvokedInIncorrectState;


@@ 132,14 132,14 @@ namespace audio
        return GetDeviceError(audioDevice->Resume());
    }

    audio::RetCode PlaybackOperation::SetOutputVolume(float vol)
    RetCode PlaybackOperation::SetOutputVolume(float vol)
    {
        currentProfile->SetOutputVolume(vol);
        auto ret = audioDevice->setOutputVolume(vol);
        return GetDeviceError(ret);
    }

    audio::RetCode PlaybackOperation::SetInputGain(float gain)
    RetCode PlaybackOperation::SetInputGain(float gain)
    {
        currentProfile->SetInputGain(gain);
        auto ret = audioDevice->setInputGain(gain);


@@ 151,22 151,21 @@ namespace audio
        return dec->getCurrentPosition();
    }

    audio::RetCode PlaybackOperation::SwitchToPriorityProfile(audio::PlaybackType playbackType)
    RetCode PlaybackOperation::SwitchToPriorityProfile(PlaybackType playbackType)
    {
        for (const auto &p : supportedProfiles) {
            const auto profileType = p.profile->GetType();
            if (profileType == audio::Profile::Type::PlaybackBluetoothA2DP &&
                playbackType == audio::PlaybackType::CallRingtone) {
            if (profileType == Profile::Type::PlaybackBluetoothA2DP && playbackType == PlaybackType::CallRingtone) {
                continue;
            }
            if (p.isAvailable) {
                return SwitchProfile(profileType);
            }
        }
        return audio::RetCode::ProfileNotSet;
        return RetCode::ProfileNotSet;
    }

    audio::RetCode PlaybackOperation::SendEvent(std::shared_ptr<Event> evt)
    RetCode PlaybackOperation::SendEvent(std::shared_ptr<Event> evt)
    {
        const auto isAvailable = evt->getDeviceState() == Event::DeviceState::Connected;
        switch (evt->getType()) {


@@ 185,7 184,7 @@ namespace audio
        return RetCode::Success;
    }

    audio::RetCode PlaybackOperation::SwitchProfile(const Profile::Type type)
    RetCode PlaybackOperation::SwitchProfile(const Profile::Type type)
    {
        auto newProfile = GetProfile(type);
        if (newProfile == nullptr) {


@@ 199,7 198,7 @@ namespace audio

        // adjust new profile with information from file's tags
        newProfile->SetSampleRate(dec->getSourceFormat().getSampleRate());
        newProfile->SetInOutFlags(static_cast<std::uint32_t>(audio::codec::Flags::OutputStereo));
        newProfile->SetInOutFlags(static_cast<std::uint32_t>(codec::Flags::OutputStereo));

        /// profile change - (re)create output device; stop audio first by
        /// killing audio connection


@@ 228,7 227,7 @@ namespace audio
            Start(operationToken);
        }

        return audio::RetCode::Success;
        return RetCode::Success;
    }

    PlaybackOperation::~PlaybackOperation()

M module-audio/Audio/Operation/PlaybackOperation.hpp => module-audio/Audio/Operation/PlaybackOperation.hpp +13 -14
@@ 1,4 1,4 @@
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2025, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/blob/master/LICENSE.md

#pragma once


@@ 19,28 19,27 @@ namespace audio
    {
      public:
        PlaybackOperation(const std::string &filePath,
                          const audio::PlaybackType &playbackType,
                          const audio::PlaybackMode &playbackMode,
                          const PlaybackType &playbackType,
                          const PlaybackMode &playbackMode,
                          AudioServiceMessage::Callback callback = nullptr);

        ~PlaybackOperation() override;

        audio::RetCode Start(audio::Token token) final;
        audio::RetCode Stop() final;
        audio::RetCode Pause() final;
        audio::RetCode Resume() final;
        audio::RetCode SendEvent(std::shared_ptr<Event> evt) final;
        audio::RetCode SwitchProfile(const Profile::Type type) final;
        audio::RetCode SetOutputVolume(float vol) final;
        audio::RetCode SetInputGain(float gain) final;
        RetCode Start(Token token) final;
        RetCode Stop() final;
        RetCode Pause() final;
        RetCode Resume() final;
        RetCode SendEvent(std::shared_ptr<Event> evt) final;
        RetCode SwitchProfile(const Profile::Type type) final;
        RetCode SetOutputVolume(float vol) final;
        RetCode SetInputGain(float gain) final;

        Position GetPosition() final;
        audio::RetCode SwitchToPriorityProfile(audio::PlaybackType playbackType) final;
        RetCode SwitchToPriorityProfile(PlaybackType playbackType) final;

      private:
        static constexpr auto playbackTimeConstraint = 10ms;
        static constexpr auto playbackStartPosition  = 0U;
        audio::PlaybackMode playbackMode             = audio::PlaybackMode::Single;
        PlaybackMode playbackMode                    = PlaybackMode::Single;

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

M module-audio/Audio/decoder/Decoder.cpp => module-audio/Audio/decoder/Decoder.cpp +10 -9
@@ 1,4 1,4 @@
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2025, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/blob/master/LICENSE.md

#include <cstdio>


@@ 42,12 42,12 @@ namespace audio
        }
    }

    std::unique_ptr<tags::fetcher::Tags> Decoder::fetchTags()
    auto Decoder::fetchTags() -> std::unique_ptr<tags::fetcher::Tags>
    {
        return std::make_unique<tags::fetcher::Tags>(tags::fetcher::fetchTags(filePath));
    }

    std::unique_ptr<Decoder> Decoder::Create(const std::string &filePath)
    auto Decoder::Create(const std::string &filePath) -> std::unique_ptr<Decoder>
    {
        const auto extension          = std::filesystem::path(filePath).extension();
        const auto extensionLowercase = utils::stringToLowercase(extension);


@@ 74,10 74,11 @@ namespace audio
        return dec;
    }

    void Decoder::startDecodingWorker(const DecoderWorker::EndOfFileCallback &endOfFileCallback,
                                      const DecoderWorker::FileDeletedCallback &fileDeletedCallback)
    auto Decoder::startDecodingWorker(const DecoderWorker::EndOfFileCallback &endOfFileCallback,
                                      const DecoderWorker::FileDeletedCallback &fileDeletedCallback) -> void
    {
        assert(_stream != nullptr);

        if (audioWorker == nullptr) {
            const auto channelMode = (tags->num_channel == 1) ? DecoderWorker::ChannelMode::ForceStereo
                                                              : DecoderWorker::ChannelMode::NoConversion;


@@ 92,7 93,7 @@ namespace audio
        }
    }

    void Decoder::stopDecodingWorker()
    auto Decoder::stopDecodingWorker() -> void
    {
        if (audioWorker) {
            audioWorker->close();


@@ 100,17 101,17 @@ namespace audio
        audioWorker = nullptr;
    }

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

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

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

M module-audio/Audio/decoder/Decoder.hpp => module-audio/Audio/decoder/Decoder.hpp +18 -15
@@ 1,4 1,4 @@
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2025, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/blob/master/LICENSE.md

#pragma once


@@ 28,49 28,52 @@ namespace audio
        explicit Decoder(const std::string &path);
        virtual ~Decoder();

        virtual std::int32_t decode(std::uint32_t samplesToRead, std::int16_t *pcmData) = 0;
        virtual auto decode(std::uint32_t samplesToRead, std::int16_t *pcmData) -> std::int32_t = 0;

        // Range 0 - 1
        virtual void setPosition(float pos) = 0;
        virtual auto setPosition(float pos) -> void = 0;

        std::uint32_t getSampleRate()
        // Rewind to first audio sample
        virtual auto rewind() -> void = 0;

        [[nodiscard]] auto getSampleRate() const noexcept -> std::uint32_t
        {
            return sampleRate;
        }

        std::uint32_t getChannelCount()
        [[nodiscard]] auto getChannelCount() const noexcept -> std::uint32_t
        {
            return channelCount;
        }

        float getCurrentPosition()
        [[nodiscard]] auto getCurrentPosition() const noexcept -> float
        {
            return position;
        }

        void onDataReceive() override;
        void enableInput() override;
        void disableInput() override;
        auto onDataReceive() -> void override;
        auto enableInput() -> void override;
        auto disableInput() -> void override;

        auto getSourceFormat() -> AudioFormat override;
        auto getSupportedFormats() -> std::vector<AudioFormat> override;

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

        void startDecodingWorker(const DecoderWorker::EndOfFileCallback &endOfFileCallback,
                                 const DecoderWorker::FileDeletedCallback &fileDeletedCallback);
        void stopDecodingWorker();
        auto startDecodingWorker(const DecoderWorker::EndOfFileCallback &endOfFileCallback,
                                 const DecoderWorker::FileDeletedCallback &fileDeletedCallback) -> void;
        auto stopDecodingWorker() -> void;

        // Factory method
        static std::unique_ptr<Decoder> Create(const std::string &path);
        static auto Create(const std::string &path) -> std::unique_ptr<Decoder>;

      protected:
        virtual auto getBitWidth() -> unsigned int
        [[nodiscard]] virtual auto getBitWidth() -> unsigned int
        {
            return bitsPerSample;
        }

        virtual std::unique_ptr<tags::fetcher::Tags> fetchTags();
        virtual auto fetchTags() -> std::unique_ptr<tags::fetcher::Tags>;

        static constexpr Endpoint::Traits decoderCaps = {.usesDMA = false};


M module-audio/Audio/decoder/DecoderFLAC.cpp => module-audio/Audio/decoder/DecoderFLAC.cpp +21 -22
@@ 1,9 1,8 @@
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2025, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/blob/master/LICENSE.md

#include "DecoderFLAC.hpp"
#include "DecoderCommon.hpp"
#include "flac/flacfile.h"

#define DR_FLAC_IMPLEMENTATION
#define DR_FLAC_NO_STDIO


@@ 39,13 38,13 @@ namespace audio
        drflac_close(flac);
    }

    std::int32_t DecoderFLAC::decode(std::uint32_t samplesToRead, std::int16_t *pcmData)
    auto DecoderFLAC::decode(std::uint32_t samplesToRead, std::int16_t *pcmData) -> std::int32_t
    {
        const auto samplesRead =
        const auto framesRead =
            drflac_read_pcm_frames_s16(flac, samplesToRead / channelCount, reinterpret_cast<drflac_int16 *>(pcmData));
        if (samplesRead > 0) {
        if (framesRead > 0) {
            /* Calculate frame duration in seconds */
            position += static_cast<float>(samplesRead) / static_cast<float>(sampleRate);
            position += static_cast<float>(framesRead) / static_cast<float>(sampleRate);
        }
        else if (!fileExists(fd)) {
            /* Unfortunately this second check of file existence is needed


@@ 54,34 53,34 @@ namespace audio
            LOG_WARN("File '%s' was deleted during playback!", filePath.c_str());
            return fileDeletedRetCode;
        }
        return samplesRead * channelCount;
        return framesRead * channelCount;
    }

    void DecoderFLAC::setPosition(float pos)
    auto DecoderFLAC::setPosition(float pos) -> void
    {
        if (!isInitialized) {
            LOG_ERROR("FLAC decoder not initialized");
            return;
        }
        drflac_seek_to_pcm_frame(flac, flac->totalPCMFrameCount * pos);

        const auto frameToSeek = static_cast<std::uint64_t>(flac->totalPCMFrameCount * pos);
        const auto status      = drflac_seek_to_pcm_frame(flac, frameToSeek);
        if (status == DRFLAC_FALSE) {
            LOG_ERROR("Failed to seek to frame %" PRIu64 " in file '%s'!", frameToSeek, filePath.c_str());
        }

        // Calculate new position
        position = static_cast<float>(flac->totalPCMFrameCount) * pos / static_cast<float>(sampleRate);
        position = frameToSeek / static_cast<float>(sampleRate);
    }

    /* Data encoded in UTF-8 */
    void DecoderFLAC::parseText(
        std::uint8_t *in, std::uint32_t taglen, std::uint32_t datalen, std::uint8_t *out, std::uint32_t outlen)
    auto DecoderFLAC::rewind() -> void
    {
        /* Little Endian here */
        const auto size = std::min(datalen - taglen, outlen - 1);
        memcpy(out, in + taglen, size);
        out[size] = '\0';
        setPosition(0.0f);
    }

    std::size_t DecoderFLAC::drflacRead(void *pUserData, void *pBufferOut, std::size_t bytesToRead)
    auto DecoderFLAC::drflacRead(void *pUserData, void *pBufferOut, std::size_t bytesToRead) -> std::size_t
    {
        const auto decoderContext = reinterpret_cast<DecoderFLAC *>(pUserData);
        const auto decoderContext = static_cast<DecoderFLAC *>(pUserData);

        /* Check if the file exists - std::fread happily returns bytesToRead if
         * requested to read from deleted file, what causes decoding library


@@ 92,11 91,11 @@ namespace audio
        return std::fread(pBufferOut, 1, bytesToRead, decoderContext->fd);
    }

    drflac_bool32 DecoderFLAC::drflacSeek(void *pUserData, int offset, drflac_seek_origin origin)
    auto DecoderFLAC::drflacSeek(void *pUserData, int offset, drflac_seek_origin origin) -> drflac_bool32
    {
        const auto decoderContext = reinterpret_cast<DecoderFLAC *>(pUserData);
        const auto decoderContext = static_cast<DecoderFLAC *>(pUserData);
        const auto seekError =
            std::fseek(decoderContext->fd, offset, origin == drflac_seek_origin_start ? SEEK_SET : SEEK_CUR);
            std::fseek(decoderContext->fd, offset, (origin == drflac_seek_origin_start) ? SEEK_SET : SEEK_CUR);
        return (seekError == 0) ? DRFLAC_TRUE : DRFLAC_FALSE;
    }
} // namespace audio

M module-audio/Audio/decoder/DecoderFLAC.hpp => module-audio/Audio/decoder/DecoderFLAC.hpp +7 -10
@@ 1,4 1,4 @@
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2025, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/blob/master/LICENSE.md

#pragma once


@@ 12,19 12,16 @@ namespace audio
    {
      public:
        explicit DecoderFLAC(const std::string &filePath);
        ~DecoderFLAC();
        ~DecoderFLAC() override;

        std::int32_t decode(std::uint32_t samplesToRead, std::int16_t *pcmData) override;
        auto decode(std::uint32_t samplesToRead, std::int16_t *pcmData) -> std::int32_t override;

        void setPosition(float pos) override;
        auto setPosition(float pos) -> void override;
        auto rewind() -> void override;

      private:
        drflac *flac = nullptr;

        /* Data encoded in UTF-8 */
        void parseText(
            std::uint8_t *in, std::uint32_t taglen, std::uint32_t datalen, std::uint8_t *out, std::uint32_t outlen);

        // Callback for when data needs to be read from the client.
        //
        // pUserData   [in]  The user data that was passed to drflac_open() and family.


@@ 35,7 32,7 @@ namespace audio
        //
        // A return value of less than bytesToRead indicates the end of the stream. Do _not_ return from this callback
        // until either the entire bytesToRead is filled or you have reached the end of the stream.
        static std::size_t drflacRead(void *pUserData, void *pBufferOut, std::size_t bytesToRead);
        static auto drflacRead(void *pUserData, void *pBufferOut, std::size_t bytesToRead) -> std::size_t;

        // Callback for when data needs to be seeked.
        //


@@ 48,6 45,6 @@ namespace audio
        // The offset will never be negative. Whether it is relative to the beginning or current position is
        // determined by the "origin" parameter which will be either drflac_seek_origin_start or
        // drflac_seek_origin_current.
        static drflac_bool32 drflacSeek(void *pUserData, int offset, drflac_seek_origin origin);
        static auto drflacSeek(void *pUserData, int offset, drflac_seek_origin origin) -> drflac_bool32;
    };
} // namespace audio

M module-audio/Audio/decoder/DecoderMP3.cpp => module-audio/Audio/decoder/DecoderMP3.cpp +25 -11
@@ 1,4 1,4 @@
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2025, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/blob/master/LICENSE.md

#define MINIMP3_IMPLEMENTATION


@@ 25,7 25,7 @@ namespace audio

        const auto decoderStatus = mp3dec_ex_open_cb(dec.get(), dec->io, MP3D_SEEK_TO_SAMPLE | MP3D_DO_NOT_SCAN);
        if (decoderStatus != 0) {
            LOG_ERROR("Failed to open minimp3, error: %d", decoderStatus);
            LOG_ERROR("Failed to open minimp3, error %d", decoderStatus);
            return;
        }



@@ 45,22 45,36 @@ namespace audio
        isInitialized = false;
    }

    void DecoderMP3::setPosition(float pos)
    auto DecoderMP3::setPosition(float pos) -> void
    {
        if (!isInitialized) {
            LOG_ERROR("MP3 decoder not initialized");
            return;
        }
        mp3dec_ex_seek(dec.get(), pos);

        const auto sampleToSeek = static_cast<std::uint64_t>(dec->samples * pos);
        const auto frameToSeek  = sampleToSeek / channelCount;
        const auto status       = mp3dec_ex_seek(dec.get(), sampleToSeek);
        if (status != 0) {
            LOG_ERROR(
                "Failed to seek to frame %" PRIu64 " in file '%s', error %d!", frameToSeek, filePath.c_str(), status);
        }

        position = frameToSeek / static_cast<float>(sampleRate);
    }

    auto DecoderMP3::rewind() -> void
    {
        setPosition(0.0f);
    }

    std::int32_t DecoderMP3::decode(std::uint32_t samplesToRead, std::int16_t *pcmData)
    auto DecoderMP3::decode(std::uint32_t samplesToRead, std::int16_t *pcmData) -> std::int32_t
    {
        const auto samplesRead = mp3dec_ex_read(dec.get(), reinterpret_cast<mp3d_sample_t *>(pcmData), samplesToRead);
        if (samplesRead > 0) {
            /* Calculate frame duration in seconds */
            const auto samplesPerChannel = static_cast<float>(samplesRead) / static_cast<float>(channelCount);
            position += samplesPerChannel / static_cast<float>(sampleRate);
            const auto framesRead = static_cast<float>(samplesRead) / static_cast<float>(channelCount);
            position += framesRead / static_cast<float>(sampleRate);
        }
        else if (!fileExists(fd)) {
            /* Unfortunately this second check of file existence is needed


@@ 72,9 86,9 @@ namespace audio
        return samplesRead;
    }

    std::size_t DecoderMP3::mp3Read(void *pBufferOut, std::size_t bytesToRead, void *pUserData)
    auto DecoderMP3::mp3Read(void *pBufferOut, std::size_t bytesToRead, void *pUserData) -> std::size_t
    {
        const auto decoderContext = reinterpret_cast<DecoderMP3 *>(pUserData);
        const auto decoderContext = static_cast<DecoderMP3 *>(pUserData);

        /* Check if the file exists - std::fread happily returns bytesToRead if
         * requested to read from deleted file, what causes decoding library


@@ 85,9 99,9 @@ namespace audio
        return std::fread(pBufferOut, 1, bytesToRead, decoderContext->fd);
    }

    int DecoderMP3::mp3Seek(std::uint64_t offset, void *pUserData)
    auto DecoderMP3::mp3Seek(std::uint64_t offset, void *pUserData) -> int
    {
        const auto decoderContext = reinterpret_cast<DecoderMP3 *>(pUserData);
        const auto decoderContext = static_cast<DecoderMP3 *>(pUserData);
        return std::fseek(decoderContext->fd, offset, SEEK_SET);
    }
} // namespace audio

M module-audio/Audio/decoder/DecoderMP3.hpp => module-audio/Audio/decoder/DecoderMP3.hpp +7 -6
@@ 1,4 1,4 @@
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2025, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/blob/master/LICENSE.md

#pragma once


@@ 12,11 12,12 @@ namespace audio
    {
      public:
        explicit DecoderMP3(const std::string &filePath);
        ~DecoderMP3();
        ~DecoderMP3() override;

        std::int32_t decode(std::uint32_t samplesToRead, std::int16_t *pcmData) override;
        auto decode(std::uint32_t samplesToRead, std::int16_t *pcmData) -> std::int32_t override;

        void setPosition(float pos) override;
        auto setPosition(float pos) -> void override;
        auto rewind() -> void override;

      private:
        std::unique_ptr<mp3dec_ex_t> dec;


@@ 32,7 33,7 @@ namespace audio
        //
        // A return value of less than bytesToRead indicates the end of the stream. Do _not_ return from this callback
        // until either the entire bytesToRead is filled or you have reached the end of the stream.
        static std::size_t mp3Read(void *pBufferOut, std::size_t bytesToRead, void *pUserData);
        static auto mp3Read(void *pBufferOut, std::size_t bytesToRead, void *pUserData) -> std::size_t;

        // Callback for when data needs to be seeked.
        //


@@ 42,6 43,6 @@ namespace audio
        // Returns whether the seek was successful.
        //
        // The offset will never be negative. It relates to the beginning position.
        static int mp3Seek(std::uint64_t offset, void *pUserData);
        static auto mp3Seek(std::uint64_t offset, void *pUserData) -> int;
    };
} // namespace audio

M module-audio/Audio/decoder/DecoderWAV.cpp => module-audio/Audio/decoder/DecoderWAV.cpp +24 -14
@@ 1,4 1,4 @@
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2025, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/blob/master/LICENSE.md

#include "DecoderWAV.hpp"


@@ 38,29 38,39 @@ namespace audio
        }
    }

    void DecoderWAV::setPosition(float pos)
    auto DecoderWAV::setPosition(float pos) -> void
    {
        if (!isInitialized) {
            LOG_ERROR("WAV decoder not initialized");
            return;
        }
        drwav_seek_to_pcm_frame(wav.get(), wav->totalPCMFrameCount * pos);

        const auto frameToSeek = static_cast<std::uint64_t>(wav->totalPCMFrameCount * pos);
        const auto status      = drwav_seek_to_pcm_frame(wav.get(), frameToSeek);
        if (status == DRWAV_FALSE) {
            LOG_ERROR("Failed to seek to frame %" PRIu64 " in file '%s'!", frameToSeek, filePath.c_str());
        }

        /* Calculate new position */
        position = static_cast<float>(wav->totalPCMFrameCount) * pos / static_cast<float>(sampleRate);
        position = frameToSeek / static_cast<float>(sampleRate);
    }

    auto DecoderWAV::rewind() -> void
    {
        setPosition(0.0f);
    }

    std::int32_t DecoderWAV::decode(std::uint32_t samplesToRead, std::int16_t *pcmData)
    auto DecoderWAV::decode(std::uint32_t samplesToRead, std::int16_t *pcmData) -> std::int32_t
    {
        if (!isInitialized) {
            LOG_ERROR("WAV decoder not initialized");
            return 0;
        }

        const auto samplesRead = drwav_read_pcm_frames_s16(wav.get(), samplesToRead / channelCount, pcmData);
        if (samplesRead > 0) {
        const auto framesRead = drwav_read_pcm_frames_s16(wav.get(), samplesToRead / channelCount, pcmData);
        if (framesRead > 0) {
            /* Calculate frame duration in seconds */
            position += static_cast<float>(samplesRead) / static_cast<float>(sampleRate);
            position += static_cast<float>(framesRead) / static_cast<float>(sampleRate);
        }
        else if (!fileExists(fd)) {
            /* Unfortunately this second check of file existence is needed


@@ 69,12 79,12 @@ namespace audio
            LOG_WARN("File '%s' was deleted during playback!", filePath.c_str());
            return fileDeletedRetCode;
        }
        return samplesRead * channelCount;
        return framesRead * channelCount;
    }

    std::size_t DecoderWAV::drwavRead(void *pUserData, void *pBufferOut, std::size_t bytesToRead)
    auto DecoderWAV::drwavRead(void *pUserData, void *pBufferOut, std::size_t bytesToRead) -> std::size_t
    {
        const auto decoderContext = reinterpret_cast<DecoderWAV *>(pUserData);
        const auto decoderContext = static_cast<DecoderWAV *>(pUserData);

        /* Check if the file exists - std::fread happily returns bytesToRead if
         * requested to read from deleted file, what causes decoding library


@@ 85,11 95,11 @@ namespace audio
        return std::fread(pBufferOut, 1, bytesToRead, decoderContext->fd);
    }

    drwav_bool32 DecoderWAV::drwavSeek(void *pUserData, int offset, drwav_seek_origin origin)
    auto DecoderWAV::drwavSeek(void *pUserData, int offset, drwav_seek_origin origin) -> drwav_bool32
    {
        const auto decoderContext = reinterpret_cast<DecoderWAV *>(pUserData);
        const auto decoderContext = static_cast<DecoderWAV *>(pUserData);
        const auto seekError =
            std::fseek(decoderContext->fd, offset, origin == drwav_seek_origin_start ? SEEK_SET : SEEK_CUR);
            std::fseek(decoderContext->fd, offset, (origin == drwav_seek_origin_start) ? SEEK_SET : SEEK_CUR);
        return (seekError == 0) ? DRWAV_TRUE : DRWAV_FALSE;
    }
} // namespace audio

M module-audio/Audio/decoder/DecoderWAV.hpp => module-audio/Audio/decoder/DecoderWAV.hpp +7 -6
@@ 1,4 1,4 @@
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2025, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/blob/master/LICENSE.md

#pragma once


@@ 12,11 12,12 @@ namespace audio
    {
      public:
        explicit DecoderWAV(const std::string &filePath);
        ~DecoderWAV();
        ~DecoderWAV() override;

        std::int32_t decode(std::uint32_t samplesToRead, std::int16_t *pcmData) override;
        auto decode(std::uint32_t samplesToRead, std::int16_t *pcmData) -> std::int32_t override;

        void setPosition(float pos) override;
        auto setPosition(float pos) -> void override;
        auto rewind() -> void override;

      private:
        std::unique_ptr<drwav> wav;


@@ 31,7 32,7 @@ namespace audio
        //
        // A return value of less than bytesToRead indicates the end of the stream. Do _not_ return from this callback
        // until either the entire bytesToRead is filled or you have reached the end of the stream.
        static std::size_t drwavRead(void *pUserData, void *pBufferOut, std::size_t bytesToRead);
        static auto drwavRead(void *pUserData, void *pBufferOut, std::size_t bytesToRead) -> std::size_t;

        // Callback for when data needs to be seeked.
        //


@@ 44,6 45,6 @@ namespace audio
        // The offset will never be negative. Whether it is relative to the beginning or current position is
        // determined by the "origin" parameter which will be either drwav_seek_origin_start or
        // drwav_seek_origin_current.
        static drwav_bool32 drwavSeek(void *pUserData, int offset, drwav_seek_origin origin);
        static auto drwavSeek(void *pUserData, int offset, drwav_seek_origin origin) -> drwav_bool32;
    };
} // namespace audio

M third-party/minimp3/minimp3 => third-party/minimp3/minimp3 +1 -1
@@ 1,1 1,1 @@
Subproject commit e7202154493fb9dd8ca8c93f432003c4f191c5ae
Subproject commit 3fd4dc273c4a5a19d12b4183220eaf0168b725bf