~aleteoryx/muditaos

b932e06472ee5d6f28fef424826c54d399f4ce64 — Lefucjusz 2 years ago 862b074
[BH-1823] Fix relaxation start delay for specific MP3 files

Fix of the issue that when trying to play
files with large ID3V2 metadata the
playback would start with significant
delay of several seconds. It was also
possible to leave relaxation progress
window without stopping the playback.
8 files changed, 87 insertions(+), 134 deletions(-)

M harmony_changelog.md
M module-audio/Audio/decoder/decoderMP3.cpp
M module-audio/Audio/decoder/decoderMP3.hpp
D module-audio/Audio/decoder/xing_header.c
D module-audio/Audio/decoder/xing_header.h
M module-audio/CMakeLists.txt
M products/BellHybrid/services/audio/ServiceAudio.cpp
M pure_changelog.md
M harmony_changelog.md => harmony_changelog.md +1 -0
@@ 5,6 5,7 @@
### Fixed
* Fixed eink errors in logs
* Fixed alarm when the onboarding is in progress
* Fixed relaxation start delay when trying to play MP3 files with large metadata

### Added
* Added shortcuts instruction to settings

M module-audio/Audio/decoder/decoderMP3.cpp => module-audio/Audio/decoder/decoderMP3.cpp +52 -5
@@ 8,6 8,45 @@
#include "decoderMP3.hpp"
#include <cstdio>

namespace
{
    signed skipID3V2TagIfPresent(std::FILE *fd)
    {
        constexpr auto ID3V2FrameOffset            = 0;
        constexpr auto ID3V2FrameHeaderSize        = 10;
        constexpr auto ID3V2FrameMagicString       = "ID3";
        constexpr auto ID3V2FrameMagicStringLength = 3;
        std::uint8_t frameBuffer[ID3V2FrameHeaderSize];

        /* Seek to the beginning of the frame and read frame's header */
        if (std::fseek(fd, ID3V2FrameOffset, SEEK_SET) != 0) {
            return -EIO;
        }
        if (std::fread(frameBuffer, sizeof(*frameBuffer), ID3V2FrameHeaderSize, fd) != ID3V2FrameHeaderSize) {
            return -EIO;
        }

        /* Check magic */
        if (strncmp(reinterpret_cast<const char *>(frameBuffer), ID3V2FrameMagicString, ID3V2FrameMagicStringLength) !=
            0) {
            return 0;
        }

        /* The tag size (minus the 10-byte header) is encoded into four bytes,
         * but the most significant bit needs to be masked in each byte.
         * Those frame indices are just copied from the ID3V2 docs. */
        const auto ID3V2TagTotalSize = (((frameBuffer[6] & 0x7F) << 21) | ((frameBuffer[7] & 0x7F) << 14) |
                                        ((frameBuffer[8] & 0x7F) << 7) | ((frameBuffer[9] & 0x7F) << 0)) +
                                       ID3V2FrameHeaderSize;

        /* Skip the tag */
        if (std::fseek(fd, ID3V2FrameOffset + ID3V2TagTotalSize, SEEK_SET) != 0) {
            return -EIO;
        }
        return ID3V2TagTotalSize;
    }
} // namespace

namespace audio
{
    decoderMP3::decoderMP3(const std::string &filePath) : Decoder(filePath)


@@ 16,6 55,14 @@ namespace audio
            return;
        }

        const auto tagSkipStatus = skipID3V2TagIfPresent(fd);
        if (tagSkipStatus < 0) {
            LOG_ERROR("Failed to skip ID3V2 tag, error %d", tagSkipStatus);
        }
        else if (tagSkipStatus == 0) {
            LOG_INFO("No ID3V2 tag to skip");
        }

        mp3 = std::make_unique<drmp3>();

        drmp3_init(mp3.get(), drmp3_read, drmp3_seek, this, nullptr);


