diff options
Diffstat (limited to 'src/codecs')
| -rw-r--r-- | src/codecs/CMakeLists.txt | 4 | ||||
| -rw-r--r-- | src/codecs/codec.cpp | 4 | ||||
| -rw-r--r-- | src/codecs/dr_flac.cpp | 119 | ||||
| -rw-r--r-- | src/codecs/include/dr_flac.hpp (renamed from src/codecs/include/miniflac.hpp) | 19 | ||||
| -rw-r--r-- | src/codecs/miniflac.cpp | 213 |
5 files changed, 130 insertions, 229 deletions
diff --git a/src/codecs/CMakeLists.txt b/src/codecs/CMakeLists.txt index 4b296c84..b6481bd1 100644 --- a/src/codecs/CMakeLists.txt +++ b/src/codecs/CMakeLists.txt @@ -3,10 +3,10 @@ # SPDX-License-Identifier: GPL-3.0-only idf_component_register( - SRCS "miniflac.cpp" "codec.cpp" "mad.cpp" "opus.cpp" "vorbis.cpp" + SRCS "dr_flac.cpp" "codec.cpp" "mad.cpp" "opus.cpp" "vorbis.cpp" "source_buffer.cpp" "sample.cpp" "wav.cpp" INCLUDE_DIRS "include" - REQUIRES "result" "span" "libmad" "miniflac" "tremor" "opusfile" "memory" "util" + REQUIRES "result" "span" "libmad" "drflac" "tremor" "opusfile" "memory" "util" "komihash") target_compile_options("${COMPONENT_LIB}" PRIVATE ${EXTRA_WARNINGS}) diff --git a/src/codecs/codec.cpp b/src/codecs/codec.cpp index 7bc591aa..a51c40d6 100644 --- a/src/codecs/codec.cpp +++ b/src/codecs/codec.cpp @@ -10,7 +10,7 @@ #include <optional> #include "mad.hpp" -#include "miniflac.hpp" +#include "dr_flac.hpp" #include "opus.hpp" #include "types.hpp" #include "vorbis.hpp" @@ -42,7 +42,7 @@ auto CreateCodecForType(StreamType type) -> std::optional<ICodec*> { case StreamType::kVorbis: return new TremorVorbisDecoder(); case StreamType::kFlac: - return new MiniFlacDecoder(); + return new DrFlacDecoder(); case StreamType::kOpus: return new XiphOpusDecoder(); case StreamType::kWav: diff --git a/src/codecs/dr_flac.cpp b/src/codecs/dr_flac.cpp new file mode 100644 index 00000000..cacf7a6e --- /dev/null +++ b/src/codecs/dr_flac.cpp @@ -0,0 +1,119 @@ +/* + * 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}; +} + +auto DrFlacDecoder::SeekTo(size_t target) -> cpp::result<void, Error> { + return {}; +} + +} // namespace codecs diff --git a/src/codecs/include/miniflac.hpp b/src/codecs/include/dr_flac.hpp index d1daca2f..8dcfdaf5 100644 --- a/src/codecs/include/miniflac.hpp +++ b/src/codecs/include/dr_flac.hpp @@ -6,7 +6,6 @@ #pragma once -#include <sys/_stdint.h> #include <cstddef> #include <cstdint> #include <memory> @@ -14,7 +13,7 @@ #include <string> #include <utility> -#include "miniflac.h" +#include "dr_flac.h" #include "sample.hpp" #include "source_buffer.hpp" #include "span.hpp" @@ -23,10 +22,10 @@ namespace codecs { -class MiniFlacDecoder : public ICodec { +class DrFlacDecoder : public ICodec { public: - MiniFlacDecoder(); - ~MiniFlacDecoder(); + DrFlacDecoder(); + ~DrFlacDecoder(); auto OpenStream(std::shared_ptr<IStream> input,uint32_t offset) -> cpp::result<OutputFormat, Error> override; @@ -36,16 +35,12 @@ class MiniFlacDecoder : public ICodec { auto SeekTo(std::size_t target_sample) -> cpp::result<void, Error> override; - MiniFlacDecoder(const MiniFlacDecoder&) = delete; - MiniFlacDecoder& operator=(const MiniFlacDecoder&) = delete; + DrFlacDecoder(const DrFlacDecoder&) = delete; + DrFlacDecoder& operator=(const DrFlacDecoder&) = delete; private: std::shared_ptr<IStream> input_; - SourceBuffer buffer_; - - std::unique_ptr<miniflac_t> flac_; - std::array<int32_t*, 2> samples_by_channel_; - std::optional<size_t> current_sample_; + drflac *flac_; }; } // namespace codecs diff --git a/src/codecs/miniflac.cpp b/src/codecs/miniflac.cpp deleted file mode 100644 index 45e063c7..00000000 --- a/src/codecs/miniflac.cpp +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#include "miniflac.hpp" -#include <stdint.h> - -#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) { - buffer_.ConsumeBytes(fn); - if (res == MINIFLAC_CONTINUE && !buffer_.Refill(input_.get())) { - 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) { - // TODO: This assumes a constant sample_rate - uint64_t target_sample = sample_rate * offset; - ESP_LOGI(kTag, "seeking to %lu seconds (sample=%llu)", offset, - target_sample); - - while (true) { - 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); - } - - if (!miniflac_is_frame(flac_.get())) { - continue; - } - - uint64_t samples_in_frame = miniflac_frame_block_size(flac_.get()); - if (samples_in_frame <= target_sample) { - target_sample -= samples_in_frame; - } else { - break; - } - } - } - - 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 |
