~aleteoryx/muditaos

200c9b949d964dea034de7ea6ad9f6ce04a49643 — Jakub Pyszczak 4 years ago 13af1f4
[EGD-7432] Tags fetcher

Added tags fetcher as universal audio utility
which can be used without bus overhead
30 files changed, 232 insertions(+), 261 deletions(-)

M module-apps/application-alarm-clock/widgets/AlarmOptionsItem.cpp
M module-apps/application-alarm-clock/widgets/AlarmOptionsItem.hpp
M module-apps/application-music-player/ApplicationMusicPlayer.cpp
M module-apps/application-music-player/CMakeLists.txt
M module-apps/application-music-player/models/SongsRepository.cpp
M module-apps/application-music-player/models/SongsRepository.hpp
M module-apps/application-music-player/tests/MockSongsRepository.hpp
M module-apps/application-music-player/tests/MockTagsFetcher.hpp
M module-apps/application-music-player/tests/unittest_songrepository.cpp
M module-apps/application-music-player/tests/unittest_songsmodel.cpp
M module-apps/application-settings/models/apps/SoundsModel.cpp
M module-audio/Audio/Audio.cpp
M module-audio/Audio/Audio.hpp
M module-audio/Audio/Operation/PlaybackOperation.cpp
M module-audio/Audio/Operation/PlaybackOperation.hpp
M module-audio/Audio/decoder/Decoder.cpp
M module-audio/Audio/decoder/Decoder.hpp
M module-audio/Audio/decoder/decoderFLAC.cpp
M module-audio/Audio/decoder/decoderFLAC.hpp
M module-audio/Audio/test/unittest_audio.cpp
M module-audio/CMakeLists.txt
M module-audio/README.md
A module-audio/tags_fetcher/CMakeLists.txt
A module-audio/tags_fetcher/TagsFetcher.cpp
A module-audio/tags_fetcher/TagsFetcher.hpp
M module-services/service-audio/AudioServiceAPI.cpp
M module-services/service-audio/ServiceAudio.cpp
M module-services/service-audio/include/service-audio/AudioMessage.hpp
M module-services/service-audio/include/service-audio/AudioServiceAPI.hpp
M module-services/service-audio/include/service-audio/ServiceAudio.hpp
M module-apps/application-alarm-clock/widgets/AlarmOptionsItem.cpp => module-apps/application-alarm-clock/widgets/AlarmOptionsItem.cpp +6 -11
@@ 242,7 242,7 @@ namespace gui
        onLoadCallback = [&](std::shared_ptr<AlarmsRecord> alarm) {
            switch (itemName) {
            case AlarmOptionItemName::Sound: {
                auto it = std::find_if(songsList.begin(), songsList.end(), [alarm](const audio::Tags &tag) {
                auto it = std::find_if(songsList.begin(), songsList.end(), [alarm](const tags::fetcher::Tags &tag) {
                    return tag.filePath == alarm->path.c_str();
                });
                if (it == songsList.end()) {


@@ 308,22 308,17 @@ namespace gui
        };
    }

    std::vector<audio::Tags> AlarmOptionsItem::getMusicFilesList()
    std::vector<tags::fetcher::Tags> AlarmOptionsItem::getMusicFilesList()
    {
        const auto musicFolder = (purefs::dir::getUserDiskPath() / "music").string();
        std::vector<audio::Tags> musicFiles;
        std::vector<tags::fetcher::Tags> musicFiles;
        LOG_INFO("Scanning music folder: %s", musicFolder.c_str());
        for (const auto &ent : std::filesystem::directory_iterator(musicFolder)) {
            if (!ent.is_directory()) {
                const auto filePath = std::string(musicFolder) + "/" + ent.path().filename().c_str();
                auto fileTags       = AudioServiceAPI::GetFileTags(application, filePath);
                if (fileTags) {
                    musicFiles.push_back(*fileTags);
                    LOG_DEBUG("file: %s found", ent.path().filename().c_str());
                }
                else {
                    LOG_ERROR("Not an audio file %s", ent.path().filename().c_str());
                }
                auto fileTags       = tags::fetcher::fetchTags(filePath);
                musicFiles.push_back(fileTags);
                LOG_DEBUG("file: %s found", ent.path().filename().c_str());
            }
        }
        LOG_INFO("Total number of music files found: %u", static_cast<unsigned int>(musicFiles.size()));

M module-apps/application-alarm-clock/widgets/AlarmOptionsItem.hpp => module-apps/application-alarm-clock/widgets/AlarmOptionsItem.hpp +4 -5
@@ 9,7 9,7 @@
#include <Label.hpp>
#include <Image.hpp>
#include <BoxLayout.hpp>
#include <Audio/decoder/Decoder.hpp>
#include <tags_fetcher/TagsFetcher.hpp>

namespace gui
{


@@ 32,12 32,11 @@ namespace gui
        gui::Image *rightArrow        = nullptr;
        AlarmOptionItemName itemName;
        std::vector<std::string> optionsNames;
        std::vector<audio::Tags> songsList;

        /// pointer to audio operations which allows to make audio preview
        std::unique_ptr<app::AbstractAudioOperations> audioOperations;

        MusicStatus musicStatus        = MusicStatus::Stop;
        std::vector<tags::fetcher::Tags> songsList;
        MusicStatus musicStatus = MusicStatus::Stop;
        audio::Token currentlyPreviewedToken;
        std::string currentlyPreviewedPath;



@@ 48,7 47,7 @@ namespace gui
        std::function<void()> bottomBarRestoreFromTemporaryMode      = nullptr;
        void prepareOptionsNames();
        void applyCallbacks();
        std::vector<audio::Tags> getMusicFilesList();
        std::vector<tags::fetcher::Tags> getMusicFilesList();

      public:
        AlarmOptionsItem(app::Application *app,

M module-apps/application-music-player/ApplicationMusicPlayer.cpp => module-apps/application-music-player/ApplicationMusicPlayer.cpp +1 -1
@@ 31,7 31,7 @@ namespace app
        };
    } // namespace music_player::internal

    constexpr std::size_t applicationMusicPlayerStackSize = 4 * 1024;
    constexpr std::size_t applicationMusicPlayerStackSize = 5 * 1024;

    ApplicationMusicPlayer::ApplicationMusicPlayer(std::string name,
                                                   std::string parent,

M module-apps/application-music-player/CMakeLists.txt => module-apps/application-music-player/CMakeLists.txt +1 -0
@@ 46,6 46,7 @@ target_link_libraries(application-music-player
    PUBLIC
        apps-common
        module-audio
        tagsfetcher
)

if (${ENABLE_TESTS})

M module-apps/application-music-player/models/SongsRepository.cpp => module-apps/application-music-player/models/SongsRepository.cpp +8 -6
@@ 9,6 9,7 @@
#include <service-audio/AudioServiceName.hpp>
#include <time/ScopedTime.hpp>
#include <service-audio/AudioMessage.hpp>
#include <tags_fetcher/TagsFetcher.hpp>

#include <filesystem>



@@ 17,9 18,9 @@ namespace app::music_player
    ServiceAudioTagsFetcher::ServiceAudioTagsFetcher(Application *application) : application(application)
    {}

    std::optional<audio::Tags> ServiceAudioTagsFetcher::getFileTags(const std::string &filePath) const
    std::optional<tags::fetcher::Tags> ServiceAudioTagsFetcher::getFileTags(const std::string &filePath) const
    {
        return AudioServiceAPI::GetFileTags(application, filePath);
        return tags::fetcher::fetchTags(filePath);
    }

    SongsRepository::SongsRepository(std::unique_ptr<AbstractTagsFetcher> tagsFetcher, std::string musicFolderName)


@@ 45,14 46,15 @@ namespace app::music_player
                    }
                }
            }
            std::sort(musicFiles.begin(), musicFiles.end(), [](audio::Tags t1, audio::Tags t2) {
                return t1.filePath < t2.filePath;
            });
            std::sort(
                musicFiles.begin(), musicFiles.end(), [](const tags::fetcher::Tags &t1, const tags::fetcher::Tags &t2) {
                    return t1.filePath < t2.filePath;
                });
        }
        LOG_INFO("Total number of music files found: %u", static_cast<unsigned int>(musicFiles.size()));
    }

    std::vector<audio::Tags> SongsRepository::getMusicFilesList() const
    std::vector<tags::fetcher::Tags> SongsRepository::getMusicFilesList() const
    {
        return musicFiles;
    }

M module-apps/application-music-player/models/SongsRepository.hpp => module-apps/application-music-player/models/SongsRepository.hpp +6 -6
@@ 4,7 4,7 @@
#pragma once

#include <apps-common/Application.hpp>
#include <Audio/decoder/Decoder.hpp>
#include <tags_fetcher/TagsFetcher.hpp>
#include <purefs/filesystem_paths.hpp>

#include <memory>


@@ 21,7 21,7 @@ namespace app::music_player
      public:
        virtual ~AbstractTagsFetcher() noexcept = default;

        virtual std::optional<audio::Tags> getFileTags(const std::string &filePath) const = 0;
        virtual std::optional<tags::fetcher::Tags> getFileTags(const std::string &filePath) const = 0;
    };

    class ServiceAudioTagsFetcher : public AbstractTagsFetcher


