summaryrefslogtreecommitdiff
path: root/src/codecs
diff options
context:
space:
mode:
Diffstat (limited to 'src/codecs')
-rw-r--r--src/codecs/CMakeLists.txt4
-rw-r--r--src/codecs/codec.cpp4
-rw-r--r--src/codecs/foxenflac.cpp110
-rw-r--r--src/codecs/include/miniflac.hpp (renamed from src/codecs/include/foxenflac.hpp)17
-rw-r--r--src/codecs/miniflac.cpp176
5 files changed, 190 insertions, 121 deletions
diff --git a/src/codecs/CMakeLists.txt b/src/codecs/CMakeLists.txt
index 748e1440..d42c7426 100644
--- a/src/codecs/CMakeLists.txt
+++ b/src/codecs/CMakeLists.txt
@@ -3,9 +3,9 @@
# SPDX-License-Identifier: GPL-3.0-only
idf_component_register(
- SRCS "codec.cpp" "mad.cpp" "foxenflac.cpp" "opus.cpp" "vorbis.cpp"
+ SRCS "codec.cpp" "mad.cpp" "miniflac.cpp" "opus.cpp" "vorbis.cpp"
"source_buffer.cpp"
INCLUDE_DIRS "include"
- REQUIRES "result" "span" "libmad" "libfoxenflac" "tremor" "opusfile" "memory")
+ REQUIRES "result" "span" "libmad" "miniflac" "tremor" "opusfile" "memory")
target_compile_options("${COMPONENT_LIB}" PRIVATE ${EXTRA_WARNINGS})
diff --git a/src/codecs/codec.cpp b/src/codecs/codec.cpp
index 3610dea8..d81d4b05 100644
--- a/src/codecs/codec.cpp
+++ b/src/codecs/codec.cpp
@@ -9,8 +9,8 @@
#include <memory>
#include <optional>
-#include "foxenflac.hpp"
#include "mad.hpp"
+#include "miniflac.hpp"
#include "opus.hpp"
#include "types.hpp"
#include "vorbis.hpp"
@@ -41,7 +41,7 @@ auto CreateCodecForType(StreamType type) -> std::optional<ICodec*> {
case StreamType::kVorbis:
return new TremorVorbisDecoder();
case StreamType::kFlac:
- return new FoxenFlacDecoder();
+ return new MiniFlacDecoder();
case StreamType::kOpus:
return new XiphOpusDecoder();
default:
diff --git a/src/codecs/foxenflac.cpp b/src/codecs/foxenflac.cpp
deleted file mode 100644
index 1fd95cd1..00000000
--- a/src/codecs/foxenflac.cpp
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright 2023 jacqueline <me@jacqueline.id.au>
- *
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-#include "foxenflac.hpp"
-#include <stdint.h>
-#include <sys/_stdint.h>
-
-#include <cstdlib>
-
-#include "esp_log.h"
-#include "foxen/flac.h"
-#include "sample.hpp"
-
-namespace codecs {
-
-[[maybe_unused]] static const char kTag[] = "flac";
-
-FoxenFlacDecoder::FoxenFlacDecoder()
- : input_(),
- buffer_(),
- flac_(fx_flac_init(
- heap_caps_malloc(fx_flac_size(FLAC_SUBSET_MAX_BLOCK_SIZE_48KHZ, 2),
- MALLOC_CAP_SPIRAM),
- FLAC_SUBSET_MAX_BLOCK_SIZE_48KHZ,
- 2)) {}
-
-FoxenFlacDecoder::~FoxenFlacDecoder() {
- free(flac_);
-}
-
-auto FoxenFlacDecoder::OpenStream(std::shared_ptr<IStream> input)
- -> cpp::result<OutputFormat, Error> {
- input_ = input;
-
- bool eof = false;
- fx_flac_state_t state;
- do {
- eof = buffer_.Refill(input_.get());
- buffer_.ConsumeBytes([&](cpp::span<std::byte> buf) -> size_t {
- uint32_t bytes_used = buf.size();
- state =
- fx_flac_process(flac_, reinterpret_cast<const uint8_t*>(buf.data()),
- &bytes_used, NULL, NULL);
- return bytes_used;
- });
- } while (state != FLAC_END_OF_METADATA && !eof);
-
- if (state != FLAC_END_OF_METADATA) {
- if (state == FLAC_ERR) {
- return cpp::fail(Error::kMalformedData);
- } else {
- return cpp::fail(Error::kOutOfInput);
- }
- }
-
- int64_t channels = fx_flac_get_streaminfo(flac_, FLAC_KEY_N_CHANNELS);
- int64_t fs = fx_flac_get_streaminfo(flac_, FLAC_KEY_SAMPLE_RATE);
- if (channels == FLAC_INVALID_METADATA_KEY ||
- fs == FLAC_INVALID_METADATA_KEY) {
- return cpp::fail(Error::kMalformedData);
- }
-
- OutputFormat format{
- .num_channels = static_cast<uint8_t>(channels),
- .sample_rate_hz = static_cast<uint32_t>(fs),
- };
-
- uint64_t num_samples = fx_flac_get_streaminfo(flac_, FLAC_KEY_N_SAMPLES);
- if (num_samples > 0) {
- format.total_samples = num_samples * channels;
- }
-
- return format;
-}
-
-auto FoxenFlacDecoder::DecodeTo(cpp::span<sample::Sample> output)
- -> cpp::result<OutputInfo, Error> {
- bool is_eof = buffer_.Refill(input_.get());
-
- cpp::span<int32_t> output32{reinterpret_cast<int32_t*>(output.data()),
- output.size() / 2};
- uint32_t samples_written = output32.size();
-
- fx_flac_state_t state;
- buffer_.ConsumeBytes([&](cpp::span<std::byte> buf) -> size_t {
- uint32_t bytes_read = buf.size_bytes();
- state = fx_flac_process(flac_, reinterpret_cast<const uint8_t*>(buf.data()),
- &bytes_read, output32.data(), &samples_written);
- return bytes_read;
- });
- if (state == FLAC_ERR) {
- return cpp::fail(Error::kMalformedData);
- }
-
- for (size_t i = 0; i < samples_written; i++) {
- output[i] = output32[i] >> 16;
- }
-
- return OutputInfo{.samples_written = samples_written,
- .is_stream_finished = samples_written == 0 && is_eof};
-}
-
-auto FoxenFlacDecoder::SeekTo(size_t target) -> cpp::result<void, Error> {
- return {};
-}
-
-} // namespace codecs
diff --git a/src/codecs/include/foxenflac.hpp b/src/codecs/include/miniflac.hpp
index 7522d967..d57b08a3 100644
--- a/src/codecs/include/foxenflac.hpp
+++ b/src/codecs/include/miniflac.hpp
@@ -6,6 +6,7 @@
#pragma once
+#include <sys/_stdint.h>
#include <cstddef>
#include <cstdint>
#include <memory>
@@ -13,7 +14,7 @@
#include <string>
#include <utility>
-#include "foxen/flac.h"
+#include "miniflac.h"
#include "sample.hpp"
#include "source_buffer.hpp"
#include "span.hpp"
@@ -22,10 +23,10 @@
namespace codecs {
-class FoxenFlacDecoder : public ICodec {
+class MiniFlacDecoder : public ICodec {
public:
- FoxenFlacDecoder();
- ~FoxenFlacDecoder();
+ MiniFlacDecoder();
+ ~MiniFlacDecoder();
auto OpenStream(std::shared_ptr<IStream> input)
-> cpp::result<OutputFormat, Error> override;
@@ -35,14 +36,16 @@ class FoxenFlacDecoder : public ICodec {
auto SeekTo(std::size_t target_sample) -> cpp::result<void, Error> override;
- FoxenFlacDecoder(const FoxenFlacDecoder&) = delete;
- FoxenFlacDecoder& operator=(const FoxenFlacDecoder&) = delete;
+ MiniFlacDecoder(const MiniFlacDecoder&) = delete;
+ MiniFlacDecoder& operator=(const MiniFlacDecoder&) = delete;
private:
std::shared_ptr<IStream> input_;
SourceBuffer buffer_;
- fx_flac_t* flac_;
+ std::unique_ptr<miniflac_t> flac_;
+ std::array<int32_t*, 2> samples_by_channel_;
+ std::optional<size_t> current_sample_;
};
} // namespace codecs
diff --git a/src/codecs/miniflac.cpp b/src/codecs/miniflac.cpp
new file mode 100644
index 00000000..cc261f75
--- /dev/null
+++ b/src/codecs/miniflac.cpp
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2023 jacqueline <me@jacqueline.id.au>
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "miniflac.hpp"
+#include <stdint.h>
+#include <sys/_stdint.h>
+
+#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++) {
+ // Full decoded frames too big to fit in internal ram :(
+ samples_by_channel_[i] = reinterpret_cast<int32_t*>(
+ heap_caps_malloc(kMaxFrameSize * sizeof(int32_t), MALLOC_CAP_SPIRAM));
+ }
+}
+
+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_) {
+ const uint8_t shift = flac_->frame.header.bps - 16;
+
+ 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_] >> shift, 16);
+ }
+ (*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