Merge pull request 'Add seeking support for all codecs' (#50) from seek-support into main

Reviewed-on: https://codeberg.org/cool-tech-zone/tangara-fw/pulls/50
Reviewed-by: ailurux <ailurux@noreply.codeberg.org>
custom
ailurux 1 year ago
commit aa87c13799
  1. 2
      lib/drflac/CMakeLists.txt
  2. 4
      lib/drflac/dr_flac.c
  3. 12536
      lib/drflac/dr_flac.h
  4. 5
      lib/luavgl/src/lvgl.lua
  5. 10
      lib/luavgl/src/widgets/slider.c
  6. 5
      lib/miniflac/miniflac.c
  7. 6092
      lib/miniflac/miniflac.h
  8. 3
      lua/licenses.lua
  9. 10
      lua/playing.lua
  10. 6
      src/audio/audio_converter.cpp
  11. 12
      src/audio/audio_decoder.cpp
  12. 10
      src/audio/audio_fsm.cpp
  13. 14
      src/audio/audio_source.cpp
  14. 8
      src/audio/fatfs_audio_input.cpp
  15. 2
      src/audio/include/audio_decoder.hpp
  16. 6
      src/audio/include/audio_events.hpp
  17. 3
      src/audio/include/audio_fsm.hpp
  18. 11
      src/audio/include/audio_source.hpp
  19. 4
      src/audio/include/fatfs_audio_input.hpp
  20. 4
      src/codecs/CMakeLists.txt
  21. 4
      src/codecs/codec.cpp
  22. 115
      src/codecs/dr_flac.cpp
  23. 4
      src/codecs/include/codec.hpp
  24. 23
      src/codecs/include/dr_flac.hpp
  25. 4
      src/codecs/include/mad.hpp
  26. 4
      src/codecs/include/opus.hpp
  27. 1
      src/codecs/include/source_buffer.hpp
  28. 4
      src/codecs/include/vorbis.hpp
  29. 4
      src/codecs/include/wav.hpp
  30. 51
      src/codecs/mad.cpp
  31. 181
      src/codecs/miniflac.cpp
  32. 14
      src/codecs/opus.cpp
  33. 5
      src/codecs/source_buffer.cpp
  34. 14
      src/codecs/vorbis.cpp
  35. 11
      src/codecs/wav.cpp
  36. 16
      src/ui/ui_fsm.cpp
  37. 2
      tools/cmake/common.cmake

@ -1,5 +1,5 @@
# Copyright 2023 jacqueline <me@jacqueline.id.au> # Copyright 2023 jacqueline <me@jacqueline.id.au>
# #
# SPDX-License-Identifier: GPL-3.0-only # SPDX-License-Identifier: GPL-3.0-only
idf_component_register(SRCS miniflac.c INCLUDE_DIRS .) idf_component_register(SRCS dr_flac.c INCLUDE_DIRS .)
target_compile_options("${COMPONENT_LIB}" PRIVATE -Ofast) target_compile_options("${COMPONENT_LIB}" PRIVATE -Ofast)

@ -0,0 +1,4 @@
#define DR_FLAC_IMPLEMENTATION
#define DR_FLAC_NO_STDIO
#define DR_FLAC_NO_SIMD
#include "dr_flac.h"

File diff suppressed because it is too large Load Diff

@ -1098,6 +1098,11 @@ end
function slider:value() function slider:value()
end end
--- get whether slider is dragged or not
--- @return boolean
function slider:is_dragged()
end
--- ---
--- Switch widget --- Switch widget
---@class Switch:Object ---@class Switch:Object

@ -73,9 +73,19 @@ static int luavgl_slider_tostring(lua_State *L) {
return 1; return 1;
} }
static int luavgl_slider_is_dragged(lua_State *L) {
lv_obj_t *obj = luavgl_to_obj(L, 1);
bool is_dragged = lv_slider_is_dragged(obj);
lv_group_t * g = lv_obj_get_group(obj);
bool editing = lv_group_get_editing(g);
lua_pushboolean(L, editing || is_dragged);
return 1;
}
static const luaL_Reg luavgl_slider_methods[] = { static const luaL_Reg luavgl_slider_methods[] = {
{"set", luavgl_slider_set}, {"set", luavgl_slider_set},
{"value", luavgl_slider_value}, {"value", luavgl_slider_value},
{"is_dragged", luavgl_slider_is_dragged},
{NULL, NULL}, {NULL, NULL},
}; };

@ -1,5 +0,0 @@
#define MINIFLAC_IMPLEMENTATION
#define MINIFLAC_API
#define MINIFLAC_PRIVATE static inline
#include "miniflac.h"

File diff suppressed because it is too large Load Diff

@ -149,9 +149,6 @@ return function()
library("MillerShuffle", "Apache 2.0", function() library("MillerShuffle", "Apache 2.0", function()
apache("Copyright 2022 Ronald Ross Miller") apache("Copyright 2022 Ronald Ross Miller")
end) end)
library("miniflac", "BSD", function()
bsd("Copyright (C) 2022 John Regan <john@jrjrtech.com>")
end)
library("ogg", "BSD", function() library("ogg", "BSD", function()
xiphbsd("Copyright (c) 2002, Xiph.org Foundation") xiphbsd("Copyright (c) 2002, Xiph.org Foundation")
end) end)

