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
commit
aa87c13799
@ -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
@ -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
@ -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
|
@ -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
|
|
Loading…
Reference in new issue