@@ 29,7 29,7 @@ namespace app::music_player
      public:
        explicit ServiceAudioTagsFetcher(Application *application);

        std::optional<audio::Tags> getFileTags(const std::string &filePath) const final;
        std::optional<tags::fetcher::Tags> getFileTags(const std::string &filePath) const final;

      private:
        Application *application = nullptr;


@@ 41,7 41,7 @@ namespace app::music_player
        virtual ~AbstractSongsRepository() noexcept = default;

        virtual void scanMusicFilesList()                                          = 0;
        virtual std::vector<audio::Tags> getMusicFilesList() const                 = 0;
        virtual std::vector<tags::fetcher::Tags> getMusicFilesList() const         = 0;
        virtual std::size_t getFileIndex(const std::string &filePath) const        = 0;
        virtual std::string getNextFilePath(const std::string &filePath) const     = 0;
        virtual std::string getPreviousFilePath(const std::string &filePath) const = 0;


@@ 56,7 56,7 @@ namespace app::music_player
                                 std::string musicFolderName = purefs::dir::getUserDiskPath() / musicSubfolderName);

        void scanMusicFilesList() override;
        std::vector<audio::Tags> getMusicFilesList() const override;
        std::vector<tags::fetcher::Tags> getMusicFilesList() const override;
        std::size_t getFileIndex(const std::string &filePath) const override;
        std::string getNextFilePath(const std::string &filePath) const override;
        std::string getPreviousFilePath(const std::string &filePath) const override;