@ -112,13 +112,17 @@ return function(opts)
} }
playlist:Object({ w = 3, h = 1 }) -- spacer playlist:Object({ w = 3, h = 1 }) -- spacer
local scrubber = screen.root:Bar { local scrubber = screen.root:Slider {
w = lvgl.PCT(100), w = lvgl.PCT(100),
h = 5, h = 5,
range = { min = 0, max = 100 }, range = { min = 0, max = 100 },
value = 0, value = 0,
} }
scrubber:onevent(lvgl.EVENT.RELEASED, function()
playback.position:set(scrubber:value())
end)
local controls = screen.root:Object { local controls = screen.root:Object {
flex = { flex = {
flex_direction = "row", flex_direction = "row",
@ -182,7 +186,9 @@ return function(opts)
cur_time:set { cur_time:set {
text = format_time(pos) text = format_time(pos)
} }
scrubber:set { value = pos } if not scrubber:is_dragged() then
scrubber:set { value = pos }
end
end), end),
playback.track:bind(function(track) playback.track:bind(function(track)
if not track then return end if not track then return end

@ -77,9 +77,9 @@ auto SampleConverter::ConvertSamples(cpp::span<sample::Sample> input,
reinterpret_cast<std::byte*>(input.data()), input.size_bytes()}; reinterpret_cast<std::byte*>(input.data()), input.size_bytes()};
size_t bytes_sent = 0; size_t bytes_sent = 0;
while (bytes_sent < input_as_bytes.size()) { while (bytes_sent < input_as_bytes.size()) {
bytes_sent += bytes_sent += xStreamBufferSend(
xStreamBufferSend(source_, input_as_bytes.subspan(bytes_sent).data(), source_, input_as_bytes.subspan(bytes_sent).data(),
input_as_bytes.size() - bytes_sent, portMAX_DELAY); input_as_bytes.size() - bytes_sent, pdMS_TO_TICKS(100));
} }
} }

@ -51,9 +51,10 @@ static constexpr std::size_t kCodecBufferLength =
drivers::kI2SBufferLengthFrames * sizeof(sample::Sample); drivers::kI2SBufferLengthFrames * sizeof(sample::Sample);
Timer::Timer(std::shared_ptr<Track> t, Timer::Timer(std::shared_ptr<Track> t,
const codecs::ICodec::OutputFormat& format) const codecs::ICodec::OutputFormat& format,
uint32_t current_seconds)
: track_(t), : track_(t),
current_seconds_(0), current_seconds_(current_seconds),
current_sample_in_second_(0), current_sample_in_second_(0),
samples_per_second_(format.sample_rate_hz * format.num_channels), samples_per_second_(format.sample_rate_hz * format.num_channels),
total_duration_seconds_(format.total_samples.value_or(0) / total_duration_seconds_(format.total_samples.value_or(0) /
@ -131,7 +132,7 @@ auto Decoder::BeginDecoding(std::shared_ptr<TaggedStream> stream) -> bool {
return false; return false;
} }
auto open_res = codec_->OpenStream(stream); auto open_res = codec_->OpenStream(stream, stream->Offset());
if (open_res.has_error()) { if (open_res.has_error()) {
ESP_LOGE(kTag, "codec failed to start: %s", ESP_LOGE(kTag, "codec failed to start: %s",
codecs::ICodec::ErrorString(open_res.error()).c_str()); codecs::ICodec::ErrorString(open_res.error()).c_str());
@ -152,10 +153,11 @@ auto Decoder::BeginDecoding(std::shared_ptr<TaggedStream> stream) -> bool {
.db_info = {}, .db_info = {},
.bitrate_kbps = open_res->sample_rate_hz, .bitrate_kbps = open_res->sample_rate_hz,
.encoding = stream->type(), .encoding = stream->type(),
.filepath = stream->Filepath(),
}); });
timer_.reset(new Timer(tags, open_res.value())); timer_.reset(new Timer(tags, open_res.value(), stream->Offset()));
PlaybackUpdate ev{.seconds_elapsed = 0, .track = tags}; PlaybackUpdate ev{.seconds_elapsed = stream->Offset(), .track = tags};
events::Audio().Dispatch(ev); events::Audio().Dispatch(ev);
events::Ui().Dispatch(ev); events::Ui().Dispatch(ev);

@ -280,6 +280,8 @@ void Uninitialised::react(const system_fsm::BootComplete& ev) {
} }
void Standby::react(const PlayFile& ev) { void Standby::react(const PlayFile& ev) {
sCurrentTrack = 0;
sIsPlaybackAllowed = true;
sFileSource->SetPath(ev.filename); sFileSource->SetPath(ev.filename);
} }
@ -287,6 +289,14 @@ void Playback::react(const PlayFile& ev) {
sFileSource->SetPath(ev.filename); sFileSource->SetPath(ev.filename);
} }
void Standby::react(const SeekFile& ev) {
sFileSource->SetPath(ev.filename, ev.offset);
}
void Playback::react(const SeekFile& ev) {
sFileSource->SetPath(ev.filename, ev.offset);
}
void Standby::react(const internal::InputFileOpened& ev) { void Standby::react(const internal::InputFileOpened& ev) {
if (readyToPlay()) { if (readyToPlay()) {
transit<Playback>(); transit<Playback>();

@ -11,8 +11,10 @@
namespace audio { namespace audio {
TaggedStream::TaggedStream(std::shared_ptr<database::TrackTags> t, TaggedStream::TaggedStream(std::shared_ptr<database::TrackTags> t,
std::unique_ptr<codecs::IStream> w) std::unique_ptr<codecs::IStream> w,
: codecs::IStream(w->type()), tags_(t), wrapped_(std::move(w)) {} std::string filepath,
uint32_t offset)
: codecs::IStream(w->type()), tags_(t), wrapped_(std::move(w)), filepath_(filepath), offset_(offset) {}
auto TaggedStream::tags() -> std::shared_ptr<database::TrackTags> { auto TaggedStream::tags() -> std::shared_ptr<database::TrackTags> {
return tags_; return tags_;
@ -38,6 +40,14 @@ auto TaggedStream::Size() -> std::optional<int64_t> {
return wrapped_->Size(); return wrapped_->Size();
} }
auto TaggedStream::Offset() -> uint32_t {
return offset_;
}
auto TaggedStream::Filepath() -> std::string {
return filepath_;
}
auto TaggedStream::SetPreambleFinished() -> void { auto TaggedStream::SetPreambleFinished() -> void {
wrapped_->SetPreambleFinished(); wrapped_->SetPreambleFinished();
} }

@ -62,9 +62,9 @@ auto FatfsAudioInput::SetPath(std::optional<std::string> path) -> void {
} }
} }
auto FatfsAudioInput::SetPath(const std::string& path) -> void { auto FatfsAudioInput::SetPath(const std::string& path,uint32_t offset) -> void {
std::lock_guard<std::mutex> guard{new_stream_mutex_}; std::lock_guard<std::mutex> guard{new_stream_mutex_};
if (OpenFile(path)) { if (OpenFile(path, offset)) {
has_new_stream_ = true; has_new_stream_ = true;
has_new_stream_.notify_one(); has_new_stream_.notify_one();
} }
@ -103,7 +103,7 @@ auto FatfsAudioInput::NextStream() -> std::shared_ptr<TaggedStream> {
} }
} }
auto FatfsAudioInput::OpenFile(const std::string& path) -> bool { auto FatfsAudioInput::OpenFile(const std::string& path,uint32_t offset) -> bool {
ESP_LOGI(kTag, "opening file %s", path.c_str()); ESP_LOGI(kTag, "opening file %s", path.c_str());
auto tags = tag_parser_.ReadAndParseTags(path); auto tags = tag_parser_.ReadAndParseTags(path);
@ -136,7 +136,7 @@ auto FatfsAudioInput::OpenFile(const std::string& path) -> bool {
auto source = auto source =
std::make_unique<FatfsSource>(stream_type.value(), std::move(file)); std::make_unique<FatfsSource>(stream_type.value(), std::move(file));
new_stream_.reset(new TaggedStream(tags, std::move(source))); new_stream_.reset(new TaggedStream(tags, std::move(source), path, offset));
return true; return true;
} }

@ -24,7 +24,7 @@ namespace audio {
*/ */
class Timer { class Timer {
public: public:
Timer(std::shared_ptr<Track>, const codecs::ICodec::OutputFormat& format); Timer(std::shared_ptr<Track>, const codecs::ICodec::OutputFormat& format, uint32_t current_seconds = 0);
auto AddSamples(std::size_t) -> void; auto AddSamples(std::size_t) -> void;

@ -25,6 +25,7 @@ struct Track {
uint32_t duration; uint32_t duration;
uint32_t bitrate_kbps; uint32_t bitrate_kbps;
codecs::StreamType encoding; codecs::StreamType encoding;
std::string filepath;
}; };
struct PlaybackStarted : tinyfsm::Event {}; struct PlaybackStarted : tinyfsm::Event {};
@ -51,6 +52,11 @@ struct PlayFile : tinyfsm::Event {
std::string filename; std::string filename;
}; };
struct SeekFile : tinyfsm::Event {
uint32_t offset;
std::string filename;
};
struct StepUpVolume : tinyfsm::Event {}; struct StepUpVolume : tinyfsm::Event {};
struct StepDownVolume : tinyfsm::Event {}; struct StepDownVolume : tinyfsm::Event {};
struct SetVolume : tinyfsm::Event { struct SetVolume : tinyfsm::Event {

@ -57,6 +57,7 @@ class AudioState : public tinyfsm::Fsm<AudioState> {
virtual void react(const system_fsm::BluetoothEvent&); virtual void react(const system_fsm::BluetoothEvent&);
virtual void react(const PlayFile&) {} virtual void react(const PlayFile&) {}
virtual void react(const SeekFile&) {}
virtual void react(const QueueUpdate&) {} virtual void react(const QueueUpdate&) {}
virtual void react(const PlaybackUpdate&) {} virtual void react(const PlaybackUpdate&) {}
void react(const TogglePlayPause&); void react(const TogglePlayPause&);
@ -102,6 +103,7 @@ class Uninitialised : public AudioState {
class Standby : public AudioState { class Standby : public AudioState {
public: public:
void react(const PlayFile&) override; void react(const PlayFile&) override;
void react(const SeekFile&) override;
void react(const internal::InputFileOpened&) override; void react(const internal::InputFileOpened&) override;
void react(const QueueUpdate&) override; void react(const QueueUpdate&) override;
void react(const system_fsm::KeyLockChanged&) override; void react(const system_fsm::KeyLockChanged&) override;
@ -118,6 +120,7 @@ class Playback : public AudioState {
void react(const system_fsm::HasPhonesChanged&) override; void react(const system_fsm::HasPhonesChanged&) override;
void react(const PlayFile&) override; void react(const PlayFile&) override;
void react(const SeekFile&) override;
void react(const QueueUpdate&) override; void react(const QueueUpdate&) override;
void react(const PlaybackUpdate&) override; void react(const PlaybackUpdate&) override;

@ -16,7 +16,10 @@ namespace audio {
class TaggedStream : public codecs::IStream { class TaggedStream : public codecs::IStream {
public: public:
TaggedStream(std::shared_ptr<database::TrackTags>, TaggedStream(std::shared_ptr<database::TrackTags>,
std::unique_ptr<codecs::IStream> wrapped); std::unique_ptr<codecs::IStream> wrapped,
std::string path,
uint32_t offset = 0
);
auto tags() -> std::shared_ptr<database::TrackTags>; auto tags() -> std::shared_ptr<database::TrackTags>;
@ -30,11 +33,17 @@ class TaggedStream : public codecs::IStream {
auto Size() -> std::optional<int64_t> override; auto Size() -> std::optional<int64_t> override;
auto Offset() -> uint32_t;
auto Filepath() -> std::string;
auto SetPreambleFinished() -> void override; auto SetPreambleFinished() -> void override;
private: private:
std::shared_ptr<database::TrackTags> tags_; std::shared_ptr<database::TrackTags> tags_;
std::unique_ptr<codecs::IStream> wrapped_; std::unique_ptr<codecs::IStream> wrapped_;
std::string filepath_;
int32_t offset_;
}; };
class IAudioSource { class IAudioSource {

@ -39,7 +39,7 @@ class FatfsAudioInput : public IAudioSource {
* given file path. * given file path.
*/ */
auto SetPath(std::optional<std::string>) -> void; auto SetPath(std::optional<std::string>) -> void;
auto SetPath(const std::string&) -> void; auto SetPath(const std::string&,uint32_t offset = 0) -> void;
auto SetPath() -> void; auto SetPath() -> void;
auto HasNewStream() -> bool override; auto HasNewStream() -> bool override;
@ -49,7 +49,7 @@ class FatfsAudioInput : public IAudioSource {
FatfsAudioInput& operator=(const FatfsAudioInput&) = delete; FatfsAudioInput& operator=(const FatfsAudioInput&) = delete;
private: private:
auto OpenFile(const std::string& path) -> bool; auto OpenFile(const std::string& path,uint32_t offset) -> bool;
auto ContainerToStreamType(database::Container) auto ContainerToStreamType(database::Container)
-> std::optional<codecs::StreamType>; -> std::optional<codecs::StreamType>;

@ -3,10 +3,10 @@
# SPDX-License-Identifier: GPL-3.0-only # SPDX-License-Identifier: GPL-3.0-only
idf_component_register( idf_component_register(
SRCS "codec.cpp" "mad.cpp" "miniflac.cpp" "opus.cpp" "vorbis.cpp" SRCS "dr_flac.cpp" "codec.cpp" "mad.cpp" "opus.cpp" "vorbis.cpp"
"source_buffer.cpp" "sample.cpp" "wav.cpp" "source_buffer.cpp" "sample.cpp" "wav.cpp"
INCLUDE_DIRS "include" INCLUDE_DIRS "include"
REQUIRES "result" "span" "libmad" "miniflac" "tremor" "opusfile" "memory" "util" REQUIRES "result" "span" "libmad" "drflac" "tremor" "opusfile" "memory" "util"
"komihash") "komihash")
target_compile_options("${COMPONENT_LIB}" PRIVATE ${EXTRA_WARNINGS}) target_compile_options("${COMPONENT_LIB}" PRIVATE ${EXTRA_WARNINGS})

@ -10,7 +10,7 @@
#include <optional> #include <optional>
#include "mad.hpp" #include "mad.hpp"
#include "miniflac.hpp" #include "dr_flac.hpp"
#include "opus.hpp" #include "opus.hpp"
#include "types.hpp" #include "types.hpp"
#include "vorbis.hpp" #include "vorbis.hpp"
@ -42,7 +42,7 @@ auto CreateCodecForType(StreamType type) -> std::optional<ICodec*> {
case StreamType::kVorbis: case StreamType::kVorbis:
return new TremorVorbisDecoder(); return new TremorVorbisDecoder();
case StreamType::kFlac: case StreamType::kFlac:
return new MiniFlacDecoder(); return new DrFlacDecoder();
case StreamType::kOpus: case StreamType::kOpus:
return new XiphOpusDecoder(); return new XiphOpusDecoder();
case StreamType::kWav: case StreamType::kWav:

@ -0,0 +1,115 @@
/*
* Copyright 2023 jacqueline <me@jacqueline.id.au>
*
* SPDX-License-Identifier: GPL-3.0-only
*/
#include "dr_flac.hpp"
#include <cstdint>
#include <cstdlib>
#include "codec.hpp"
#include "dr_flac.h"
#include "esp_heap_caps.h"
#include "esp_log.h"
#include "result.hpp"
#include "sample.hpp"
namespace codecs {
[[maybe_unused]] static const char kTag[] = "flac";
static void* onMalloc(size_t sz, void* pUserData) {
return heap_caps_malloc(sz, MALLOC_CAP_SPIRAM);
}
static void* onRealloc(void* p, size_t sz, void* pUserData) {
return heap_caps_realloc(p, sz, MALLOC_CAP_SPIRAM);
}
static void onFree(void* p, void* pUserData) {
heap_caps_free(p);
}
static drflac_allocation_callbacks kAllocCallbacks{
.pUserData = nullptr,
.onMalloc = onMalloc,
.onRealloc = onRealloc,
.onFree = onFree,
};
static size_t readProc(void* pUserData, void* pBufferOut, size_t bytesToRead) {
IStream* stream = reinterpret_cast<IStream*>(pUserData);
ssize_t res =
stream->Read({reinterpret_cast<std::byte*>(pBufferOut), bytesToRead});
return res < 0 ? 0 : res;
}
static drflac_bool32 seekProc(void* pUserData,
int offset,
drflac_seek_origin origin) {
IStream* stream = reinterpret_cast<IStream*>(pUserData);
if (!stream->CanSeek()) {
return DRFLAC_FALSE;
}
IStream::SeekFrom seek_from;
switch (origin) {
case drflac_seek_origin_start:
seek_from = IStream::SeekFrom::kStartOfStream;
break;
case drflac_seek_origin_current:
seek_from = IStream::SeekFrom::kCurrentPosition;
break;
default:
return DRFLAC_FALSE;
}
stream->SeekTo(offset, seek_from);
// FIXME: Detect falling off the end of the file.
return DRFLAC_TRUE;
}
DrFlacDecoder::DrFlacDecoder() : input_(), flac_() {}
DrFlacDecoder::~DrFlacDecoder() {
if (flac_) {
drflac_free(flac_, &kAllocCallbacks);
}
}
auto DrFlacDecoder::OpenStream(std::shared_ptr<IStream> input, uint32_t offset)
-> cpp::result<OutputFormat, Error> {
input_ = input;
flac_ = drflac_open(readProc, seekProc, input_.get(), &kAllocCallbacks);
if (!flac_) {
return cpp::fail(Error::kMalformedData);
}
if (offset && !drflac_seek_to_pcm_frame(flac_, offset * flac_->sampleRate)) {
return cpp::fail(Error::kMalformedData);
}
OutputFormat format{
.num_channels = static_cast<uint8_t>(flac_->channels),
.sample_rate_hz = static_cast<uint32_t>(flac_->sampleRate),
.total_samples = flac_->totalPCMFrameCount * flac_->channels,
};
return format;
}
auto DrFlacDecoder::DecodeTo(cpp::span<sample::Sample> output)
-> cpp::result<OutputInfo, Error> {
size_t frames_to_read = output.size() / flac_->channels / 2;
auto frames_written = drflac_read_pcm_frames_s16(
flac_, output.size() / flac_->channels, output.data());
return OutputInfo{
.samples_written = static_cast<size_t>(frames_written * flac_->channels),
.is_stream_finished = frames_written < frames_to_read};
}
} // namespace codecs

@ -117,7 +117,7 @@ class ICodec {
* Decodes metadata or headers from the given input stream, and returns the * Decodes metadata or headers from the given input stream, and returns the
* format for the samples that will be decoded from it. * format for the samples that will be decoded from it.
*/ */
virtual auto OpenStream(std::shared_ptr<IStream> input) virtual auto OpenStream(std::shared_ptr<IStream> input,uint32_t offset)
-> cpp::result<OutputFormat, Error> = 0; -> cpp::result<OutputFormat, Error> = 0;
struct OutputInfo { struct OutputInfo {
@ -130,8 +130,6 @@ class ICodec {
*/ */
virtual auto DecodeTo(cpp::span<sample::Sample> destination) virtual auto DecodeTo(cpp::span<sample::Sample> destination)
-> cpp::result<OutputInfo, Error> = 0; -> cpp::result<OutputInfo, Error> = 0;
virtual auto SeekTo(size_t target_sample) -> cpp::result<void, Error> = 0;
}; };
auto CreateCodecForType(StreamType type) -> std::optional<ICodec*>; auto CreateCodecForType(StreamType type) -> std::optional<ICodec*>;

@ -6,7 +6,6 @@
#pragma once #pragma once
#include <sys/_stdint.h>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
@ -14,7 +13,7 @@
#include <string> #include <string>
#include <utility> #include <utility>
#include "miniflac.h" #include "dr_flac.h"
#include "sample.hpp" #include "sample.hpp"
#include "source_buffer.hpp" #include "source_buffer.hpp"
#include "span.hpp" #include "span.hpp"
@ -23,29 +22,23 @@
namespace codecs { namespace codecs {
class MiniFlacDecoder : public ICodec { class DrFlacDecoder : public ICodec {
public: public:
MiniFlacDecoder(); DrFlacDecoder();
~MiniFlacDecoder(); ~DrFlacDecoder();
auto OpenStream(std::shared_ptr<IStream> input) auto OpenStream(std::shared_ptr<IStream> input,uint32_t offset)
-> cpp::result<OutputFormat, Error> override; -> cpp::result<OutputFormat, Error> override;
auto DecodeTo(cpp::span<sample::Sample> destination) auto DecodeTo(cpp::span<sample::Sample> destination)
-> cpp::result<OutputInfo, Error> override; -> cpp::result<OutputInfo, Error> override;
auto SeekTo(std::size_t target_sample) -> cpp::result<void, Error> override; DrFlacDecoder(const DrFlacDecoder&) = delete;
DrFlacDecoder& operator=(const DrFlacDecoder&) = delete;
MiniFlacDecoder(const MiniFlacDecoder&) = delete;
MiniFlacDecoder& operator=(const MiniFlacDecoder&) = delete;
private: private:
std::shared_ptr<IStream> input_; std::shared_ptr<IStream> input_;
SourceBuffer buffer_; drflac *flac_;
std::unique_ptr<miniflac_t> flac_;
std::array<int32_t*, 2> samples_by_channel_;
std::optional<size_t> current_sample_;
}; };
} // namespace codecs } // namespace codecs

@ -26,14 +26,12 @@ class MadMp3Decoder : public ICodec {
MadMp3Decoder(); MadMp3Decoder();
~MadMp3Decoder(); ~MadMp3Decoder();
auto OpenStream(std::shared_ptr<IStream> input) auto OpenStream(std::shared_ptr<IStream> input,uint32_t offset)
-> cpp::result<OutputFormat, Error> override; -> cpp::result<OutputFormat, Error> override;
auto DecodeTo(cpp::span<sample::Sample> destination) auto DecodeTo(cpp::span<sample::Sample> destination)
-> cpp::result<OutputInfo, Error> override; -> cpp::result<OutputInfo, Error> override;
auto SeekTo(std::size_t target_sample) -> cpp::result<void, Error> override;
MadMp3Decoder(const MadMp3Decoder&) = delete; MadMp3Decoder(const MadMp3Decoder&) = delete;
MadMp3Decoder& operator=(const MadMp3Decoder&) = delete; MadMp3Decoder& operator=(const MadMp3Decoder&) = delete;

@ -26,14 +26,12 @@ class XiphOpusDecoder : public ICodec {
XiphOpusDecoder(); XiphOpusDecoder();
~XiphOpusDecoder(); ~XiphOpusDecoder();
auto OpenStream(std::shared_ptr<IStream> input) auto OpenStream(std::shared_ptr<IStream> input,uint32_t offset)
-> cpp::result<OutputFormat, Error> override; -> cpp::result<OutputFormat, Error> override;
auto DecodeTo(cpp::span<sample::Sample> destination) auto DecodeTo(cpp::span<sample::Sample> destination)
-> cpp::result<OutputInfo, Error> override; -> cpp::result<OutputInfo, Error> override;
auto SeekTo(std::size_t target_sample) -> cpp::result<void, Error> override;
XiphOpusDecoder(const XiphOpusDecoder&) = delete; XiphOpusDecoder(const XiphOpusDecoder&) = delete;
XiphOpusDecoder& operator=(const XiphOpusDecoder&) = delete; XiphOpusDecoder& operator=(const XiphOpusDecoder&) = delete;

@ -24,6 +24,7 @@ class SourceBuffer {
auto Refill(IStream* src) -> bool; auto Refill(IStream* src) -> bool;
auto AddBytes(std::function<size_t(cpp::span<std::byte>)> writer) -> void; auto AddBytes(std::function<size_t(cpp::span<std::byte>)> writer) -> void;
auto ConsumeBytes(std::function<size_t(cpp::span<std::byte>)> reader) -> void; auto ConsumeBytes(std::function<size_t(cpp::span<std::byte>)> reader) -> void;
auto Empty() -> void;
SourceBuffer(const SourceBuffer&) = delete; SourceBuffer(const SourceBuffer&) = delete;
SourceBuffer& operator=(const SourceBuffer&) = delete; SourceBuffer& operator=(const SourceBuffer&) = delete;

@ -26,14 +26,12 @@ class TremorVorbisDecoder : public ICodec {
TremorVorbisDecoder(); TremorVorbisDecoder();
~TremorVorbisDecoder(); ~TremorVorbisDecoder();
auto OpenStream(std::shared_ptr<IStream> input) auto OpenStream(std::shared_ptr<IStream> input,uint32_t offset)
-> cpp::result<OutputFormat, Error> override; -> cpp::result<OutputFormat, Error> override;
auto DecodeTo(cpp::span<sample::Sample> destination) auto DecodeTo(cpp::span<sample::Sample> destination)
-> cpp::result<OutputInfo, Error> override; -> cpp::result<OutputInfo, Error> override;
auto SeekTo(std::size_t target_sample) -> cpp::result<void, Error> override;
TremorVorbisDecoder(const TremorVorbisDecoder&) = delete; TremorVorbisDecoder(const TremorVorbisDecoder&) = delete;
TremorVorbisDecoder& operator=(const TremorVorbisDecoder&) = delete; TremorVorbisDecoder& operator=(const TremorVorbisDecoder&) = delete;

@ -31,14 +31,12 @@ class WavDecoder : public ICodec {
WavDecoder(); WavDecoder();
~WavDecoder(); ~WavDecoder();
auto OpenStream(std::shared_ptr<IStream> input) auto OpenStream(std::shared_ptr<IStream> input,uint32_t offset)
-> cpp::result<OutputFormat, Error> override; -> cpp::result<OutputFormat, Error> override;
auto DecodeTo(cpp::span<sample::Sample> destination) auto DecodeTo(cpp::span<sample::Sample> destination)
-> cpp::result<OutputInfo, Error> override; -> cpp::result<OutputInfo, Error> override;
auto SeekTo(std::size_t target_sample) -> cpp::result<void, Error> override;
WavDecoder(const WavDecoder&) = delete; WavDecoder(const WavDecoder&) = delete;
WavDecoder& operator=(const WavDecoder&) = delete; WavDecoder& operator=(const WavDecoder&) = delete;

@ -44,6 +44,7 @@ MadMp3Decoder::MadMp3Decoder()
mad_frame_init(frame_.get()); mad_frame_init(frame_.get());
mad_synth_init(synth_.get()); mad_synth_init(synth_.get());
} }
MadMp3Decoder::~MadMp3Decoder() { MadMp3Decoder::~MadMp3Decoder() {
mad_stream_finish(stream_.get()); mad_stream_finish(stream_.get());
mad_frame_finish(frame_.get()); mad_frame_finish(frame_.get());
@ -58,7 +59,7 @@ auto MadMp3Decoder::GetBytesUsed() -> std::size_t {
} }
} }
auto MadMp3Decoder::OpenStream(std::shared_ptr<IStream> input) auto MadMp3Decoder::OpenStream(std::shared_ptr<IStream> input, uint32_t offset)
-> cpp::result<OutputFormat, ICodec::Error> { -> cpp::result<OutputFormat, ICodec::Error> {
input_ = input; input_ = input;
@ -113,6 +114,45 @@ auto MadMp3Decoder::OpenStream(std::shared_ptr<IStream> input)
auto cbr_length = input->Size().value() / (header.bitrate / 8); auto cbr_length = input->Size().value() / (header.bitrate / 8);
output.total_samples = cbr_length * output.sample_rate_hz * channels; output.total_samples = cbr_length * output.sample_rate_hz * channels;
} }
mad_timer_t timer;
mad_timer_reset(&timer);
bool need_refill = false;
bool seek_err = false;
while (mad_timer_count(timer, MAD_UNITS_SECONDS) < offset) {
if (seek_err) {
return cpp::fail(ICodec::Error::kMalformedData);
}
if (need_refill && buffer_.Refill(input_.get())) {
return cpp::fail(ICodec::Error::kMalformedData);
}
need_refill = false;
buffer_.ConsumeBytes([&](cpp::span<std::byte> buf) -> size_t {
mad_stream_buffer(stream_.get(),
reinterpret_cast<const unsigned char*>(buf.data()),
buf.size());
while (mad_header_decode(&header, stream_.get()) < 0) {
if (MAD_RECOVERABLE(stream_->error)) {
continue;
}
if (stream_->error == MAD_ERROR_BUFLEN) {
need_refill = true;
return GetBytesUsed();
}
// The error is unrecoverable. Give up.
seek_err = true;
return 0;
}
mad_timer_add(&timer, header.duration);
return GetBytesUsed();
});
}
return output; return output;
} }
@ -190,11 +230,6 @@ auto MadMp3Decoder::DecodeTo(cpp::span<sample::Sample> output)
.is_stream_finished = is_eos_}; .is_stream_finished = is_eos_};
} }
auto MadMp3Decoder::SeekTo(std::size_t target_sample)
-> cpp::result<void, Error> {
return {};
}
auto MadMp3Decoder::SkipID3Tags(IStream& stream) -> void { auto MadMp3Decoder::SkipID3Tags(IStream& stream) -> void {
// First check that the file actually does start with ID3 tags. // First check that the file actually does start with ID3 tags.
std::array<std::byte, 3> magic_buf{}; std::array<std::byte, 3> magic_buf{};
@ -222,8 +257,8 @@ auto MadMp3Decoder::SkipID3Tags(IStream& stream) -> void {
} }
/* /*
* Implementation taken from SDL_mixer and modified. Original is zlib-licensed, * Implementation taken from SDL_mixer and modified. Original is
* copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org> * zlib-licensed, copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
*/ */
auto MadMp3Decoder::GetVbrLength(const mad_header& header) auto MadMp3Decoder::GetVbrLength(const mad_header& header)
-> std::optional<uint32_t> { -> std::optional<uint32_t> {

@ -1,181 +0,0 @@
/*
* Copyright 2023 jacqueline <me@jacqueline.id.au>
*
* SPDX-License-Identifier: GPL-3.0-only
*/
#include "miniflac.hpp"
#include <cstdint>
#include <cstdlib>
#include "esp_heap_caps.h"
#include "esp_log.h"
#include "miniflac.h"
#include "result.hpp"
#include "sample.hpp"
namespace codecs {
[[maybe_unused]] static const char kTag[] = "flac";
static constexpr size_t kMaxFrameSize = 4608;
MiniFlacDecoder::MiniFlacDecoder()
: input_(),
buffer_(),
flac_(reinterpret_cast<miniflac_t*>(
heap_caps_malloc(sizeof(miniflac_t),
MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT))),
current_sample_() {
miniflac_init(flac_.get(), MINIFLAC_CONTAINER_UNKNOWN);
for (int i = 0; i < samples_by_channel_.size(); i++) {
uint32_t caps;
if (i == 0) {
caps = MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL;
} else {
// FIXME: We can *almost* fit two channels into internal ram, but we're a
// few KiB shy of being able to do it safely.
caps = MALLOC_CAP_SPIRAM;
}
samples_by_channel_[i] = reinterpret_cast<int32_t*>(
heap_caps_malloc(kMaxFrameSize * sizeof(int32_t), caps));
}
}
MiniFlacDecoder::~MiniFlacDecoder() {
for (int i = 0; i < samples_by_channel_.size(); i++) {
heap_caps_free(samples_by_channel_[i]);
}
}
auto MiniFlacDecoder::OpenStream(std::shared_ptr<IStream> input)
-> cpp::result<OutputFormat, Error> {
input_ = input;
MINIFLAC_RESULT res;
auto read_until_result = [&](auto fn) {
while (true) {
bool eof = buffer_.Refill(input_.get());
buffer_.ConsumeBytes(fn);
if (res == MINIFLAC_CONTINUE && !eof) {
continue;
}
break;
}
};
uint32_t sample_rate = 0;
read_until_result([&](cpp::span<std::byte> buf) -> size_t {
uint32_t bytes_used = 0;
res = miniflac_streaminfo_sample_rate(
flac_.get(), reinterpret_cast<const uint8_t*>(buf.data()),
buf.size_bytes(), &bytes_used, &sample_rate);
return bytes_used;
});
if (res != MINIFLAC_OK) {
return cpp::fail(Error::kMalformedData);
}
uint8_t channels = 0;
read_until_result([&](cpp::span<std::byte> buf) -> size_t {
uint32_t bytes_used = 0;
res = miniflac_streaminfo_channels(
flac_.get(), reinterpret_cast<const uint8_t*>(buf.data()),
buf.size_bytes(), &bytes_used, &channels);
return bytes_used;
});
if (res != MINIFLAC_OK) {
return cpp::fail(Error::kMalformedData);
}
uint64_t total_samples = 0;
read_until_result([&](cpp::span<std::byte> buf) -> size_t {
uint32_t bytes_used = 0;
res = miniflac_streaminfo_total_samples(
flac_.get(), reinterpret_cast<const uint8_t*>(buf.data()),
buf.size_bytes(), &bytes_used, &total_samples);
return bytes_used;
});
if (res != MINIFLAC_OK) {
return cpp::fail(Error::kMalformedData);
}
if (channels == 0 || channels > 2) {
return cpp::fail(Error::kMalformedData);
}
OutputFormat format{
.num_channels = static_cast<uint8_t>(channels),
.sample_rate_hz = static_cast<uint32_t>(sample_rate),
.total_samples = total_samples * channels,
};
return format;
}
auto MiniFlacDecoder::DecodeTo(cpp::span<sample::Sample> output)
-> cpp::result<OutputInfo, Error> {
bool is_eof = false;
if (!current_sample_) {
MINIFLAC_RESULT res = MINIFLAC_CONTINUE;
while (res == MINIFLAC_CONTINUE && !is_eof) {
is_eof = buffer_.Refill(input_.get());
buffer_.ConsumeBytes([&](cpp::span<std::byte> buf) -> size_t {
// FIXME: We should do a miniflac_sync first, in order to check that
// our sample buffers have enough space for the next frame.
uint32_t bytes_read = 0;
res = miniflac_decode(
flac_.get(), reinterpret_cast<const uint8_t*>(buf.data()),
buf.size_bytes(), &bytes_read, samples_by_channel_.data());
return bytes_read;
});
}
if (res == MINIFLAC_OK) {
current_sample_ = 0;
} else if (is_eof) {
return OutputInfo{
.samples_written = 0,
.is_stream_finished = true,
};
} else {
return cpp::fail(Error::kMalformedData);
}
}
size_t samples_written = 0;
if (current_sample_) {
while (*current_sample_ < flac_->frame.header.block_size) {
if (samples_written + flac_->frame.header.channels >= output.size()) {
// We can't fit the next full PCM frame into the buffer.
return OutputInfo{.samples_written = samples_written,
.is_stream_finished = false};
}
for (int channel = 0; channel < flac_->frame.header.channels; channel++) {
output[samples_written++] =
sample::FromSigned(samples_by_channel_[channel][*current_sample_],
flac_->frame.header.bps);
}
(*current_sample_)++;
}
}
current_sample_.reset();
return OutputInfo{.samples_written = samples_written,
.is_stream_finished = samples_written == 0 && is_eof};
}
auto MiniFlacDecoder::SeekTo(size_t target) -> cpp::result<void, Error> {
return {};
}
} // namespace codecs

@ -78,7 +78,8 @@ XiphOpusDecoder::~XiphOpusDecoder() {
} }
} }
auto XiphOpusDecoder::OpenStream(std::shared_ptr<IStream> input) auto XiphOpusDecoder::OpenStream(std::shared_ptr<IStream> input,
uint32_t offset)
-> cpp::result<OutputFormat, Error> { -> cpp::result<OutputFormat, Error> {
input_ = input; input_ = input;
@ -128,6 +129,10 @@ auto XiphOpusDecoder::OpenStream(std::shared_ptr<IStream> input)
length = l * 2; length = l * 2;
} }
if (offset && op_pcm_seek(opus_, offset * 48000) != 0) {
return cpp::fail(Error::kInternalError);
}
return OutputFormat{ return OutputFormat{
.num_channels = 2, .num_channels = 2,
.sample_rate_hz = 48000, .sample_rate_hz = 48000,
@ -151,11 +156,4 @@ auto XiphOpusDecoder::DecodeTo(cpp::span<sample::Sample> output)
}; };
} }
auto XiphOpusDecoder::SeekTo(size_t target) -> cpp::result<void, Error> {
if (op_pcm_seek(opus_, target) != 0) {
return cpp::fail(Error::kInternalError);
}
return {};
}
} // namespace codecs } // namespace codecs

@ -74,4 +74,9 @@ auto SourceBuffer::ConsumeBytes(
} }
} }
auto SourceBuffer::Empty() -> void {
offset_of_bytes_ = 0;
bytes_in_buffer_ = 0;
}
} // namespace codecs } // namespace codecs

@ -77,7 +77,8 @@ TremorVorbisDecoder::~TremorVorbisDecoder() {
ov_clear(vorbis_.get()); ov_clear(vorbis_.get());
} }
auto TremorVorbisDecoder::OpenStream(std::shared_ptr<IStream> input) auto TremorVorbisDecoder::OpenStream(std::shared_ptr<IStream> input,
uint32_t offset)
-> cpp::result<OutputFormat, Error> { -> cpp::result<OutputFormat, Error> {
int res = ov_open_callbacks(input.get(), vorbis_.get(), NULL, 0, kCallbacks); int res = ov_open_callbacks(input.get(), vorbis_.get(), NULL, 0, kCallbacks);
if (res < 0) { if (res < 0) {
@ -117,6 +118,10 @@ auto TremorVorbisDecoder::OpenStream(std::shared_ptr<IStream> input)
length = l * info->channels; length = l * info->channels;
} }
if (offset && ov_time_seek(vorbis_.get(), offset * 1000) != 0) {
return cpp::fail(Error::kInternalError);
}
return OutputFormat{ return OutputFormat{
.num_channels = static_cast<uint8_t>(info->channels), .num_channels = static_cast<uint8_t>(info->channels),
.sample_rate_hz = static_cast<uint32_t>(info->rate), .sample_rate_hz = static_cast<uint32_t>(info->rate),
@ -145,11 +150,4 @@ auto TremorVorbisDecoder::DecodeTo(cpp::span<sample::Sample> output)
}; };
} }
auto TremorVorbisDecoder::SeekTo(size_t target) -> cpp::result<void, Error> {
if (ov_pcm_seek(vorbis_.get(), target) != 0) {
return cpp::fail(Error::kInternalError);
}
return {};
}
} // namespace codecs } // namespace codecs

@ -84,7 +84,7 @@ WavDecoder::WavDecoder() : input_(), buffer_() {}
WavDecoder::~WavDecoder() {} WavDecoder::~WavDecoder() {}
auto WavDecoder::OpenStream(std::shared_ptr<IStream> input) auto WavDecoder::OpenStream(std::shared_ptr<IStream> input,uint32_t offset)
-> cpp::result<OutputFormat, Error> { -> cpp::result<OutputFormat, Error> {
input_ = input; input_ = input;
@ -199,8 +199,10 @@ auto WavDecoder::OpenStream(std::shared_ptr<IStream> input)
return cpp::fail(Error::kUnsupportedFormat); return cpp::fail(Error::kUnsupportedFormat);
} }
int64_t data_offset = offset * samples_per_second * bytes_per_sample_;
// Seek track to start of data // Seek track to start of data
input->SeekTo(data_chunk_index + 8, IStream::SeekFrom::kStartOfStream); input->SeekTo(data_chunk_index + 8 + data_offset, IStream::SeekFrom::kStartOfStream);
output_format_ = {.num_channels = (uint8_t)num_channels_, output_format_ = {.num_channels = (uint8_t)num_channels_,
.sample_rate_hz = samples_per_second, .sample_rate_hz = samples_per_second,
@ -241,14 +243,11 @@ auto WavDecoder::DecodeTo(cpp::span<sample::Sample> output)
return samples_written * bytes_per_sample_; return samples_written * bytes_per_sample_;
}); });
return OutputInfo{.samples_written = samples_written, return OutputInfo{.samples_written = samples_written,
.is_stream_finished = samples_written == 0 && is_eof}; .is_stream_finished = samples_written == 0 && is_eof};
} }
auto WavDecoder::SeekTo(size_t target) -> cpp::result<void, Error> {
return {};
}
auto codecs::WavDecoder::GetFormat() const -> uint16_t { auto codecs::WavDecoder::GetFormat() const -> uint16_t {
if (wave_format_ == kWaveFormatExtensible) { if (wave_format_ == kWaveFormatExtensible) {
return subformat_; return subformat_;

@ -125,7 +125,21 @@ lua::Property UiState::sPlaybackPlaying{
}}; }};
lua::Property UiState::sPlaybackTrack{}; lua::Property UiState::sPlaybackTrack{};
lua::Property UiState::sPlaybackPosition{0}; lua::Property UiState::sPlaybackPosition{0, [](const lua::LuaValue& val) {
int current_val = std::get<int>(sPlaybackPosition.Get());
if (!std::holds_alternative<int>(val)) {
return false;
}
int new_val = std::get<int>(val);
if (current_val != new_val) {
auto track = sPlaybackTrack.Get();
if (!std::holds_alternative<audio::Track>(track)) {
return false;
}
events::Audio().Dispatch(audio::SeekFile{.offset = (uint32_t)new_val, .filename = std::get<audio::Track>(track).filepath});
}
return true;
}};
lua::Property UiState::sQueuePosition{0}; lua::Property UiState::sQueuePosition{0};
lua::Property UiState::sQueueSize{0}; lua::Property UiState::sQueueSize{0};

@ -28,7 +28,7 @@ list(APPEND EXTRA_COMPONENT_DIRS "$ENV{PROJ_PATH}/lib/lua-term")
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{PROJ_PATH}/lib/luavgl") list(APPEND EXTRA_COMPONENT_DIRS "$ENV{PROJ_PATH}/lib/luavgl")
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{PROJ_PATH}/lib/lvgl") list(APPEND EXTRA_COMPONENT_DIRS "$ENV{PROJ_PATH}/lib/lvgl")
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{PROJ_PATH}/lib/millershuffle") list(APPEND EXTRA_COMPONENT_DIRS "$ENV{PROJ_PATH}/lib/millershuffle")
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{PROJ_PATH}/lib/miniflac") list(APPEND EXTRA_COMPONENT_DIRS "$ENV{PROJ_PATH}/lib/drflac")
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{PROJ_PATH}/lib/ogg") list(APPEND EXTRA_COMPONENT_DIRS "$ENV{PROJ_PATH}/lib/ogg")
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{PROJ_PATH}/lib/opusfile") list(APPEND EXTRA_COMPONENT_DIRS "$ENV{PROJ_PATH}/lib/opusfile")
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{PROJ_PATH}/lib/result") list(APPEND EXTRA_COMPONENT_DIRS "$ENV{PROJ_PATH}/lib/result")

Loading…
Cancel
Save