parent
67caeb6e3c
commit
d8fc77101d
@ -0,0 +1,70 @@ |
|||||||
|
/*
|
||||||
|
* Copyright 2023 jacqueline <me@jacqueline.id.au> |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: GPL-3.0-only |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "fatfs_source.hpp" |
||||||
|
#include <sys/_stdint.h> |
||||||
|
|
||||||
|
#include <cstddef> |
||||||
|
#include <cstdint> |
||||||
|
#include <memory> |
||||||
|
|
||||||
|
#include "esp_log.h" |
||||||
|
#include "ff.h" |
||||||
|
|
||||||
|
#include "audio_source.hpp" |
||||||
|
#include "codec.hpp" |
||||||
|
#include "types.hpp" |
||||||
|
|
||||||
|
namespace audio { |
||||||
|
|
||||||
|
static constexpr char kTag[] = "fatfs_src"; |
||||||
|
|
||||||
|
FatfsSource::FatfsSource(codecs::StreamType t, std::unique_ptr<FIL> file) |
||||||
|
: IStream(t), file_(std::move(file)) {} |
||||||
|
|
||||||
|
FatfsSource::~FatfsSource() { |
||||||
|
f_close(file_.get()); |
||||||
|
} |
||||||
|
|
||||||
|
auto FatfsSource::Read(cpp::span<std::byte> dest) -> ssize_t { |
||||||
|
if (f_eof(file_.get())) { |
||||||
|
ESP_LOGI(kTag, "read from empty file"); |
||||||
|
return 0; |
||||||
|
} |
||||||
|
UINT bytes_read = 0; |
||||||
|
FRESULT res = f_read(file_.get(), dest.data(), dest.size(), &bytes_read); |
||||||
|
if (res != FR_OK) { |
||||||
|
ESP_LOGE(kTag, "error reading from file"); |
||||||
|
return -1; |
||||||
|
} |
||||||
|
ESP_LOGI(kTag, "read %u bytes into %p (%u)", bytes_read, dest.data(), |
||||||
|
dest.size_bytes()); |
||||||
|
return bytes_read; |
||||||
|
} |
||||||
|
|
||||||
|
auto FatfsSource::CanSeek() -> bool { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
auto FatfsSource::SeekTo(int64_t destination, SeekFrom from) -> void { |
||||||
|
ESP_LOGI(kTag, "seeking to %llu", destination); |
||||||
|
switch (from) { |
||||||
|
case SeekFrom::kStartOfStream: |
||||||
|
f_lseek(file_.get(), destination); |
||||||
|
break; |
||||||
|
case SeekFrom::kEndOfStream: |
||||||
|
f_lseek(file_.get(), f_size(file_.get()) + destination); |
||||||
|
break; |
||||||
|
case SeekFrom::kCurrentPosition: |
||||||
|
f_lseek(file_.get(), f_tell(file_.get()) + destination); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
auto FatfsSource::CurrentPosition() -> int64_t { |
||||||
|
return f_tell(file_.get()); |
||||||
|
} |
||||||
|
} // namespace audio
|
@ -0,0 +1,44 @@ |
|||||||
|
/*
|
||||||
|
* Copyright 2023 jacqueline <me@jacqueline.id.au> |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: GPL-3.0-only |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <cstddef> |
||||||
|
#include <cstdint> |
||||||
|
#include <memory> |
||||||
|
|
||||||
|
#include "codec.hpp" |
||||||
|
#include "ff.h" |
||||||
|
|
||||||
|
#include "audio_source.hpp" |
||||||
|
|
||||||
|
namespace audio { |
||||||
|
|
||||||
|
/*
|
||||||
|
* Handles coordination with a persistent background task to asynchronously |
||||||
|
* read files from disk into a StreamBuffer. |
||||||
|
*/ |
||||||
|
class FatfsSource : public codecs::IStream { |
||||||
|
public: |
||||||
|
FatfsSource(codecs::StreamType, std::unique_ptr<FIL> file); |
||||||
|
~FatfsSource(); |
||||||
|
|
||||||
|
auto Read(cpp::span<std::byte> dest) -> ssize_t override; |
||||||
|
|
||||||
|
auto CanSeek() -> bool override; |
||||||
|
|
||||||
|
auto SeekTo(int64_t destination, SeekFrom from) -> void override; |
||||||
|
|
||||||
|
auto CurrentPosition() -> int64_t override; |
||||||
|
|
||||||
|
FatfsSource(const FatfsSource&) = delete; |
||||||
|
FatfsSource& operator=(const FatfsSource&) = delete; |
||||||
|
|
||||||
|
private: |
||||||
|
std::unique_ptr<FIL> file_; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace audio
|
@ -0,0 +1,37 @@ |
|||||||
|
/*
|
||||||
|
* Copyright 2023 jacqueline <me@jacqueline.id.au> |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: GPL-3.0-only |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <cstddef> |
||||||
|
#include <cstdint> |
||||||
|
#include <functional> |
||||||
|
|
||||||
|
#include "span.hpp" |
||||||
|
|
||||||
|
#include "codec.hpp" |
||||||
|
|
||||||
|
namespace codecs { |
||||||
|
|
||||||
|
class SourceBuffer { |
||||||
|
public: |
||||||
|
SourceBuffer(); |
||||||
|
~SourceBuffer(); |
||||||
|
|
||||||
|
auto Refill(IStream* src) -> bool; |
||||||
|
auto AddBytes(std::function<size_t(cpp::span<std::byte>)> writer) -> void; |
||||||
|
auto ConsumeBytes(std::function<size_t(cpp::span<std::byte>)> reader) -> void; |
||||||
|
|
||||||
|
SourceBuffer(const SourceBuffer&) = delete; |
||||||
|
SourceBuffer& operator=(const SourceBuffer&) = delete; |
||||||
|
|
||||||
|
private: |
||||||
|
const cpp::span<std::byte> buffer_; |
||||||
|
size_t bytes_in_buffer_; |
||||||
|
size_t offset_of_bytes_; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace codecs
|
@ -0,0 +1,75 @@ |
|||||||
|
/*
|
||||||
|
* Copyright 2023 jacqueline <me@jacqueline.id.au> |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: GPL-3.0-only |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "source_buffer.hpp" |
||||||
|
#include <sys/_stdint.h> |
||||||
|
|
||||||
|
#include <algorithm> |
||||||
|
#include <cstring> |
||||||
|
|
||||||
|
#include "esp_heap_caps.h" |
||||||
|
#include "esp_log.h" |
||||||
|
|
||||||
|
#include "codec.hpp" |
||||||
|
|
||||||
|
namespace codecs { |
||||||
|
|
||||||
|
static constexpr char kTag[] = "dec_buf"; |
||||||
|
static constexpr size_t kBufferSize = 1024 * 8; |
||||||
|
|
||||||
|
SourceBuffer::SourceBuffer() |
||||||
|
: buffer_(reinterpret_cast<std::byte*>( |
||||||
|
heap_caps_malloc(kBufferSize, MALLOC_CAP_SPIRAM)), |
||||||
|
kBufferSize), |
||||||
|
bytes_in_buffer_(0), |
||||||
|
offset_of_bytes_(0) { |
||||||
|
assert(buffer_.data() != nullptr); |
||||||
|
} |
||||||
|
|
||||||
|
SourceBuffer::~SourceBuffer() { |
||||||
|
free(buffer_.data()); |
||||||
|
} |
||||||
|
|
||||||
|
auto SourceBuffer::Refill(IStream* src) -> bool { |
||||||
|
if (bytes_in_buffer_ == buffer_.size_bytes()) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
bool eof = false; |
||||||
|
AddBytes([&](cpp::span<std::byte> buf) -> size_t { |
||||||
|
size_t bytes_read = src->Read(buf); |
||||||
|
eof = bytes_read == 0; |
||||||
|
return bytes_read; |
||||||
|
}); |
||||||
|
return eof; |
||||||
|
} |
||||||
|
|
||||||
|
auto SourceBuffer::AddBytes(std::function<size_t(cpp::span<std::byte>)> writer) |
||||||
|
-> void { |
||||||
|
if (offset_of_bytes_ > 0) { |
||||||
|
std::memmove(buffer_.data(), buffer_.data() + offset_of_bytes_, |
||||||
|
bytes_in_buffer_); |
||||||
|
offset_of_bytes_ = 0; |
||||||
|
} |
||||||
|
size_t added_bytes = std::invoke(writer, buffer_.subspan(bytes_in_buffer_)); |
||||||
|
assert(bytes_in_buffer_ + added_bytes <= buffer_.size_bytes()); |
||||||
|
bytes_in_buffer_ += added_bytes; |
||||||
|
} |
||||||
|
|
||||||
|
auto SourceBuffer::ConsumeBytes( |
||||||
|
std::function<size_t(cpp::span<std::byte>)> reader) -> void { |
||||||
|
size_t bytes_consumed = std::invoke( |
||||||
|
reader, buffer_.subspan(offset_of_bytes_).first(bytes_in_buffer_)); |
||||||
|
assert(bytes_consumed <= bytes_in_buffer_); |
||||||
|
|
||||||
|
bytes_in_buffer_ -= bytes_consumed; |
||||||
|
if (bytes_in_buffer_ == 0) { |
||||||
|
offset_of_bytes_ = 0; |
||||||
|
} else { |
||||||
|
offset_of_bytes_ += bytes_consumed; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace codecs
|
Loading…
Reference in new issue