@@ 35,20 82,21 @@ namespace audio
    {
        drmp3_uninit(mp3.get());
    }

    void decoderMP3::setPosition(float pos)
    {
        if (!isInitialized) {
            LOG_ERROR("MP3 decoder not initialized");
            return;
        }
        auto totalFramesCount = drmp3_get_pcm_frame_count(mp3.get());
        const auto totalFramesCount = drmp3_get_pcm_frame_count(mp3.get());
        drmp3_seek_to_pcm_frame(mp3.get(), totalFramesCount * pos);
        position = static_cast<float>(totalFramesCount) * pos / static_cast<float>(sampleRate);
    }

    std::uint32_t decoderMP3::decode(std::uint32_t samplesToRead, std::int16_t *pcmData)
    {
        std::uint32_t samplesRead =
        const auto samplesRead =
            drmp3_read_pcm_frames_s16(mp3.get(), samplesToRead / chanNumber, reinterpret_cast<drmp3_int16 *>(pcmData));
        if (samplesRead > 0) {
            position += static_cast<float>(samplesRead) / static_cast<float>(sampleRate);


@@ 57,7 105,7 @@ namespace audio
        return samplesRead * chanNumber;
    }

    size_t decoderMP3::drmp3_read(void *pUserData, void *pBufferOut, size_t bytesToRead)
    std::size_t decoderMP3::drmp3_read(void *pUserData, void *pBufferOut, std::size_t bytesToRead)
    {
        const auto decoderContext = reinterpret_cast<decoderMP3 *>(pUserData);
        return !statFd(decoderContext->fd, "MP3 audio file deleted by user!")


@@ 68,9 116,8 @@ namespace audio
    drmp3_bool32 decoderMP3::drmp3_seek(void *pUserData, int offset, drmp3_seek_origin origin)
    {
        const auto decoderContext = reinterpret_cast<decoderMP3 *>(pUserData);
        const int seekError =
        const auto seekError =
            std::fseek(decoderContext->fd, offset, origin == drmp3_seek_origin_start ? SEEK_SET : SEEK_CUR);
        return (seekError == 0) ? DRMP3_TRUE : DRMP3_FALSE;
    }

} // namespace audio

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

#pragma once


@@ 6,14 6,8 @@
#include "Decoder.hpp"
#include <src/dr_mp3.h>

extern "C"
{
#include "xing_header.h"
};

namespace audio
{

    class decoderMP3 : public Decoder
    {



@@ 27,7 21,7 @@ namespace audio
        void setPosition(float pos) override;

      private:
        std::unique_ptr<drmp3> mp3 = nullptr;
        std::unique_ptr<drmp3> mp3;

        // Callback for when data needs to be read from the client.
        //


@@ 39,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 size_t drmp3_read(void *pUserData, void *pBufferOut, size_t bytesToRead);
        static std::size_t drmp3_read(void *pUserData, void *pBufferOut, std::size_t bytesToRead);

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


@@ 54,5 48,4 @@ namespace audio
        // drflac_seek_origin_current.
        static drmp3_bool32 drmp3_seek(void *pUserData, int offset, drmp3_seek_origin origin);
    };

} // namespace audio

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

#include "xing_header.h"
#include <string.h>

/* XING VBR-Header

 size   description
 4      'Xing' or 'Info'
 4      flags (indicates which fields are used)
 4      frames (optional)
 4      bytes (optional)
 100    toc (optional)
 4      a VBR quality indicator: 0=best 100=worst (optional)

*/

typedef struct
{
    char desc[4];
    uint32_t flags;
} xing_header_t;

// for XING VBR Header flags
#define FRAMES_FLAG    0x0001
#define BYTES_FLAG     0x0002
#define TOC_FLAG       0x0004
#define VBR_SCALE_FLAG 0x0008

//! Byte swap unsigned int
static inline uint32_t swap32(uint32_t ui)
{
    return (ui >> 24) | ((ui << 8) & 0x00FF0000) | ((ui >> 8) & 0x0000FF00) | (ui << 24);
}

uint8_t parseXingHeader(uint8_t *data, size_t datasize, xing_info_t *info)
{
    char *p1 = memmem(data, datasize, "Xing", 4);
    char *p2 = memmem(data, datasize, "Info", 4);

    if (p1 != NULL || p2 != NULL) {
        xing_header_t *head = (xing_header_t *)(p1 != NULL ? p1 : p2);
        uint8_t *raw_data   = (uint8_t *)head + sizeof(xing_header_t);

        head->flags = swap32(head->flags);

        uint8_t offset = 0;
        // get flags (mandatory in XING header)

        // extract total number of frames in file
        if (head->flags & FRAMES_FLAG) {
            uint32_t *tmp     = (uint32_t *)&raw_data[offset];
            info->TotalFrames = swap32(*tmp);
            offset += 4;
        }
        else {
            return 0;
        }

        // extract total number of bytes in file
        if (head->flags & BYTES_FLAG) {
            uint32_t *tmp    = (uint32_t *)&raw_data[offset];
            info->TotalBytes = swap32(*tmp);
            offset += 4;
        }

        // extract TOC
        if (head->flags & TOC_FLAG) {
            memcpy(info->TOC, &raw_data[offset], sizeof(info->TOC));
            offset += sizeof(info->TOC);
        }

        // extract quality status
        if (head->flags & VBR_SCALE_FLAG) {
            info->Quality = raw_data[offset + 3];
        }

        return 1;
    }
    else {
        return 0;
    }
}

D module-audio/Audio/decoder/xing_header.h => module-audio/Audio/decoder/xing_header.h +0 -26
@@ 1,26 0,0 @@
// 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 <stdint.h>
#include <stddef.h>

#define XING_HEADER_SIZE 120

typedef struct
{
    uint32_t TotalFrames;
    uint32_t TotalBytes;
    uint8_t TOC[100];
    uint32_t Quality;
} xing_info_t;

/**
 * @brief This function is used to parse Xing header in MP3 file
 * @param data [in] Pointer to data buffer which contains data to be parsed
 * @param datalen [in] Size of data buffer
 * @param info [out] Pointer to structure describing xing header
 * @return 1 if success, otherwise 0.
 */
uint8_t parseXingHeader(uint8_t *data, size_t datasize, xing_info_t *info);

M module-audio/CMakeLists.txt => module-audio/CMakeLists.txt +0 -1
@@ 25,7 25,6 @@ target_sources(
                ${CMAKE_CURRENT_SOURCE_DIR}/Audio/decoder/decoderMP3.cpp
                ${CMAKE_CURRENT_SOURCE_DIR}/Audio/decoder/decoderWAV.cpp
                ${CMAKE_CURRENT_SOURCE_DIR}/Audio/decoder/DecoderWorker.cpp
                ${CMAKE_CURRENT_SOURCE_DIR}/Audio/decoder/xing_header.c
                ${CMAKE_CURRENT_SOURCE_DIR}/Audio/encoder/Encoder.cpp
                ${CMAKE_CURRENT_SOURCE_DIR}/Audio/encoder/EncoderWAV.cpp
                ${CMAKE_CURRENT_SOURCE_DIR}/Audio/Endpoint.cpp

M products/BellHybrid/services/audio/ServiceAudio.cpp => products/BellHybrid/services/audio/ServiceAudio.cpp +30 -8
@@ 23,7 23,7 @@ namespace
    {
        using namespace audio;
        // clang-format off
        static constexpr std::initializer_list<std::pair<audio::DbPathElement, const char *>> values{
        constexpr std::initializer_list<std::pair<audio::DbPathElement, const char *>> values{
            {DbPathElement{Setting::Volume, PlaybackType::Meditation, Profile::Type::PlaybackLoudspeaker},defaultVolume},
            {DbPathElement{Setting::Volume, PlaybackType::Multimedia, Profile::Type::PlaybackLoudspeaker},defaultVolume},
            {DbPathElement{Setting::Volume, PlaybackType::Alarm, Profile::Type::PlaybackLoudspeaker}, defaultVolume},


@@ 71,7 71,7 @@ namespace service

        connect(typeid(AudioStartPlaybackRequest), [this](sys::Message *msg) -> sys::MessagePointer {
            auto *msgl = static_cast<AudioStartPlaybackRequest *>(msg);
            return handleStart(audio::Operation::Type::Playback, msgl->fileName.c_str(), msgl->playbackType);
            return handleStart(audio::Operation::Type::Playback, msgl->fileName, msgl->playbackType);
        });

        connect(typeid(AudioInternalEOFNotificationMessage), [this](sys::Message *msg) -> sys::MessagePointer {


@@ 95,18 95,23 @@ namespace service
            return handleGetVolume(msgl->playbackType);
        });

        connect(typeid(AudioPauseRequest), [this](sys::Message *msg) -> sys::MessagePointer { return handlePause(); });
        connect(typeid(AudioPauseRequest),
                [this]([[maybe_unused]] sys::Message *msg) -> sys::MessagePointer { return handlePause(); });

        connect(typeid(AudioResumeRequest),
                [this](sys::Message *msg) -> sys::MessagePointer { return handleResume(); });
                [this]([[maybe_unused]] sys::Message *msg) -> sys::MessagePointer { return handleResume(); });
    }

    Audio::~Audio()
    {
    }
    sys::MessagePointer Audio::DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *resp)

    sys::MessagePointer Audio::DataReceivedHandler([[maybe_unused]] sys::DataMessage *msgl,
                                                   [[maybe_unused]] sys::ResponseMessage *resp)
    {
        return sys::msgNotHandled();
    }

    sys::ReturnCodes Audio::InitHandler()
    {
        settingsProvider->init(service::ServiceProxy(weak_from_this()));


@@ 114,11 119,13 @@ namespace service
        LOG_INFO("Initialized");
        return sys::ReturnCodes::Success;
    }

    sys::ReturnCodes Audio::DeinitHandler()
    {
        LOG_INFO("Deinitialized");
        return sys::ReturnCodes::Success;
    }

    void Audio::ProcessCloseReason([[maybe_unused]] sys::CloseReason closeReason)
    {
        if (const auto &activeInputOpt = audioMux.GetActiveInput(); activeInputOpt.has_value()) {


@@ 126,6 133,7 @@ namespace service
            activeInput->audio->Stop();
        }
    }

    auto Audio::handleStart(const audio::Operation::Type opType,
                            const std::string &fileName,
                            const audio::PlaybackType &playbackType) -> std::unique_ptr<AudioResponseMessage>


@@ 141,7 149,7 @@ namespace service
                retToken = audioMux.ResetInput(input);

                try {
                    retCode = (*input)->audio->Start(opType, retToken, fileName.c_str(), playbackType);
                    retCode = (*input)->audio->Start(opType, retToken, fileName, playbackType);
                }
                catch (const audio::AudioInitException &audioException) {
                    retCode = audio::RetCode::FailedToAllocateMemory;


@@ 154,8 162,9 @@ namespace service

        return std::make_unique<AudioStartPlaybackResponse>(retCode, retToken);
    }
    auto Audio::handleStop(const std::vector<audio::PlaybackType> &stopTypes, const audio::Token &token)
        -> std::unique_ptr<AudioResponseMessage>

    auto Audio::handleStop([[maybe_unused]] const std::vector<audio::PlaybackType> &stopTypes,
                           const audio::Token &token) -> std::unique_ptr<AudioResponseMessage>
    {
        std::vector<std::pair<audio::Token, audio::RetCode>> retCodes;



@@ 178,6 187,7 @@ namespace service

        return std::make_unique<AudioStopResponse>(audio::RetCode::Success, token);
    }

    auto Audio::stopInput(audio::AudioMux::Input *input, Audio::StopReason stopReason) -> audio::RetCode
    {
        if (input->audio->GetCurrentState() == audio::Audio::State::Idle) {


@@ 197,10 207,12 @@ namespace service
        manageCpuSentinel();
        return retCode;
    }

    constexpr auto Audio::shouldLoop(const std::optional<audio::PlaybackType> &type) const -> bool
    {
        return type == audio::PlaybackType::Alarm;
    }

    auto Audio::isBusy() const -> bool
    {
        const auto &inputs = audioMux.GetAllInputs();


@@ 208,6 220,7 @@ namespace service
            return input.audio->GetCurrentState() != audio::Audio::State::Idle;
        });
    }

    void Audio::handleEOF(const audio::Token &token)
    {
        if (const auto input = audioMux.GetInput(token); input) {


@@ 222,6 235,7 @@ namespace service
            }
        }
    }

    auto Audio::AudioServicesCallback(const sys::Message *msg) -> std::optional<std::string>
    {
        std::optional<std::string> ret;


@@ 243,6 257,7 @@ namespace service

        return ret;
    }

    auto Audio::handleSetVolume(const audio::PlaybackType &playbackType, const std::string &value)
        -> std::unique_ptr<AudioResponseMessage>
    {


@@ 261,6 276,7 @@ namespace service
        }
        return std::make_unique<AudioResponseMessage>(retCode);
    }

    auto Audio::handleGetVolume(const audio::PlaybackType &playbackType) -> std::unique_ptr<AudioResponseMessage>
    {
        constexpr auto setting = audio::Setting::Volume;


@@ 272,10 288,12 @@ namespace service

        return std::make_unique<AudioResponseMessage>(audio::RetCode::Failed);
    }

    sys::ReturnCodes Audio::SwitchPowerModeHandler([[maybe_unused]] const sys::ServicePowerMode mode)
    {
        return sys::ReturnCodes::Success;
    }

    auto Audio::handlePause() -> std::unique_ptr<AudioResponseMessage>
    {
        auto retCode = audio::RetCode::InvokedInIncorrectState;


@@ 291,6 309,7 @@ namespace service
        manageCpuSentinel();
        return std::make_unique<AudioResponseMessage>(retCode);
    }

    auto Audio::handleResume() -> std::unique_ptr<AudioResponseMessage>
    {
        auto retCode = audio::RetCode::InvokedInIncorrectState;


@@ 301,15 320,18 @@ namespace service
        manageCpuSentinel();
        return std::make_unique<AudioResponseMessage>(retCode);
    }

    constexpr auto Audio::isResumable(audio::PlaybackType type) const -> bool
    {
        return type == audio::PlaybackType::Multimedia;
    }

    void Audio::manageCpuSentinel()
    {
        isBusy() ? cpuSentinel->HoldMinimumFrequency(bsp::CpuFrequencyMHz::Level_6)
                 : cpuSentinel->ReleaseMinimumFrequency();
    }

    void Audio::initializeDatabase()
    {
        for (const auto &entry : initializer::values) {

M pure_changelog.md => pure_changelog.md +1 -0
@@ 19,6 19,7 @@
* Fixed possible crash when entering phone number
* Fixed incorrect calculation of requested CPU frequency
* Fixed CPU frequency setting when dumping logs to a file
* Fixed playback start delay when trying to play MP3 files with large metadata

## [1.9.0 2023-10-19]