From 62d51a304eb02f0eab0645488c0b922b4a45e1c9 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Tue, 19 Dec 2023 18:11:23 +1100 Subject: replace foxenflac with miniflac it's better! --- src/codecs/CMakeLists.txt | 4 +- src/codecs/codec.cpp | 4 +- src/codecs/foxenflac.cpp | 110 ------------------------ src/codecs/include/foxenflac.hpp | 48 ----------- src/codecs/include/miniflac.hpp | 51 ++++++++++++ src/codecs/miniflac.cpp | 176 +++++++++++++++++++++++++++++++++++++++ src/database/track.cpp | 4 +- 7 files changed, 233 insertions(+), 164 deletions(-) delete mode 100644 src/codecs/foxenflac.cpp delete mode 100644 src/codecs/include/foxenflac.hpp create mode 100644 src/codecs/include/miniflac.hpp create mode 100644 src/codecs/miniflac.cpp (limited to 'src') 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 #include -#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 { 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 - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#include "foxenflac.hpp" -#include -#include - -#include - -#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 input) - -> cpp::result { - input_ = input; - - bool eof = false; - fx_flac_state_t state; - do { - eof = buffer_.Refill(input_.get()); - buffer_.ConsumeBytes([&](cpp::span buf) -> size_t { - uint32_t bytes_used = buf.size(); - state = - fx_flac_process(flac_, reinterpret_cast(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(channels), - .sample_rate_hz = static_cast(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 output) - -> cpp::result { - bool is_eof = buffer_.Refill(input_.get()); - - cpp::span output32{reinterpret_cast(output.data()), - output.size() / 2}; - uint32_t samples_written = output32.size(); - - fx_flac_state_t state; - buffer_.ConsumeBytes([&](cpp::span buf) -> size_t { - uint32_t bytes_read = buf.size_bytes(); - state = fx_flac_process(flac_, reinterpret_cast(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 { - return {}; -} - -} // namespace codecs diff --git a/src/codecs/include/foxenflac.hpp b/src/codecs/include/foxenflac.hpp deleted file mode 100644 index 7522d967..00000000 --- a/src/codecs/include/foxenflac.hpp +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2023 jacqueline - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "foxen/flac.h" -#include "sample.hpp" -#include "source_buffer.hpp" -#include "span.hpp" - -#include "codec.hpp" - -namespace codecs { - -class FoxenFlacDecoder : public ICodec { - public: - FoxenFlacDecoder(); - ~FoxenFlacDecoder(); - - auto OpenStream(std::shared_ptr input) - -> cpp::result override; - - auto DecodeTo(cpp::span destination) - -> cpp::result override; - - auto SeekTo(std::size_t target_sample) -> cpp::result override; - - FoxenFlacDecoder(const FoxenFlacDecoder&) = delete; - FoxenFlacDecoder& operator=(const FoxenFlacDecoder&) = delete; - - private: - std::shared_ptr input_; - SourceBuffer buffer_; - - fx_flac_t* flac_; -}; - -} // namespace codecs diff --git a/src/codecs/include/miniflac.hpp b/src/codecs/include/miniflac.hpp new file mode 100644 index 00000000..d57b08a3 --- /dev/null +++ b/src/codecs/include/miniflac.hpp @@ -0,0 +1,51 @@ +/* + * Copyright 2023 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "miniflac.h" +#include "sample.hpp" +#include "source_buffer.hpp" +#include "span.hpp" + +#include "codec.hpp" + +namespace codecs { + +class MiniFlacDecoder : public ICodec { + public: + MiniFlacDecoder(); + ~MiniFlacDecoder(); + + auto OpenStream(std::shared_ptr input) + -> cpp::result override; + + auto DecodeTo(cpp::span destination) + -> cpp::result override; + + auto SeekTo(std::size_t target_sample) -> cpp::result override; + + MiniFlacDecoder(const MiniFlacDecoder&) = delete; + MiniFlacDecoder& operator=(const MiniFlacDecoder&) = delete; + + private: + std::shared_ptr input_; + SourceBuffer buffer_; + + std::unique_ptr flac_; + std::array samples_by_channel_; + std::optional 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 + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "miniflac.hpp" +#include +#include + +#include + +#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( + 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( + 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 input) + -> cpp::result { + 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 buf) -> size_t { + uint32_t bytes_used = 0; + res = miniflac_streaminfo_sample_rate( + flac_.get(), reinterpret_cast(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 buf) -> size_t { + uint32_t bytes_used = 0; + res = miniflac_streaminfo_channels( + flac_.get(), reinterpret_cast(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 buf) -> size_t { + uint32_t bytes_used = 0; + res = miniflac_streaminfo_total_samples( + flac_.get(), reinterpret_cast(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(channels), + .sample_rate_hz = static_cast(sample_rate), + .total_samples = total_samples * channels, + }; + + return format; +} + +auto MiniFlacDecoder::DecodeTo(cpp::span output) + -> cpp::result { + 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 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(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 { + return {}; +} + +} // namespace codecs diff --git a/src/database/track.cpp b/src/database/track.cpp index acd479f1..58097cef 100644 --- a/src/database/track.cpp +++ b/src/database/track.cpp @@ -203,7 +203,7 @@ auto TrackTags::disc() const -> const std::optional& { } auto TrackTags::disc(const std::string_view s) -> void { - disc_ = std::stoi({s.data(), s.size()}); + disc_ = std::strtol(s.data(), nullptr, 10); } auto TrackTags::track() const -> const std::optional& { @@ -211,7 +211,7 @@ auto TrackTags::track() const -> const std::optional& { } auto TrackTags::track(const std::string_view s) -> void { - track_ = std::stoi({s.data(), s.size()}); + track_ = std::strtol(s.data(), nullptr, 10); } auto TrackTags::albumOrder() const -> uint32_t { -- cgit v1.2.3