parent
344a46d066
commit
b242ba9986
@ -1,163 +0,0 @@ |
|||||||
/*
|
|
||||||
* Copyright 2023 jacqueline <me@jacqueline.id.au> |
|
||||||
* |
|
||||||
* SPDX-License-Identifier: GPL-3.0-only |
|
||||||
*/ |
|
||||||
|
|
||||||
#include "audio/fatfs_audio_input.hpp" |
|
||||||
|
|
||||||
#include <algorithm> |
|
||||||
#include <climits> |
|
||||||
#include <cstddef> |
|
||||||
#include <cstdint> |
|
||||||
#include <functional> |
|
||||||
#include <future> |
|
||||||
#include <memory> |
|
||||||
#include <mutex> |
|
||||||
#include <span> |
|
||||||
#include <string> |
|
||||||
#include <variant> |
|
||||||
|
|
||||||
#include "audio/readahead_source.hpp" |
|
||||||
#include "esp_heap_caps.h" |
|
||||||
#include "esp_log.h" |
|
||||||
#include "ff.h" |
|
||||||
#include "freertos/portmacro.h" |
|
||||||
#include "freertos/projdefs.h" |
|
||||||
|
|
||||||
#include "audio/audio_events.hpp" |
|
||||||
#include "audio/audio_fsm.hpp" |
|
||||||
#include "audio/audio_source.hpp" |
|
||||||
#include "audio/fatfs_source.hpp" |
|
||||||
#include "codec.hpp" |
|
||||||
#include "database/future_fetcher.hpp" |
|
||||||
#include "database/tag_parser.hpp" |
|
||||||
#include "database/track.hpp" |
|
||||||
#include "drivers/spi.hpp" |
|
||||||
#include "events/event_queue.hpp" |
|
||||||
#include "tasks.hpp" |
|
||||||
#include "types.hpp" |
|
||||||
|
|
||||||
[[maybe_unused]] static const char* kTag = "SRC"; |
|
||||||
|
|
||||||
namespace audio { |
|
||||||
|
|
||||||
FatfsAudioInput::FatfsAudioInput(database::ITagParser& tag_parser, |
|
||||||
tasks::WorkerPool& bg_worker) |
|
||||||
: IAudioSource(), |
|
||||||
tag_parser_(tag_parser), |
|
||||||
bg_worker_(bg_worker), |
|
||||||
new_stream_mutex_(), |
|
||||||
new_stream_(), |
|
||||||
has_new_stream_(false) {} |
|
||||||
|
|
||||||
FatfsAudioInput::~FatfsAudioInput() {} |
|
||||||
|
|
||||||
auto FatfsAudioInput::SetPath(std::optional<std::string> path) -> void { |
|
||||||
if (path) { |
|
||||||
SetPath(*path); |
|
||||||
} else { |
|
||||||
SetPath(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
auto FatfsAudioInput::SetPath(const std::string& path, |
|
||||||
uint32_t offset) -> void { |
|
||||||
std::lock_guard<std::mutex> guard{new_stream_mutex_}; |
|
||||||
if (OpenFile(path, offset)) { |
|
||||||
has_new_stream_ = true; |
|
||||||
has_new_stream_.notify_one(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
auto FatfsAudioInput::SetPath() -> void { |
|
||||||
std::lock_guard<std::mutex> guard{new_stream_mutex_}; |
|
||||||
new_stream_.reset(); |
|
||||||
has_new_stream_ = true; |
|
||||||
has_new_stream_.notify_one(); |
|
||||||
} |
|
||||||
|
|
||||||
auto FatfsAudioInput::HasNewStream() -> bool { |
|
||||||
return has_new_stream_; |
|
||||||
} |
|
||||||
|
|
||||||
auto FatfsAudioInput::NextStream() -> std::shared_ptr<TaggedStream> { |
|
||||||
while (true) { |
|
||||||
has_new_stream_.wait(false); |
|
||||||
|
|
||||||
{ |
|
||||||
std::lock_guard<std::mutex> guard{new_stream_mutex_}; |
|
||||||
if (!has_new_stream_.exchange(false)) { |
|
||||||
// If the new stream went away, then we need to go back to waiting.
|
|
||||||
continue; |
|
||||||
} |
|
||||||
|
|
||||||
if (new_stream_ == nullptr) { |
|
||||||
continue; |
|
||||||
} |
|
||||||
|
|
||||||
auto stream = new_stream_; |
|
||||||
new_stream_ = nullptr; |
|
||||||
return stream; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
auto FatfsAudioInput::OpenFile(const std::string& path, |
|
||||||
uint32_t offset) -> bool { |
|
||||||
ESP_LOGI(kTag, "opening file %s", path.c_str()); |
|
||||||
|
|
||||||
auto tags = tag_parser_.ReadAndParseTags(path); |
|
||||||
if (!tags) { |
|
||||||
ESP_LOGE(kTag, "failed to read tags"); |
|
||||||
return false; |
|
||||||
} |
|
||||||
if (!tags->title()) { |
|
||||||
tags->title(path); |
|
||||||
} |
|
||||||
|
|
||||||
auto stream_type = ContainerToStreamType(tags->encoding()); |
|
||||||
if (!stream_type.has_value()) { |
|
||||||
ESP_LOGE(kTag, "couldn't match container to stream"); |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
std::unique_ptr<FIL> file = std::make_unique<FIL>(); |
|
||||||
FRESULT res; |
|
||||||
|
|
||||||
{ |
|
||||||
auto lock = drivers::acquire_spi(); |
|
||||||
res = f_open(file.get(), path.c_str(), FA_READ); |
|
||||||
} |
|
||||||
|
|
||||||
if (res != FR_OK) { |
|
||||||
ESP_LOGE(kTag, "failed to open file! res: %i", res); |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
auto source = |
|
||||||
std::make_unique<FatfsSource>(stream_type.value(), std::move(file)); |
|
||||||
new_stream_.reset(new TaggedStream(tags, std::move(source), path, offset)); |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
auto FatfsAudioInput::ContainerToStreamType(database::Container enc) |
|
||||||
-> std::optional<codecs::StreamType> { |
|
||||||
switch (enc) { |
|
||||||
case database::Container::kMp3: |
|
||||||
return codecs::StreamType::kMp3; |
|
||||||
case database::Container::kWav: |
|
||||||
return codecs::StreamType::kWav; |
|
||||||
case database::Container::kOgg: |
|
||||||
return codecs::StreamType::kVorbis; |
|
||||||
case database::Container::kFlac: |
|
||||||
return codecs::StreamType::kFlac; |
|
||||||
case database::Container::kOpus: |
|
||||||
return codecs::StreamType::kOpus; |
|
||||||
case database::Container::kUnsupported: |
|
||||||
default: |
|
||||||
return {}; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} // namespace audio
|
|
@ -1,66 +0,0 @@ |
|||||||
/*
|
|
||||||
* Copyright 2023 jacqueline <me@jacqueline.id.au> |
|
||||||
* |
|
||||||
* SPDX-License-Identifier: GPL-3.0-only |
|
||||||
*/ |
|
||||||
|
|
||||||
#pragma once |
|
||||||
|
|
||||||
#include <cstddef> |
|
||||||
#include <cstdint> |
|
||||||
#include <future> |
|
||||||
#include <memory> |
|
||||||
#include <string> |
|
||||||
|
|
||||||
#include "ff.h" |
|
||||||
#include "freertos/portmacro.h" |
|
||||||
|
|
||||||
#include "audio/audio_source.hpp" |
|
||||||
#include "codec.hpp" |
|
||||||
#include "database/future_fetcher.hpp" |
|
||||||
#include "database/tag_parser.hpp" |
|
||||||
#include "tasks.hpp" |
|
||||||
#include "types.hpp" |
|
||||||
|
|
||||||
namespace audio { |
|
||||||
|
|
||||||
/*
|
|
||||||
* Audio source that fetches data from a FatFs (or exfat i guess) filesystem. |
|
||||||
* |
|
||||||
* All public methods are safe to call from any task. |
|
||||||
*/ |
|
||||||
class FatfsAudioInput : public IAudioSource { |
|
||||||
public: |
|
||||||
explicit FatfsAudioInput(database::ITagParser&, tasks::WorkerPool&); |
|
||||||
~FatfsAudioInput(); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Immediately cease reading any current source, and begin reading from the |
|
||||||
* given file path. |
|
||||||
*/ |
|
||||||
auto SetPath(std::optional<std::string>) -> void; |
|
||||||
auto SetPath(const std::string&, uint32_t offset = 0) -> void; |
|
||||||
auto SetPath() -> void; |
|
||||||
|
|
||||||
auto HasNewStream() -> bool override; |
|
||||||
auto NextStream() -> std::shared_ptr<TaggedStream> override; |
|
||||||
|
|
||||||
FatfsAudioInput(const FatfsAudioInput&) = delete; |
|
||||||
FatfsAudioInput& operator=(const FatfsAudioInput&) = delete; |
|
||||||
|
|
||||||
private: |
|
||||||
auto OpenFile(const std::string& path, uint32_t offset) -> bool; |
|
||||||
|
|
||||||
auto ContainerToStreamType(database::Container) |
|
||||||
-> std::optional<codecs::StreamType>; |
|
||||||
|
|
||||||
database::ITagParser& tag_parser_; |
|
||||||
tasks::WorkerPool& bg_worker_; |
|
||||||
|
|
||||||
std::mutex new_stream_mutex_; |
|
||||||
std::shared_ptr<TaggedStream> new_stream_; |
|
||||||
|
|
||||||
std::atomic<bool> has_new_stream_; |
|
||||||
}; |
|
||||||
|
|
||||||
} // namespace audio
|
|
@ -0,0 +1,104 @@ |
|||||||
|
/*
|
||||||
|
* Copyright 2023 jacqueline <me@jacqueline.id.au> |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: GPL-3.0-only |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "audio/fatfs_stream_factory.hpp" |
||||||
|
|
||||||
|
#include <cstdint> |
||||||
|
#include <memory> |
||||||
|
#include <string> |
||||||
|
|
||||||
|
#include "database/database.hpp" |
||||||
|
#include "esp_log.h" |
||||||
|
#include "ff.h" |
||||||
|
#include "freertos/portmacro.h" |
||||||
|
#include "freertos/projdefs.h" |
||||||
|
|
||||||
|
#include "audio/audio_source.hpp" |
||||||
|
#include "audio/fatfs_source.hpp" |
||||||
|
#include "codec.hpp" |
||||||
|
#include "database/tag_parser.hpp" |
||||||
|
#include "database/track.hpp" |
||||||
|
#include "drivers/spi.hpp" |
||||||
|
#include "system_fsm/service_locator.hpp" |
||||||
|
#include "tasks.hpp" |
||||||
|
#include "types.hpp" |
||||||
|
|
||||||
|
[[maybe_unused]] static const char* kTag = "SRC"; |
||||||
|
|
||||||
|
namespace audio { |
||||||
|
|
||||||
|
FatfsStreamFactory::FatfsStreamFactory(system_fsm::ServiceLocator& services) |
||||||
|
: services_(services) {} |
||||||
|
|
||||||
|
auto FatfsStreamFactory::create(database::TrackId id, uint32_t offset) |
||||||
|
-> std::shared_ptr<TaggedStream> { |
||||||
|
auto db = services_.database().lock(); |
||||||
|
if (!db) { |
||||||
|
return {}; |
||||||
|
} |
||||||
|
auto path = db->getTrackPath(id); |
||||||
|
if (!path) { |
||||||
|
return {}; |
||||||
|
} |
||||||
|
return create(*path, offset); |
||||||
|
} |
||||||
|
|
||||||
|
auto FatfsStreamFactory::create(std::string path, uint32_t offset) |
||||||
|
-> std::shared_ptr<TaggedStream> { |
||||||
|
auto tags = services_.tag_parser().ReadAndParseTags(path); |
||||||
|
if (!tags) { |
||||||
|
ESP_LOGE(kTag, "failed to read tags"); |
||||||
|
return {}; |
||||||
|
} |
||||||
|
|
||||||
|
if (!tags->title()) { |
||||||
|
tags->title(path); |
||||||
|
} |
||||||
|
|
||||||
|
auto stream_type = ContainerToStreamType(tags->encoding()); |
||||||
|
if (!stream_type.has_value()) { |
||||||
|
ESP_LOGE(kTag, "couldn't match container to stream"); |
||||||
|
return {}; |
||||||
|
} |
||||||
|
|
||||||
|
std::unique_ptr<FIL> file = std::make_unique<FIL>(); |
||||||
|
FRESULT res; |
||||||
|
|
||||||
|
{ |
||||||
|
auto lock = drivers::acquire_spi(); |
||||||
|
res = f_open(file.get(), path.c_str(), FA_READ); |
||||||
|
} |
||||||
|
|
||||||
|
if (res != FR_OK) { |
||||||
|
ESP_LOGE(kTag, "failed to open file! res: %i", res); |
||||||
|
return {}; |
||||||
|
} |
||||||
|
|
||||||
|
return std::make_shared<TaggedStream>( |
||||||
|
tags, std::make_unique<FatfsSource>(stream_type.value(), std::move(file)), |
||||||
|
path, offset); |
||||||
|
} |
||||||
|
|
||||||
|
auto FatfsStreamFactory::ContainerToStreamType(database::Container enc) |
||||||
|
-> std::optional<codecs::StreamType> { |
||||||
|
switch (enc) { |
||||||
|
case database::Container::kMp3: |
||||||
|
return codecs::StreamType::kMp3; |
||||||
|
case database::Container::kWav: |
||||||
|
return codecs::StreamType::kWav; |
||||||
|
case database::Container::kOgg: |
||||||
|
return codecs::StreamType::kVorbis; |
||||||
|
case database::Container::kFlac: |
||||||
|
return codecs::StreamType::kFlac; |
||||||
|
case database::Container::kOpus: |
||||||
|
return codecs::StreamType::kOpus; |
||||||
|
case database::Container::kUnsupported: |
||||||
|
default: |
||||||
|
return {}; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace audio
|
@ -0,0 +1,53 @@ |
|||||||
|
/*
|
||||||
|
* Copyright 2023 jacqueline <me@jacqueline.id.au> |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: GPL-3.0-only |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <stdint.h> |
||||||
|
#include <cstddef> |
||||||
|
#include <cstdint> |
||||||
|
#include <future> |
||||||
|
#include <memory> |
||||||
|
#include <string> |
||||||
|
|
||||||
|
#include "database/database.hpp" |
||||||
|
#include "database/track.hpp" |
||||||
|
#include "ff.h" |
||||||
|
#include "freertos/portmacro.h" |
||||||
|
|
||||||
|
#include "audio/audio_source.hpp" |
||||||
|
#include "codec.hpp" |
||||||
|
#include "database/future_fetcher.hpp" |
||||||
|
#include "database/tag_parser.hpp" |
||||||
|
#include "system_fsm/service_locator.hpp" |
||||||
|
#include "tasks.hpp" |
||||||
|
#include "types.hpp" |
||||||
|
|
||||||
|
namespace audio { |
||||||
|
|
||||||
|
/*
|
||||||
|
* Utility to create streams that read from files on the sd card. |
||||||
|
*/ |
||||||
|
class FatfsStreamFactory { |
||||||
|
public: |
||||||
|
explicit FatfsStreamFactory(system_fsm::ServiceLocator&); |
||||||
|
|
||||||
|
auto create(database::TrackId, uint32_t offset = 0) |
||||||
|
-> std::shared_ptr<TaggedStream>; |
||||||
|
auto create(std::string, uint32_t offset = 0) |
||||||
|
-> std::shared_ptr<TaggedStream>; |
||||||
|
|
||||||
|
FatfsStreamFactory(const FatfsStreamFactory&) = delete; |
||||||
|
FatfsStreamFactory& operator=(const FatfsStreamFactory&) = delete; |
||||||
|
|
||||||
|
private: |
||||||
|
auto ContainerToStreamType(database::Container) |
||||||
|
-> std::optional<codecs::StreamType>; |
||||||
|
|
||||||
|
system_fsm::ServiceLocator& services_; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace audio
|
Loading…
Reference in new issue