@@ 64,6 64,6 @@ namespace app::music_player
      private:
        std::unique_ptr<AbstractTagsFetcher> tagsFetcher;
        std::string musicFolderName;
        std::vector<audio::Tags> musicFiles;
        std::vector<tags::fetcher::Tags> musicFiles;
    };
} // namespace app::music_player

M module-apps/application-music-player/tests/MockSongsRepository.hpp => module-apps/application-music-player/tests/MockSongsRepository.hpp +1 -1
@@ 18,7 18,7 @@ namespace testing::app::music_player
    {
      public:
        MOCK_METHOD(void, scanMusicFilesList, (), (override));
        MOCK_METHOD(std::vector<audio::Tags>, getMusicFilesList, (), (const override));
        MOCK_METHOD(std::vector<tags::fetcher::Tags>, getMusicFilesList, (), (const override));
        MOCK_METHOD(std::size_t, getFileIndex, (const std::string &filePath), (const override));
        MOCK_METHOD(std::string, getNextFilePath, (const std::string &filePath), (const override));
        MOCK_METHOD(std::string, getPreviousFilePath, (const std::string &filePath), (const override));

M module-apps/application-music-player/tests/MockTagsFetcher.hpp => module-apps/application-music-player/tests/MockTagsFetcher.hpp +1 -1
@@ 15,6 15,6 @@ namespace testing::app::music_player
    class MockTagsFetcher : public ::app::music_player::AbstractTagsFetcher
    {
      public:
        MOCK_METHOD(std::optional<audio::Tags>, getFileTags, (const std::string &filePath), (const override));
        MOCK_METHOD(std::optional<tags::fetcher::Tags>, getFileTags, (const std::string &filePath), (const override));
    };
}; // namespace testing::app::music_player

M module-apps/application-music-player/tests/unittest_songrepository.cpp => module-apps/application-music-player/tests/unittest_songrepository.cpp +5 -14
@@ 7,7 7,7 @@
#include "MockTagsFetcher.hpp"

#include <models/SongsRepository.hpp>

#include <tags_fetcher/TagsFetcher.hpp>
#include <filesystem>
#include <fstream>
#include <stdexcept>


@@ 101,11 101,8 @@ TEST_F(SongsRepositoryFixture, ScanWithTagsReturn)
    auto rawMock         = tagsFetcherMock.get();
    auto repo = std::make_unique<app::music_player::SongsRepository>(std::move(tagsFetcherMock), musicDirPath);

    auto fooTags = ::audio::Tags();
    auto barTags = ::audio::Tags();

    fooTags.title = "foo";
    barTags.title = "bar";
    auto fooTags = tags::fetcher::Tags{"foo"};
    auto barTags = tags::fetcher::Tags{"foo"};

    ON_CALL(*rawMock, getFileTags(fs::path(musicDirPath / "foo").c_str())).WillByDefault(Return(fooTags));
    ON_CALL(*rawMock, getFileTags(fs::path(musicDirPath / "bar").c_str())).WillByDefault(Return(barTags));


@@ 125,14 122,8 @@ TEST_F(SongsRepositoryFixture, FileIndex)
    auto fooPath = musicDirPath / "foo";
    auto barPath = musicDirPath / "bar";

    auto fooTags = ::audio::Tags();
    auto barTags = ::audio::Tags();

    fooTags.title    = "foo";
    fooTags.filePath = fooPath.c_str();

    barTags.title    = "bar";
    barTags.filePath = barPath.c_str();
    auto fooTags = tags::fetcher::Tags(fooPath);
    auto barTags = tags::fetcher::Tags(barPath);

    ON_CALL(*rawMock, getFileTags(fs::path(musicDirPath / "foo").c_str())).WillByDefault(Return(fooTags));
    ON_CALL(*rawMock, getFileTags(fs::path(musicDirPath / "bar").c_str())).WillByDefault(Return(barTags));

