summaryrefslogtreecommitdiff
path: root/src/codecs
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2023-08-09 11:22:08 +1000
committerjacqueline <me@jacqueline.id.au>2023-08-09 11:22:08 +1000
commit578c3737f8c07e543b90f964da0e89db1c18bb9a (patch)
tree9463cef1108936bd25c76d17e5f0ea26a6412a36 /src/codecs
parentf277bd5d0c6fdb9e2c125a2a6cc32675f87056cf (diff)
downloadtangara-fw-578c3737f8c07e543b90f964da0e89db1c18bb9a.tar.gz
Add vorbis support whilst we're here
Diffstat (limited to 'src/codecs')
-rw-r--r--src/codecs/CMakeLists.txt2
-rw-r--r--src/codecs/codec.cpp3
-rw-r--r--src/codecs/include/ogg.hpp15
-rw-r--r--src/codecs/include/stbvorbis.hpp42
-rw-r--r--src/codecs/include/types.hpp1
-rw-r--r--src/codecs/include/vorbis.hpp58
-rw-r--r--src/codecs/ogg.cpp109
-rw-r--r--src/codecs/opus.cpp59
-rw-r--r--src/codecs/stbvorbis.cpp128
-rw-r--r--src/codecs/vorbis.cpp162
10 files changed, 389 insertions, 190 deletions
diff --git a/src/codecs/CMakeLists.txt b/src/codecs/CMakeLists.txt
index 3ce1ac86..9f0febb9 100644
--- a/src/codecs/CMakeLists.txt
+++ b/src/codecs/CMakeLists.txt
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: GPL-3.0-only
idf_component_register(
- SRCS "codec.cpp" "mad.cpp" "foxenflac.cpp" "opus.cpp"
+ SRCS "codec.cpp" "mad.cpp" "foxenflac.cpp" "opus.cpp" "ogg.cpp" "vorbis.cpp"
INCLUDE_DIRS "include"
REQUIRES "result" "span" "libmad" "libfoxenflac" "tremor" "ogg")
diff --git a/src/codecs/codec.cpp b/src/codecs/codec.cpp
index 5e67c0de..9ac20097 100644
--- a/src/codecs/codec.cpp
+++ b/src/codecs/codec.cpp
@@ -12,6 +12,7 @@
#include "foxenflac.hpp"
#include "opus.hpp"
#include "mad.hpp"
+#include "vorbis.hpp"
#include "types.hpp"
namespace codecs {
@@ -20,6 +21,8 @@ auto CreateCodecForType(StreamType type) -> std::optional<ICodec*> {
switch (type) {
case StreamType::kMp3:
return new MadMp3Decoder();
+ case StreamType::kVorbis:
+ return new TremorVorbisDecoder();
case StreamType::kFlac:
return new FoxenFlacDecoder();
case StreamType::kOpus:
diff --git a/src/codecs/include/ogg.hpp b/src/codecs/include/ogg.hpp
index 2d6ea8c5..a27e961e 100644
--- a/src/codecs/include/ogg.hpp
+++ b/src/codecs/include/ogg.hpp
@@ -21,16 +21,23 @@ class OggContainer {
OggContainer();
~OggContainer();
- auto AddBytes(cpp::span<const std::byte>) -> void;
- auto HasNextPacket() -> bool;
- auto NextPacket() -> cpp::span<uint8_t>;
- auto PeekPacket() -> cpp::span<uint8_t>;
+ auto AddBytes(cpp::span<const std::byte>) -> bool;
+
+ auto Next() -> bool;
+ auto Current() -> cpp::span<uint8_t>;
+ auto HasPacket() -> bool;
private:
+ auto AdvancePage() -> bool;
+ auto AdvancePacket() -> bool;
+
ogg_sync_state sync_;
ogg_stream_state stream_;
ogg_page page_;
ogg_packet packet_;
+
+ bool has_stream_;
+ bool has_packet_;
};
} // namespace codecs \ No newline at end of file
diff --git a/src/codecs/include/stbvorbis.hpp b/src/codecs/include/stbvorbis.hpp
deleted file mode 100644
index 045e264e..00000000
--- a/src/codecs/include/stbvorbis.hpp
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2023 jacqueline <me@jacqueline.id.au>
- *
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-#pragma once
-
-#include <cstddef>
-#include <cstdint>
-#include <memory>
-#include <optional>
-#include <string>
-#include <utility>
-
-#include "stb_vorbis.h"
-
-#include "codec.hpp"
-
-namespace codecs {
-
-class StbVorbisDecoder : public ICodec {
- public:
- StbVorbisDecoder();
- ~StbVorbisDecoder();
-
- auto BeginStream(cpp::span<const std::byte>) -> Result<OutputFormat> override;
- auto ContinueStream(cpp::span<const std::byte>, cpp::span<std::byte>)
- -> Result<OutputInfo> override;
- auto SeekStream(cpp::span<const std::byte> input, std::size_t target_sample)
- -> Result<void> override;
-
- private:
- stb_vorbis* vorbis_;
-
- int current_sample_;
- int num_channels_;
- int num_samples_;
- float** samples_array_;
-};
-
-} // namespace codecs
diff --git a/src/codecs/include/types.hpp b/src/codecs/include/types.hpp
index 2f669448..e0bba47d 100644
--- a/src/codecs/include/types.hpp
+++ b/src/codecs/include/types.hpp
@@ -13,6 +13,7 @@ namespace codecs {
enum class StreamType {
kMp3,
kPcm,
+ kVorbis,
kFlac,
kOpus,
};
diff --git a/src/codecs/include/vorbis.hpp b/src/codecs/include/vorbis.hpp
new file mode 100644
index 00000000..2804bb7c
--- /dev/null
+++ b/src/codecs/include/vorbis.hpp
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2023 jacqueline <me@jacqueline.id.au>
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#pragma once
+
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <string>
+#include <utility>
+
+#include "ivorbisfile.h"
+#include "ogg.hpp"
+#include "ogg/ogg.h"
+#include "opus.h"
+#include "sample.hpp"
+#include "span.hpp"
+
+#include "codec.hpp"
+
+namespace codecs {
+
+class TremorVorbisDecoder : public ICodec {
+ public:
+ TremorVorbisDecoder();
+ ~TremorVorbisDecoder();
+
+ /*
+ * Returns the output format for the next frame in the stream. MP3 streams
+ * may represent multiple distinct tracks, with different bitrates, and so we
+ * handle the stream only on a frame-by-frame basis.
+ */
+ auto BeginStream(cpp::span<const std::byte>) -> Result<OutputFormat> override;
+
+ /*
+ * Writes samples for the current frame.
+ */
+ auto ContinueStream(cpp::span<const std::byte> input,
+ cpp::span<sample::Sample> output)
+ -> Result<OutputInfo> override;
+
+ auto SeekStream(cpp::span<const std::byte> input, std::size_t target_sample)
+ -> Result<void> override;
+
+ auto ReadCallback() -> cpp::span<const std::byte>;
+ auto AfterReadCallback(size_t bytes_read) -> void;
+
+ private:
+ OggVorbis_File vorbis_;
+ cpp::span<const std::byte> input_;
+ size_t pos_in_input_;
+};
+
+} // namespace codecs
diff --git a/src/codecs/ogg.cpp b/src/codecs/ogg.cpp
index e69de29b..2b332a12 100644
--- a/src/codecs/ogg.cpp
+++ b/src/codecs/ogg.cpp
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2023 jacqueline <me@jacqueline.id.au>
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "ogg.hpp"
+#include <cstring>
+
+#include "esp_log.h"
+#include "ogg/ogg.h"
+
+namespace codecs {
+
+static constexpr char kTag[] = "ogg";
+
+OggContainer::OggContainer()
+ : sync_(),
+ stream_(),
+ page_(),
+ packet_(),
+ has_stream_(false),
+ has_packet_(false) {
+ ogg_sync_init(&sync_);
+ ogg_sync_pageout(&sync_, &page_);
+}
+
+OggContainer::~OggContainer() {
+ ogg_sync_clear(&sync_);
+ if (has_stream_) {
+ ogg_stream_clear(&stream_);
+ }
+}
+
+auto OggContainer::AddBytes(cpp::span<const std::byte> in) -> bool {
+ ESP_LOGI(kTag, "adding %u bytes to buffer", in.size());
+ char* buf = ogg_sync_buffer(&sync_, in.size());
+ if (buf == NULL) {
+ ESP_LOGE(kTag, "failed to allocate sync buffer");
+ return false;
+ }
+ std::memcpy(buf, in.data(), in.size());
+ if (ogg_sync_wrote(&sync_, in.size()) < 0) {
+ ESP_LOGE(kTag, "failed to write to sync buffer");
+ return false;
+ }
+ return AdvancePage() && AdvancePacket();
+}
+
+auto OggContainer::HasPacket() -> bool {
+ return has_packet_;
+}
+
+auto OggContainer::Next() -> bool {
+ if (AdvancePacket()) {
+ return true;
+ }
+ if (AdvancePage() && AdvancePacket()) {
+ return true;
+ }
+ return false;
+}
+
+auto OggContainer::Current() -> cpp::span<uint8_t> {
+ if (!has_packet_) {
+ return {};
+ }
+ ESP_LOGI(kTag, "getting packet, location %p size %li", packet_.packet,
+ packet_.bytes);
+ return {packet_.packet, static_cast<size_t>(packet_.bytes)};
+}
+
+auto OggContainer::AdvancePage() -> bool {
+ int err;
+ if ((err = ogg_sync_pageout(&sync_, &page_)) != 1) {
+ ESP_LOGE(kTag, "failed to assemble page, res %i", err);
+ return false;
+ }
+ if (!has_stream_) {
+ int serialno = ogg_page_serialno(&page_);
+ ESP_LOGI(kTag, "beginning ogg stream, serial number %i", serialno);
+ if ((err = ogg_stream_init(&stream_, serialno) < 0)) {
+ ESP_LOGE(kTag, "failed to init stream page, res %i", err);
+ return false;
+ }
+ has_stream_ = true;
+ }
+ if (ogg_stream_pagein(&stream_, &page_) < 0) {
+ ESP_LOGE(kTag, "failed to read in page");
+ return false;
+ }
+ return true;
+}
+
+auto OggContainer::AdvancePacket() -> bool {
+ has_packet_ = false;
+ int res;
+ while ((res = ogg_stream_packetout(&stream_, &packet_)) == -1) {
+ // Retry until we sync, or run out of data.
+ ESP_LOGW(kTag, "trying to sync stream...");
+ }
+ has_packet_ = res;
+ if (!has_packet_) {
+ ESP_LOGE(kTag, "failed to read out packet");
+ }
+ return has_packet_;
+}
+
+} // namespace codecs \ No newline at end of file
diff --git a/src/codecs/opus.cpp b/src/codecs/opus.cpp
index 2529d9ec..e0bc0c29 100644
--- a/src/codecs/opus.cpp
+++ b/src/codecs/opus.cpp
@@ -20,6 +20,7 @@
#include "esp_log.h"
#include "ogg/ogg.h"
#include "opus.h"
+#include "opus_defines.h"
#include "opus_types.h"
#include "result.hpp"
#include "sample.hpp"
@@ -49,12 +50,13 @@ XiphOpusDecoder::~XiphOpusDecoder() {
auto XiphOpusDecoder::BeginStream(const cpp::span<const std::byte> input)
-> Result<OutputFormat> {
- ogg_.AddBytes(input);
- if (!ogg_.HasNextPacket()) {
+ if (!ogg_.AddBytes(input)) {
+ ESP_LOGI(kTag, "need more input to begin");
return {input.size(), cpp::fail(Error::kOutOfInput)};
}
- auto packet = ogg_.NextPacket();
+ auto packet = ogg_.Current();
int num_channels = opus_packet_get_nb_channels(packet.data());
+ ESP_LOGI(kTag, "opus stream has %i channels", num_channels);
if (num_channels > 2) {
// Too many channels; we can't handle this.
// TODO: better error
@@ -78,24 +80,51 @@ auto XiphOpusDecoder::ContinueStream(cpp::span<const std::byte> input,
-> Result<OutputInfo> {
size_t bytes_used = 0;
if (pos_in_buffer_ >= samples_in_buffer_) {
- ESP_LOGI(kTag, "sample buffer is empty. parsing more.");
- if (!ogg_.HasNextPacket()) {
+ if (!ogg_.HasPacket()) {
bytes_used = input.size();
- ogg_.AddBytes(input);
+ if (!ogg_.AddBytes(input)) {
+ return {bytes_used, cpp::fail(Error::kOutOfInput)};
+ }
}
- if (!ogg_.HasNextPacket()) {
- return {bytes_used, cpp::fail(Error::kOutOfInput)};
- }
-
- auto packet = ogg_.NextPacket();
+ auto packet = ogg_.Current();
pos_in_buffer_ = 0;
- samples_in_buffer_ =
- opus_decode(opus_, packet.data(), packet.size_bytes(),
- sample_buffer_.data(), sample_buffer_.size(), 0);
+ samples_in_buffer_ = 0;
+ while (samples_in_buffer_ <= 0 && ogg_.HasPacket()) {
+ samples_in_buffer_ =
+ opus_decode(opus_, packet.data(), packet.size_bytes(),
+ sample_buffer_.data(), sample_buffer_.size(), 0);
+ ogg_.Next();
+ }
if (samples_in_buffer_ < 0) {
- ESP_LOGE(kTag, "error decoding stream");
+ std::string err_str;
+ switch (samples_in_buffer_) {
+ case OPUS_BAD_ARG:
+ err_str = "OPUS_BAD_ARG";
+ break;
+ case OPUS_BUFFER_TOO_SMALL:
+ err_str = "OPUS_BUFFER_TOO_SMALL";
+ break;
+ case OPUS_INTERNAL_ERROR:
+ err_str = "OPUS_INTERNAL_ERROR";
+ break;
+ case OPUS_INVALID_PACKET:
+ err_str = "OPUS_INVALID_PACKET";
+ break;
+ case OPUS_UNIMPLEMENTED:
+ err_str = "OPUS_UNIMPLEMENTED";
+ break;
+ case OPUS_INVALID_STATE:
+ err_str = "OPUS_INVALID_STATE";
+ break;
+ case OPUS_ALLOC_FAIL:
+ err_str = "OPUS_ALLOC_FAIL";
+ break;
+ default:
+ err_str = "unknown";
+ }
+ ESP_LOGE(kTag, "error decoding stream, err %s", err_str.c_str());
return {bytes_used, cpp::fail(Error::kMalformedData)};
}
}
diff --git a/src/codecs/stbvorbis.cpp b/src/codecs/stbvorbis.cpp
deleted file mode 100644
index de315416..00000000
--- a/src/codecs/stbvorbis.cpp
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright 2023 jacqueline <me@jacqueline.id.au>
- *
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-#include "stbvorbis.hpp"
-#include <stdint.h>
-
-#include <cstdint>
-#include <optional>
-
-#include "stb_vorbis.h"
-
-namespace codecs {
-
-StbVorbisDecoder::StbVorbisDecoder()
- : vorbis_(nullptr),
- current_sample_(-1),
- num_channels_(0),
- num_samples_(0),
- samples_array_(NULL) {}
-
-StbVorbisDecoder::~StbVorbisDecoder() {
- if (vorbis_ != nullptr) {
- stb_vorbis_close(vorbis_);
- }
-}
-
-static uint32_t scaleToBits(float sample, uint8_t bits) {
- // Scale to range.
- int32_t max_val = (1 << (bits - 1));
- int32_t fixed_point = sample * max_val;
-
- // Clamp within bounds.
- fixed_point = std::clamp(fixed_point, -max_val, max_val);
-
- // Remove sign.
- return *reinterpret_cast<uint32_t*>(&fixed_point);
-}
-
-auto StbVorbisDecoder::BeginStream(const cpp::span<const std::byte> input)
- -> Result<OutputFormat> {
- if (vorbis_ != nullptr) {
- stb_vorbis_close(vorbis_);
- vorbis_ = nullptr;
- }
- current_sample_ = -1;
- int bytes_read = 0;
- int error = 0;
- vorbis_ =
- stb_vorbis_open_pushdata(reinterpret_cast<const uint8_t*>(input.data()),
- input.size_bytes(), &bytes_read, &error, NULL);
- if (error != 0) {
- return {0, cpp::fail(Error::kMalformedData)};
- }
- stb_vorbis_info info = stb_vorbis_get_info(vorbis_);
- return {bytes_read,
- OutputFormat{.num_channels = static_cast<uint8_t>(info.channels),
- .bits_per_sample = 24,
- .sample_rate_hz = info.sample_rate}};
-}
-
-auto StbVorbisDecoder::ContinueStream(cpp::span<const std::byte> input,
- cpp::span<std::byte> output)
- -> Result<OutputInfo> {
- std::size_t bytes_used = 0;
- if (current_sample_ < 0) {
- num_channels_ = 0;
- num_samples_ = 0;
- samples_array_ = NULL;
-
- while (true) {
- auto cropped = input.subspan(bytes_used);
- std::size_t b = stb_vorbis_decode_frame_pushdata(
- vorbis_, reinterpret_cast<const uint8_t*>(cropped.data()),
- cropped.size_bytes(), &num_channels_, &samples_array_, &num_samples_);
- if (b == 0) {
- return {bytes_used, cpp::fail(Error::kOutOfInput)};
- }
- bytes_used += b;
-
- if (num_samples_ == 0) {
- // Decoder is synchronising. Decode more bytes.
- continue;
- }
- if (num_channels_ == 0 || samples_array_ == NULL) {
- // The decoder isn't satisfying its contract.
- return {bytes_used, cpp::fail(Error::kInternalError)};
- }
- current_sample_ = 0;
- break;
- }
- }
-
- // We successfully decoded a frame. Time to write out the samples.
- std::size_t output_byte = 0;
- while (current_sample_ < num_samples_) {
- if (output_byte + (2 * num_channels_) >= output.size()) {
- return {0, OutputInfo{.bytes_written = output_byte,
- .is_finished_writing = false}};
- }
-
- for (int channel = 0; channel < num_channels_; channel++) {
- float raw_sample = samples_array_[channel][current_sample_];
-
- uint16_t sample_24 = scaleToBits(raw_sample, 24);
- output[output_byte++] = static_cast<std::byte>((sample_24 >> 16) & 0xFF);
- output[output_byte++] = static_cast<std::byte>((sample_24 >> 8) & 0xFF);
- output[output_byte++] = static_cast<std::byte>((sample_24)&0xFF);
- // Pad to 32 bits for alignment.
- output[output_byte++] = static_cast<std::byte>(0);
- }
- current_sample_++;
- }
-
- current_sample_ = -1;
- return {bytes_used, OutputInfo{.bytes_written = output_byte,
- .is_finished_writing = true}};
-}
-
-auto StbVorbisDecoder::SeekStream(cpp::span<const std::byte> input,
- std::size_t target_sample) -> Result<void> {
- // TODO(jacqueline): Implement me.
- return {0, {}};
-}
-
-} // namespace codecs
diff --git a/src/codecs/vorbis.cpp b/src/codecs/vorbis.cpp
new file mode 100644
index 00000000..88ffbec4
--- /dev/null
+++ b/src/codecs/vorbis.cpp
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2023 jacqueline <me@jacqueline.id.au>
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "ivorbiscodec.h"
+#include "ivorbisfile.h"
+#include "ogg/config_types.h"
+#include "opus.hpp"
+
+#include <stdint.h>
+#include <sys/_stdint.h>
+
+#include <cstdint>
+#include <cstring>
+#include <optional>
+
+#include "esp_heap_caps.h"
+#include "mad.h"
+
+#include "codec.hpp"
+#include "esp_log.h"
+#include "ogg/ogg.h"
+#include "opus.h"
+#include "opus_defines.h"
+#include "opus_types.h"
+#include "result.hpp"
+#include "sample.hpp"
+#include "types.hpp"
+#include "vorbis.hpp"
+
+namespace codecs {
+
+static constexpr char kTag[] = "vorbis";
+
+size_t read_cb(void* ptr, size_t size, size_t nmemb, void* instance) {
+ TremorVorbisDecoder* dec = reinterpret_cast<TremorVorbisDecoder*>(instance);
+ auto input = dec->ReadCallback();
+ size_t amount_to_read = std::min<size_t>(size * nmemb, input.size_bytes());
+ std::memcpy(ptr, input.data(), amount_to_read);
+ dec->AfterReadCallback(amount_to_read);
+ return amount_to_read;
+}
+
+int seek_cb(void* instance, ogg_int64_t offset, int whence) {
+ // Seeking is handled separately.
+ return -1;
+}
+
+int close_cb(void* instance) {
+ return 0;
+}
+
+static const ov_callbacks kCallbacks{
+ .read_func = read_cb,
+ .seek_func = seek_cb,
+ .close_func = close_cb,
+ .tell_func = NULL, // Not seekable
+};
+
+TremorVorbisDecoder::TremorVorbisDecoder()
+ : vorbis_(), input_(), pos_in_input_(0) {}
+
+TremorVorbisDecoder::~TremorVorbisDecoder() {
+ ov_clear(&vorbis_);
+}
+
+auto TremorVorbisDecoder::BeginStream(const cpp::span<const std::byte> input)
+ -> Result<OutputFormat> {
+ int res = ov_open_callbacks(this, &vorbis_,
+ reinterpret_cast<const char*>(input.data()),
+ input.size(), kCallbacks);
+ if (res < 0) {
+ std::string err;
+ switch (res) {
+ case OV_EREAD:
+ err = "OV_EREAD";
+ break;
+ case OV_ENOTVORBIS:
+ err = "OV_ENOTVORBIS";
+ break;
+ case OV_EVERSION:
+ err = "OV_EVERSION";
+ break;
+ case OV_EBADHEADER:
+ err = "OV_EBADHEADER";
+ break;
+ case OV_EFAULT:
+ err = "OV_EFAULT";
+ break;
+ default:
+ err = "unknown";
+ }
+ ESP_LOGE(kTag, "error beginning stream: %s", err.c_str());
+ return {input.size(), cpp::fail(Error::kMalformedData)};
+ }
+
+ vorbis_info* info = ov_info(&vorbis_, -1);
+ if (info == NULL) {
+ ESP_LOGE(kTag, "failed to get stream info");
+ return {input.size(), cpp::fail(Error::kMalformedData)};
+ }
+
+ return {input.size(),
+ OutputFormat{
+ .num_channels = static_cast<uint8_t>(info->channels),
+ .sample_rate_hz = static_cast<uint32_t>(info->rate),
+ .bits_per_second = info->bitrate_nominal,
+ }};
+}
+
+auto TremorVorbisDecoder::ContinueStream(cpp::span<const std::byte> input,
+ cpp::span<sample::Sample> output)
+ -> Result<OutputInfo> {
+ cpp::span<int16_t> staging_buffer{
+ reinterpret_cast<int16_t*>(output.subspan(output.size() / 2).data()),
+ output.size_bytes() / 2};
+
+ input_ = input;
+ pos_in_input_ = 0;
+
+ int bitstream;
+ long bytes_written =
+ ov_read(&vorbis_, reinterpret_cast<char*>(staging_buffer.data()),
+ staging_buffer.size_bytes(), &bitstream);
+ if (bytes_written == OV_HOLE) {
+ ESP_LOGE(kTag, "got OV_HOLE");
+ return {pos_in_input_, cpp::fail(Error::kMalformedData)};
+ } else if (bytes_written == OV_EBADLINK) {
+ ESP_LOGE(kTag, "got OV_EBADLINK");
+ return {pos_in_input_, cpp::fail(Error::kMalformedData)};
+ } else if (bytes_written == 0) {
+ return {pos_in_input_, cpp::fail(Error::kOutOfInput)};
+ }
+
+ for (int i = 0; i < bytes_written / 2; i++) {
+ output[i] = sample::FromSigned(staging_buffer[i], 16);
+ }
+
+ return {pos_in_input_,
+ OutputInfo{
+ .samples_written = static_cast<size_t>(bytes_written / 2),
+ .is_finished_writing = bytes_written == 0,
+ }};
+}
+
+auto TremorVorbisDecoder::SeekStream(cpp::span<const std::byte> input,
+ std::size_t target_sample)
+ -> Result<void> {
+ return {};
+}
+
+auto TremorVorbisDecoder::ReadCallback() -> cpp::span<const std::byte> {
+ return input_.subspan(pos_in_input_);
+}
+
+auto TremorVorbisDecoder::AfterReadCallback(size_t bytes_read) -> void {
+ pos_in_input_ += bytes_read;
+}
+
+} // namespace codecs