parent
912060de1b
commit
f54347794f
@ -0,0 +1,207 @@ |
|||||||
|
/*
|
||||||
|
* 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,uint32_t offset) |
||||||
|
-> 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); |
||||||
|
} |
||||||
|
|
||||||
|
if (offset) { |
||||||
|
uint64_t samples_count = 0; |
||||||
|
uint32_t offset_count = 0; |
||||||
|
while (offset_count < offset) { |
||||||
|
read_until_result([&](cpp::span<std::byte> buf) -> size_t { |
||||||
|
uint32_t bytes_used = 0; |
||||||
|
res = miniflac_sync( |
||||||
|
flac_.get(), reinterpret_cast<const uint8_t*>(buf.data()), |
||||||
|
buf.size_bytes(), &bytes_used); |
||||||
|
return bytes_used; |
||||||
|
}); |
||||||
|
if (res != MINIFLAC_OK) { |
||||||
|
return cpp::fail(Error::kMalformedData); |
||||||
|
} |
||||||
|
|
||||||
|
uint32_t frame_samplerate = flac_.get()->frame.header.sample_rate; |
||||||
|
uint16_t frame_blocksize = flac_.get()->frame.header.block_size; |
||||||
|
if (!frame_samplerate || !frame_blocksize) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
samples_count += frame_blocksize; |
||||||
|
offset_count = samples_count / sample_rate; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
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
|
@ -0,0 +1,266 @@ |
|||||||
|
/* |
||||||
|
* 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,uint32_t offset) |
||||||
|
-> cpp::result<OutputFormat, Error> { |
||||||
|
input_ = input; |
||||||
|
|
||||||
|
MINIFLAC_RESULT res; |
||||||
|
bool is_eof; |
||||||
|
auto read_until_result = [&](auto fn) { |
||||||
|
while (true) { |
||||||
|
is_eof = buffer_.Refill(input_.get()); |
||||||
|
buffer_.ConsumeBytes(fn); |
||||||
|
if (res == MINIFLAC_CONTINUE && !eof) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
uint16_t min_block_size = 0; // In samples |
||||||
|
|
||||||
|
read_until_result([&](cpp::span<std::byte> buf) -> size_t { |
||||||
|
uint32_t bytes_used = 0; |
||||||
|
res = miniflac_streaminfo_min_block_size( |
||||||
|
flac_.get(), reinterpret_cast<const uint8_t*>(buf.data()), |
||||||
|
buf.size_bytes(), &bytes_used, &min_block_size); |
||||||
|
return bytes_used; |
||||||
|
}); |
||||||
|
|
||||||
|
if (res != MINIFLAC_OK) { |
||||||
|
return cpp::fail(Error::kMalformedData); |
||||||
|
} |
||||||
|
|
||||||
|
uint16_t max_block_size = 0; // In samples |
||||||
|
read_until_result([&](cpp::span<std::byte> buf) -> size_t { |
||||||
|
uint32_t bytes_used = 0; |
||||||
|
res = miniflac_streaminfo_min_block_size( |
||||||
|
flac_.get(), reinterpret_cast<const uint8_t*>(buf.data()), |
||||||
|
buf.size_bytes(), &bytes_used, &max_block_size); |
||||||
|
return bytes_used; |
||||||
|
}); |
||||||
|
|
||||||
|
if (res != MINIFLAC_OK) { |
||||||
|
return cpp::fail(Error::kMalformedData); |
||||||
|
} |
||||||
|
|
||||||
|
ESP_LOGI(kTag, "Blocksize min: %u max %u", min_block_size, max_block_size); |
||||||
|
|
||||||
|
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); |
||||||
|
} |
||||||
|
|
||||||
|
// Seeking |
||||||
|
offset = 0; |
||||||
|
if (offset) { |
||||||
|
// Super dumb approach, but lets try it first |
||||||
|
// Go to the first frame |
||||||
|
while(flac_.get()->state == MINIFLAC_METADATA) { |
||||||
|
read_until_result([&](cpp::span<std::byte> buf) -> size_t { |
||||||
|
uint32_t bytes_used = 0; |
||||||
|
res = miniflac_sync( |
||||||
|
flac_.get(), reinterpret_cast<const uint8_t*>(buf.data()), |
||||||
|
buf.size_bytes(), &bytes_used); |
||||||
|
return bytes_used; |
||||||
|
}); |
||||||
|
if (res != MINIFLAC_OK) { |
||||||
|
ESP_LOGI(kTag, "IT HAPPENED"); |
||||||
|
} |
||||||
|
} |
||||||
|
ESP_LOGI(kTag, "Flac state: %d", flac_->state); |
||||||
|
|
||||||
|
// Naive approach |
||||||
|
uint64_t byte_offset = offset; // TODO |
||||||
|
|
||||||
|
ESP_LOGI(kTag, "Going to skip forward %llu bytes", byte_offset); |
||||||
|
if (input_.get()->CanSeek()) { |
||||||
|
ESP_LOGI(kTag, "Skipping forward %llu bytes", byte_offset); |
||||||
|
buffer_.Empty(); |
||||||
|
input_.get()->SeekTo(byte_offset, IStream::SeekFrom::kCurrentPosition); |
||||||
|
} |
||||||
|
// buffer_.Refill(input_.get()); |
||||||
|
|
||||||
|
// // Sync again |
||||||
|
// read_until_result([&](cpp::span<std::byte> buf) -> size_t { |
||||||
|
// uint32_t bytes_used = 0; |
||||||
|
// res = miniflac_sync( |
||||||
|
// flac_.get(), reinterpret_cast<const uint8_t*>(buf.data()), |
||||||
|
// buf.size_bytes(), &bytes_used); |
||||||
|
// return bytes_used; |
||||||
|
// }); |
||||||
|
// if (res != MINIFLAC_OK) { |
||||||
|
// ESP_LOGI(kTag, "IT HAPPENED HERE! %d", res); |
||||||
|
// } |
||||||
|
|
||||||
|
// ESP_LOGI(kTag, "Decoder state: %d", flac_->state); |
||||||
|
// ESP_LOGI(kTag, "Frame header state: %d", flac_->frame.header.state); |
||||||
|
|
||||||
|
// // TODO: Sample number is not guaranteed, could be block index. |
||||||
|
// ESP_LOGI(kTag, "Ended up... at sample %llu", flac_->frame.header.sample_number); |
||||||
|
// ESP_LOGI(kTag, "and block index: %lu", flac_->frame.header.frame_number); |
||||||
|
// ESP_LOGI(kTag, "total samples: %llu", total_samples); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
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()); |
||||||
|
ESP_LOGI(kTag, "EOF? %s", is_eof ? "true" : "false"); |
||||||
|
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 { |
||||||
|
ESP_LOGI(kTag, "Failed: decoder result: %d", res); |
||||||
|
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(); |
||||||
|
ESP_LOGI(kTag, "Samples written %lu", (uint32_t)samples_written); |
||||||
|
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