M module-apps/application-music-player/tests/unittest_songsmodel.cpp => module-apps/application-music-player/tests/unittest_songsmodel.cpp +1 -1
@@ 41,6 41,6 @@ TEST(SongsModel, createDataNoSongs)
    auto model    = SongsModel(mockRepo);

    EXPECT_CALL(*mockRepo, scanMusicFilesList);
    EXPECT_CALL(*mockRepo, getMusicFilesList).WillRepeatedly(Return(std::vector<audio::Tags>()));
    EXPECT_CALL(*mockRepo, getMusicFilesList).WillRepeatedly(Return(std::vector<tags::fetcher::Tags>()));
    model.createData([](const std::string &) { return true; }, []() {}, [](const UTF8 &) {}, []() {});
}

M module-apps/application-settings/models/apps/SoundsModel.cpp => module-apps/application-settings/models/apps/SoundsModel.cpp +3 -4
@@ 9,6 9,7 @@
#include <ListView.hpp>
#include <purefs/filesystem_paths.hpp>
#include <service-audio/AudioServiceAPI.hpp>
#include <tags_fetcher/TagsFetcher.hpp>

SoundsModel::SoundsModel(std::shared_ptr<AbstractSoundsPlayer> soundsPlayer) : soundsPlayer{std::move(soundsPlayer)}
{}


@@ 105,10 106,8 @@ void SoundsModel::applyItems(const std::vector<std::filesystem::path> &sounds,
        }

        std::string itemTitle;
        auto fileTags = AudioServiceAPI::GetFileTags(app, sound);
        if (fileTags) {
            itemTitle = fileTags->title;
        }
        auto fileTags = tags::fetcher::fetchTags(sound);
        itemTitle     = fileTags.title;

        if (itemTitle.empty()) {
            itemTitle = sound.filename();

M module-audio/Audio/Audio.cpp => module-audio/Audio/Audio.cpp +0 -11
@@ 25,17 25,6 @@ namespace audio
        return currentOperation->GetPosition();
    }

    std::optional<Tags> Audio::GetFileTags(const char *filename)
    {
        auto ret = Decoder::Create(filename);
        if (ret == nullptr) {
            return {};
        }
        else {
            return *ret->fetchTags();
        };
    }

    audio::RetCode Audio::SendEvent(std::shared_ptr<Event> evt)
    {
        audioSinkState.UpdateState(evt);

M module-audio/Audio/Audio.hpp => module-audio/Audio/Audio.hpp +0 -2
@@ 49,8 49,6 @@ namespace audio
            return currentState;
        }

        static std::optional<Tags> GetFileTags(const char *filename);

        // Range 0-1
        audio::RetCode SetOutputVolume(Volume vol);


M module-audio/Audio/Operation/PlaybackOperation.cpp => module-audio/Audio/Operation/PlaybackOperation.cpp +1 -2
@@ 36,7 36,6 @@ namespace audio
        if (dec == nullptr) {
            throw AudioInitException("Error during initializing decoder", RetCode::FileDoesntExist);
        }
        tags        = dec->fetchTags();
        auto format = dec->getSourceFormat();
        LOG_DEBUG("Source format: %s", format.toString().c_str());



@@ 183,7 182,7 @@ namespace audio
        }

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

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

M module-audio/Audio/Operation/PlaybackOperation.hpp => module-audio/Audio/Operation/PlaybackOperation.hpp +0 -1
@@ 47,7 47,6 @@ namespace audio

        std::unique_ptr<Stream> dataStreamOut;
        std::unique_ptr<Decoder> dec;
        std::unique_ptr<Tags> tags;
        std::unique_ptr<StreamConnection> outputConnection;

        DecoderWorker::EndOfFileCallback endOfFileCallback;

M module-audio/Audio/decoder/Decoder.cpp => module-audio/Audio/decoder/Decoder.cpp +8 -44
@@ 11,13 11,13 @@
#include "fileref.h"
#include "tag.h"
#include "tfilestream.h"
#include <tags_fetcher/TagsFetcher.hpp>

namespace audio
{
    Decoder::Decoder(const char *fileName)
        : filePath(fileName), workerBuffer(std::make_unique<int16_t[]>(workerBufferSize)), tag(std::make_unique<Tags>())
        : filePath(fileName), workerBuffer(std::make_unique<int16_t[]>(workerBufferSize))
    {

        fd = std::fopen(fileName, "r");
        if (fd == NULL) {
            return;


@@ 26,6 26,8 @@ namespace audio
        std::fseek(fd, 0, SEEK_END);
        fileSize = std::ftell(fd);
        std::rewind(fd);

        tags = fetchTags();
    }

    Decoder::~Decoder()


@@ 39,46 41,9 @@ namespace audio
        }
    }

    std::unique_ptr<Tags> Decoder::fetchTags()
    std::unique_ptr<tags::fetcher::Tags> Decoder::fetchTags()
    {
        if (fd) {
            const auto inPos = std::ftell(fd);
            std::rewind(fd);
            TagLib::FileStream fileStream(fd);
            TagLib::FileRef tagReader(&fileStream);
            if (!tagReader.isNull() && tagReader.tag()) {
                TagLib::Tag *tags                   = tagReader.tag();
                TagLib::AudioProperties *properties = tagReader.audioProperties();

                constexpr auto unicode = true;
                tag->title             = tags->title().to8Bit(unicode);
                tag->artist            = tags->artist().to8Bit(unicode);
                tag->album             = tags->album().to8Bit(unicode);
                tag->genre             = tags->genre().to8Bit(unicode);
                tag->year   = std::to_string(tags->year());

                tag->total_duration_s = properties->length();
                tag->duration_min     = tag->total_duration_s / utils::secondsInMinute;
                tag->duration_hour    = tag->duration_min / utils::secondsInMinute;
                tag->duration_sec     = tag->total_duration_s % utils::secondsInMinute;
                tag->sample_rate      = properties->sampleRate();
                tag->num_channel      = properties->channels();
                tag->bitrate          = properties->bitrate();
            }
            std::fseek(fd, inPos, SEEK_SET);
        }

        tag->filePath.append(filePath);
        // If title tag empty fill it with raw file name
        if (tag->title.size() == 0) {
            if (const auto pos = filePath.rfind("/"); pos == std::string::npos) {
                tag->title.append(filePath);
            }
            else {
                tag->title.append(&filePath[pos + 1]);
            }
        }
        return std::make_unique<Tags>(*tag);
        return std::make_unique<tags::fetcher::Tags>(tags::fetcher::fetchTags(filePath));
    }

    std::unique_ptr<Decoder> Decoder::Create(const char *file)


@@ 127,8 92,8 @@ namespace audio
                std::make_unique<DecoderWorker>(_stream,
                                                this,
                                                endOfFileCallback,
                                                tag->num_channel == 1 ? DecoderWorker::ChannelMode::ForceStereo
                                                                      : DecoderWorker::ChannelMode::NoConversion);
                                                tags->num_channel == 1 ? DecoderWorker::ChannelMode::ForceStereo
                                                                       : DecoderWorker::ChannelMode::NoConversion);
            audioWorker->init();
            audioWorker->run();
        }


@@ 162,7 127,6 @@ namespace audio

    auto Decoder::getSourceFormat() -> AudioFormat
    {
        auto tags     = fetchTags();
        auto bitWidth = getBitWidth();
        // this is a decoder mono to stereo hack, will be removed when proper
        // transcoding implementation is added

M module-audio/Audio/decoder/Decoder.hpp => module-audio/Audio/decoder/Decoder.hpp +3 -50
@@ 17,6 17,7 @@

#include <cstring>
#include <cstdint>
#include <tags_fetcher/TagsFetcher.hpp>

namespace audio
{


@@ 26,53 27,6 @@ namespace audio
        constexpr inline auto stereoSound = 2;
    } // namespace channel

    struct Tags
    {

        /* Total audio duration in seconds */
        uint32_t total_duration_s = 0;
        /* Audio duration - hours part */
        uint32_t duration_hour = 0;
        /* Audio duration - minutes part */
        uint32_t duration_min = 0;
        /* Audio duration - seconds part */
        uint32_t duration_sec = 0;

        /* Sample rate */
        uint32_t sample_rate = 0;
        /* Number of channels */
        uint32_t num_channel = 0;
        /* bitrate */
        uint32_t bitrate = 0;

        std::string artist   = "";
        std::string genre    = "";
        std::string title    = "";
        std::string album    = "";
        std::string year     = "";
        std::string filePath = "";

        Tags()
        {}

        // Copy constructor
        Tags(const Tags &p2)
        {
            total_duration_s = p2.total_duration_s;
            duration_hour    = p2.duration_hour;
            duration_min     = p2.duration_min;
            duration_sec     = p2.duration_sec;
            sample_rate      = p2.sample_rate;
            num_channel      = p2.num_channel;
            artist           = p2.artist;
            genre            = p2.genre;
            title            = p2.title;
            album            = p2.album;
            year             = p2.year;
            filePath         = p2.filePath;
        }
    };

    class Decoder : public Source
    {



@@ 83,8 37,6 @@ namespace audio

        virtual uint32_t decode(uint32_t samplesToRead, int16_t *pcmData) = 0;

        std::unique_ptr<Tags> fetchTags();

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



@@ 120,6 72,7 @@ namespace audio

      protected:
        virtual auto getBitWidth() -> unsigned int = 0;
        virtual std::unique_ptr<tags::fetcher::Tags> fetchTags();

        void convertmono2stereo(int16_t *pcm, uint32_t samplecount);



@@ 135,7 88,7 @@ namespace audio

        // Worker buffer used for converting mono stream to stereo
        std::unique_ptr<int16_t[]> workerBuffer;
        std::unique_ptr<Tags> tag;
        std::unique_ptr<tags::fetcher::Tags> tags;
        bool isInitialized = false;

        // decoding worker

M module-audio/Audio/decoder/decoderFLAC.cpp => module-audio/Audio/decoder/decoderFLAC.cpp +2 -28
@@ 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 <Utils.hpp>


@@ 23,7 23,7 @@ namespace audio
            return;
        }

        flac = drflac_open_with_metadata(drflac_read, drflac_seek, drflac_meta, this);
        flac = drflac_open(drflac_read, drflac_seek, this);
        if (flac == NULL) {
            return;
        }


@@ 82,32 82,6 @@ namespace audio
        return !std::fseek(userdata->fd, offset, origin == drflac_seek_origin_start ? SEEK_SET : SEEK_CUR);
    }

    void decoderFLAC::drflac_meta(void *pUserData, drflac_metadata *pMetadata)
    {
        decoderFLAC *userdata = (decoderFLAC *)pUserData;

        if (pMetadata->type == DRFLAC_METADATA_BLOCK_TYPE_STREAMINFO) {
            userdata->tag->total_duration_s =
                (pMetadata->data.streaminfo.totalSampleCount / (pMetadata->data.streaminfo.bitsPerSample / 8)) /
                pMetadata->data.streaminfo.sampleRate;

            if (pMetadata->data.streaminfo.channels == 1) {
                userdata->tag->total_duration_s *= 2;
            }

            userdata->tag->duration_min  = userdata->tag->total_duration_s / utils::secondsInMinute;
            userdata->tag->duration_hour = userdata->tag->duration_min / utils::secondsInMinute;
            userdata->tag->duration_sec  = userdata->tag->total_duration_s % utils::secondsInMinute;
            userdata->tag->sample_rate   = pMetadata->data.streaminfo.sampleRate;
            userdata->tag->num_channel   = pMetadata->data.streaminfo.channels;

            userdata->sampleRate = pMetadata->data.streaminfo.sampleRate;
            userdata->chanNumber = pMetadata->data.streaminfo.channels;

            userdata->totalSamplesCount = pMetadata->data.streaminfo.totalSampleCount;
        }
    }

    auto decoderFLAC::getBitWidth() -> unsigned int
    {
        TagLib::FLAC::File flacFile(filePath.c_str());

M module-audio/Audio/decoder/decoderFLAC.hpp => module-audio/Audio/decoder/decoderFLAC.hpp +1 -4
@@ 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

#pragma once


@@ 55,9 55,6 @@ namespace audio
        // determined by the "origin" parameter which will be either drflac_seek_origin_start or
        // drflac_seek_origin_current.
        static drflac_bool32 drflac_seek(void *pUserData, int offset, drflac_seek_origin origin);

        // Use pMetadata->type to determine which metadata block is being handled and how to read the data.
        static void drflac_meta(void *pUserData, drflac_metadata *pMetadata);
    };

} // namespace audio

M module-audio/Audio/test/unittest_audio.cpp => module-audio/Audio/test/unittest_audio.cpp +17 -14
@@ 20,21 20,24 @@

using namespace audio;

TEST_CASE("Test audio tags")
TEST_CASE("Audio Decoder")
{
    SECTION(" Encoder tests ")
    {
        std::vector<std::string> testExtensions = {"flac", "wav", "mp3"};
        for (auto ext : testExtensions) {
            auto dec = audio::Decoder::Create(("testfiles/audio." + ext).c_str());
            REQUIRE(dec);
            auto tags = dec->fetchTags();
            REQUIRE(tags);
            REQUIRE(tags->title == ext + " Test track title - łąki");
            REQUIRE(tags->artist == ext + " Test artist name - łąki");
            REQUIRE(tags->album == ext + " Test album title - łąki");
            REQUIRE(tags->year == "2020");
        }
    std::vector<std::string> testExtensions = {"flac", "wav", "mp3"};
    for (auto ext : testExtensions) {
        auto dec = audio::Decoder::Create(("testfiles/audio." + ext).c_str());
        REQUIRE(dec);
    }
}

TEST_CASE(" Tags fetcher ")
{
    std::vector<std::string> testExtensions = {"flac", "wav", "mp3"};
    for (auto ext : testExtensions) {
        auto tags = tags::fetcher::fetchTags(("testfiles/audio." + ext).c_str());
        REQUIRE(tags.title == ext + " Test track title - łąki");
        REQUIRE(tags.artist == ext + " Test artist name - łąki");
        REQUIRE(tags.album == ext + " Test album title - łąki");
        REQUIRE(tags.year == "2020");
    }
}


M module-audio/CMakeLists.txt => module-audio/CMakeLists.txt +3 -0
@@ 72,8 72,11 @@ target_link_libraries(${PROJECT_NAME}
                minimp3::minimp3
        PRIVATE
                math
                tagsfetcher
)

add_subdirectory(tags_fetcher)

if (${ENABLE_TESTS})
    add_subdirectory(Audio/test)
endif ()

M module-audio/README.md => module-audio/README.md +0 -4
@@ 136,10 136,6 @@ User should be aware of following constraints:
Failing to adhere will end in returning appropriate error.  


##### std::optional<Tags> GetFileTags(const char *filename);

Fetches metadata of specified audio file. It can be invoked in any time. If file exists and is supported, audio meta tags structure will be returned.

# Missing features
#### Bluetooth audio device support
Currently support for BT audio devices is marginal. There are some code parts related to BT but they absolutely cannot be treated as target implementation. Adding BT support in my opinion should be split into several steps:

A module-audio/tags_fetcher/CMakeLists.txt => module-audio/tags_fetcher/CMakeLists.txt +20 -0
@@ 0,0 1,20 @@
add_library(tagsfetcher STATIC)

module_is_test_entity(tagsfetcher)

target_sources(tagsfetcher
        PRIVATE
        TagsFetcher.cpp
        PUBLIC
        TagsFetcher.hpp)

target_include_directories(ucs2
    PUBLIC
        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
)

target_link_libraries(tagsfetcher
    PRIVATE
    tag
    Microsoft.GSL::GSL
)

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

#include "TagsFetcher.hpp"

#include <gsl/util>
#include <Utils.hpp>

#include "fileref.h"
#include "tag.h"
#include "tfilestream.h"

namespace tags::fetcher
{
    std::optional<Tags> fetchTagsInternal(std::string filePath)
    {
        TagLib::FileRef tagReader(filePath.c_str());
        if (!tagReader.isNull() && tagReader.tag()) {
            TagLib::Tag *tags                   = tagReader.tag();
            TagLib::AudioProperties *properties = tagReader.audioProperties();

            constexpr auto unicode = true;

            auto getTitle = [&]() -> std::string {
                const auto title = tags->title().to8Bit(unicode);
                // If title tag empty fill it with raw file name
                if (title.size() == 0) {
                    if (const auto pos = filePath.rfind("/"); pos == std::string::npos) {
                        return filePath;
                    }
                    else {
                        return &filePath[pos + 1];
                    }
                }
                return title;
            };

            const auto artist = tags->artist().to8Bit(unicode);
            const auto album  = tags->album().to8Bit(unicode);
            const auto genre  = tags->genre().to8Bit(unicode);
            const auto year   = std::to_string(tags->year());

            const uint32_t total_duration_s = properties->length();
            const uint32_t duration_min     = total_duration_s / utils::secondsInMinute;
            const uint32_t duration_hour    = duration_min / utils::secondsInMinute;
            const uint32_t duration_sec     = total_duration_s % utils::secondsInMinute;
            const uint32_t sample_rate      = properties->sampleRate();
            const uint32_t num_channel      = properties->channels();
            const uint32_t bitrate          = properties->bitrate();
            const auto title                = getTitle();

            return Tags{total_duration_s,
                        duration_hour,
                        duration_min,
                        duration_sec,
                        sample_rate,
                        num_channel,
                        bitrate,
                        artist,
                        genre,
                        title,
                        album,
                        year,
                        filePath};
        }

        return {};
    }

    Tags fetchTags(std::string filePath)
    {
        return fetchTagsInternal(filePath).value_or(Tags{filePath});
    }

} // namespace tags::fetcher

A module-audio/tags_fetcher/TagsFetcher.hpp => module-audio/tags_fetcher/TagsFetcher.hpp +63 -0
@@ 0,0 1,63 @@

// 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 <cstdint>
#include <optional>
#include <string>

namespace tags::fetcher
{
    struct Tags
    {

        /* Total audio duration in seconds */
        uint32_t total_duration_s = 0;
        /* Audio duration - hours part */
        uint32_t duration_hour = 0;
        /* Audio duration - minutes part */
        uint32_t duration_min = 0;
        /* Audio duration - seconds part */
        uint32_t duration_sec = 0;

        /* Sample rate */
        uint32_t sample_rate = 0;
        /* Number of channels */
        uint32_t num_channel = 0;
        /* bitrate */
        uint32_t bitrate = 0;

        std::string artist;
        std::string genre;
        std::string album;
        std::string year;
        std::string filePath;
        std::string title;

        Tags() = default;
        Tags(uint32_t total_duration_s,
             uint32_t duration_hour,
             uint32_t duration_min,
             uint32_t duration_sec,
             uint32_t sample_rate,
             uint32_t num_channel,
             uint32_t bitrate,
             std::string artist,
             std::string genre,
             std::string title,
             std::string album,
             std::string year,
             std::string filePath)
            : total_duration_s{total_duration_s}, duration_hour{duration_hour}, duration_min{duration_min},
              duration_sec{duration_sec}, sample_rate{sample_rate}, num_channel{num_channel}, bitrate{bitrate},
              artist{artist}, genre{genre}, album{album}, year{year}, filePath{filePath}, title{title}
        {}

        explicit Tags(std::string filePath) : filePath{filePath}, title{filePath}
        {}
    };

    Tags fetchTags(std::string fileName);
} // namespace tags::fetcher

M module-services/service-audio/AudioServiceAPI.cpp => module-services/service-audio/AudioServiceAPI.cpp +0 -13
@@ 200,19 200,6 @@ namespace AudioServiceAPI
        }
    }

    std::optional<Tags> GetFileTags(sys::Service *serv, const std::string &fileName)
    {
        auto msg = std::make_shared<AudioGetFileTagsRequest>(fileName);

        auto resp = SendAudioRequest(serv, msg);
        if (resp->retCode == audio::RetCode::Success) {
            return resp->tags;
        }
        else {
            return std::nullopt;
        }
    }

    bool KeyPressed(sys::Service *serv, const int step)
    {
        auto msg = std::make_shared<AudioKeyPressedRequest>(step);

M module-services/service-audio/ServiceAudio.cpp => module-services/service-audio/ServiceAudio.cpp +0 -12
@@ 292,14 292,6 @@ void ServiceAudio::EnableContinuousVibration(std::optional<audio::AudioMux::Inpu
    }
}

std::unique_ptr<AudioResponseMessage> ServiceAudio::HandleGetFileTags(const std::string &fileName)
{
    if (auto tag = Audio::GetFileTags(fileName.c_str())) {
        return std::make_unique<AudioResponseMessage>(RetCode::Success, tag.value());
    }
    return std::make_unique<AudioResponseMessage>(RetCode::FileDoesntExist);
}

std::unique_ptr<AudioResponseMessage> ServiceAudio::HandlePause(const Token &token)
{
    auto input = audioMux.GetInput(token);


@@ 617,10 609,6 @@ sys::MessagePointer ServiceAudio::DataReceivedHandler(sys::DataMessage *msgl, sy
        auto *msg   = static_cast<AudioResumeRequest *>(msgl);
        responseMsg = HandleResume(msg->token);
    }
    else if (msgType == typeid(AudioGetFileTagsRequest)) {
        auto *msg   = static_cast<AudioGetFileTagsRequest *>(msgl);
        responseMsg = HandleGetFileTags(msg->fileName);
    }
    else if (msgType == typeid(AudioEventRequest)) {
        auto *msg   = static_cast<AudioEventRequest *>(msgl);
        responseMsg = HandleSendEvent(msg->getEvent());

M module-services/service-audio/include/service-audio/AudioMessage.hpp => module-services/service-audio/include/service-audio/AudioMessage.hpp +2 -17
@@ 24,17 24,11 @@ class AudioMessage : public sys::DataMessage
class AudioResponseMessage : public sys::ResponseMessage
{
  public:
    explicit AudioResponseMessage(audio::RetCode retCode  = audio::RetCode::Success,
                                  const audio::Tags &tags = {},
                                  const std::string &val  = {})
        : sys::ResponseMessage(), retCode(retCode), tags(tags), val(val)
    {}

    AudioResponseMessage(audio::RetCode retCode, const std::string &val) : AudioResponseMessage(retCode, {}, val)
    explicit AudioResponseMessage(audio::RetCode retCode = audio::RetCode::Success, const std::string &val = {})
        : sys::ResponseMessage(), retCode(retCode), val(val)
    {}

    const audio::RetCode retCode = audio::RetCode::Success;
    audio::Tags tags             = {};
    std::string val;
};



@@ 229,15 223,6 @@ class AudioResumeResponse : public AudioResponseMessage
    const audio::Token token;
};

class AudioGetFileTagsRequest : public AudioMessage
{
  public:
    explicit AudioGetFileTagsRequest(const std::string &fileName) : fileName(fileName)
    {}

    const std::string fileName;
};

class AudioEventRequest : public AudioMessage
{
  public:

M module-services/service-audio/include/service-audio/AudioServiceAPI.hpp => module-services/service-audio/include/service-audio/AudioServiceAPI.hpp +0 -8
@@ 122,14 122,6 @@ namespace AudioServiceAPI
    bool SendEvent(sys::Service *serv,
                   audio::EventType evt,
                   audio::Event::DeviceState state = audio::Event::DeviceState::Connected);
    /**
     * @brief Attempts to parse audio file for metatags.
     *
     * @param serv Requesting service.
     * @param fileName Path to file to be parsed.
     * @return audio::Tags on success, std::nullopt on failure
     */
    std::optional<audio::Tags> GetFileTags(sys::Service *serv, const std::string &fileName);

    /** @brief Sets vibrations setting state
     *

M module-services/service-audio/include/service-audio/ServiceAudio.hpp => module-services/service-audio/include/service-audio/ServiceAudio.hpp +0 -1
@@ 79,7 79,6 @@ class ServiceAudio : public sys::Service
    auto HandlePause(const audio::Token &token) -> std::unique_ptr<AudioResponseMessage>;
    auto HandlePause(std::optional<audio::AudioMux::Input *> input) -> std::unique_ptr<AudioResponseMessage>;
    auto HandleResume(const audio::Token &token) -> std::unique_ptr<AudioResponseMessage>;
    auto HandleGetFileTags(const std::string &fileName) -> std::unique_ptr<AudioResponseMessage>;
    void HandleEOF(const audio::Token &token);
    auto HandleKeyPressed(const int step) -> sys::MessagePointer;
    void MuteCurrentOperation();