summaryrefslogtreecommitdiff
path: root/src/codecs
diff options
context:
space:
mode:
Diffstat (limited to 'src/codecs')
-rw-r--r--src/codecs/CMakeLists.txt16
-rw-r--r--src/codecs/include/ogg.hpp43
-rw-r--r--src/codecs/include/opus.hpp16
-rw-r--r--src/codecs/include/vorbis.hpp1
-rw-r--r--src/codecs/ogg.cpp109
-rw-r--r--src/codecs/opus.cpp152
6 files changed, 71 insertions, 266 deletions
diff --git a/src/codecs/CMakeLists.txt b/src/codecs/CMakeLists.txt
index 9f0febb9..91d3f319 100644
--- a/src/codecs/CMakeLists.txt
+++ b/src/codecs/CMakeLists.txt
@@ -3,20 +3,8 @@
# SPDX-License-Identifier: GPL-3.0-only
idf_component_register(
- SRCS "codec.cpp" "mad.cpp" "foxenflac.cpp" "opus.cpp" "ogg.cpp" "vorbis.cpp"
+ SRCS "codec.cpp" "mad.cpp" "foxenflac.cpp" "opus.cpp" "vorbis.cpp"
INCLUDE_DIRS "include"
- REQUIRES "result" "span" "libmad" "libfoxenflac" "tremor" "ogg")
+ REQUIRES "result" "span" "libmad" "libfoxenflac" "tremor" "opusfile")
target_compile_options("${COMPONENT_LIB}" PRIVATE ${EXTRA_WARNINGS})
-
-set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
-
-set(OPUS_FIXED_POINT ON)
-set(OPUS_ENABLE_FLOAT_API OFF)
-set(OPUS_INSTALL_PKG_CONFIG_MODULE OFF)
-set(OPUS_INSTALL_CMAKE_CONFIG_MODULE OFF)
-set(OPUS_BUILD_TESTING OFF)
-set(OPUS_BUILD_SHARED_LIBS OFF)
-
-add_subdirectory($ENV{PROJ_PATH}/lib/opus ${CMAKE_CURRENT_BINARY_DIR}/opus)
-target_link_libraries(${COMPONENT_LIB} PUBLIC opus)
diff --git a/src/codecs/include/ogg.hpp b/src/codecs/include/ogg.hpp
deleted file mode 100644
index a27e961e..00000000
--- a/src/codecs/include/ogg.hpp
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2023 jacqueline <me@jacqueline.id.au>
- *
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-#pragma once
-
-#include <sys/_stdint.h>
-#include <cstddef>
-#include <cstdint>
-#include <optional>
-
-#include "ogg/ogg.h"
-#include "span.hpp"
-
-namespace codecs {
-
-class OggContainer {
- public:
- OggContainer();
- ~OggContainer();
-
- 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/opus.hpp b/src/codecs/include/opus.hpp
index 50717b73..051cd0b9 100644
--- a/src/codecs/include/opus.hpp
+++ b/src/codecs/include/opus.hpp
@@ -13,9 +13,7 @@
#include <string>
#include <utility>
-#include "ogg.hpp"
-#include "ogg/ogg.h"
-#include "opus.h"
+#include "opusfile.h"
#include "sample.hpp"
#include "span.hpp"
@@ -45,13 +43,13 @@ class XiphOpusDecoder : public ICodec {
auto SeekStream(cpp::span<const std::byte> input, std::size_t target_sample)
-> Result<void> override;
- private:
- OggContainer ogg_;
- OpusDecoder* opus_;
- cpp::span<int16_t> sample_buffer_;
- int32_t pos_in_buffer_;
- int32_t samples_in_buffer_;
+ auto ReadCallback() -> cpp::span<const std::byte>;
+ auto AfterReadCallback(size_t bytes_read) -> void;
+ private:
+ OggOpusFile* opus_;
+ cpp::span<const std::byte> input_;
+ size_t pos_in_input_;
};
} // namespace codecs
diff --git a/src/codecs/include/vorbis.hpp b/src/codecs/include/vorbis.hpp
index 2804bb7c..ab15af19 100644
--- a/src/codecs/include/vorbis.hpp
+++ b/src/codecs/include/vorbis.hpp
@@ -14,7 +14,6 @@
#include <utility>
#include "ivorbisfile.h"
-#include "ogg.hpp"
#include "ogg/ogg.h"
#include "opus.h"
#include "sample.hpp"
diff --git a/src/codecs/ogg.cpp b/src/codecs/ogg.cpp
deleted file mode 100644
index 2b332a12..00000000
--- a/src/codecs/ogg.cpp
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * 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 e0bc0c29..a71c5fc0 100644
--- a/src/codecs/opus.cpp
+++ b/src/codecs/opus.cpp
@@ -18,10 +18,7 @@
#include "codec.hpp"
#include "esp_log.h"
-#include "ogg/ogg.h"
-#include "opus.h"
-#include "opus_defines.h"
-#include "opus_types.h"
+#include "opusfile.h"
#include "result.hpp"
#include "sample.hpp"
#include "types.hpp"
@@ -30,47 +27,52 @@ namespace codecs {
static constexpr char kTag[] = "opus";
-// "If this is less than the maximum packet duration (120ms; 5760 for 48kHz),
-// this function will not be capable of decoding some packets"
-static constexpr size_t kSampleBufferSize = 5760;
-
-XiphOpusDecoder::XiphOpusDecoder() : opus_(nullptr) {
- pos_in_buffer_ = 0;
- sample_buffer_ = {reinterpret_cast<opus_int16*>(
- heap_caps_calloc(kSampleBufferSize, sizeof(opus_int16),
- MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM)),
- kSampleBufferSize};
+int read_cb(void* instance, unsigned char* ptr, int nbytes) {
+ XiphOpusDecoder* dec = reinterpret_cast<XiphOpusDecoder*>(instance);
+ auto input = dec->ReadCallback();
+ size_t amount_to_read = std::min<size_t>(nbytes, input.size_bytes());
+ std::memcpy(ptr, input.data(), amount_to_read);
+ dec->AfterReadCallback(amount_to_read);
+ return amount_to_read;
}
+
+static const OpusFileCallbacks kCallbacks{
+ .read = read_cb,
+ .seek = NULL,
+ .tell = NULL, // Not seekable
+ .close = NULL,
+};
+
+XiphOpusDecoder::XiphOpusDecoder() : opus_(nullptr) {}
+
XiphOpusDecoder::~XiphOpusDecoder() {
if (opus_ != nullptr) {
- opus_decoder_destroy(opus_);
+ op_free(opus_);
}
- heap_caps_free(sample_buffer_.data());
}
auto XiphOpusDecoder::BeginStream(const cpp::span<const std::byte> input)
-> Result<OutputFormat> {
- if (!ogg_.AddBytes(input)) {
- ESP_LOGI(kTag, "need more input to begin");
- return {input.size(), cpp::fail(Error::kOutOfInput)};
- }
- 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
+ int res;
+ opus_ = op_open_callbacks(
+ this, &kCallbacks, reinterpret_cast<const unsigned char*>(input.data()),
+ input.size(), &res);
+
+ if (res < 0) {
+ std::string err;
+ switch (res) {
+ case OP_EREAD:
+ err = "OP_EREAD";
+ break;
+ default:
+ err = "unknown";
+ }
+ ESP_LOGE(kTag, "error beginning stream: %s", err.c_str());
return {input.size(), cpp::fail(Error::kMalformedData)};
}
- int err;
- opus_ = opus_decoder_create(48000, num_channels, &err);
- if (err != OPUS_OK) {
- return {input.size(), cpp::fail(Error::kInternalError)};
- }
-
return {input.size(), OutputFormat{
- .num_channels = static_cast<uint8_t>(num_channels),
+ .num_channels = 2,
.sample_rate_hz = 48000,
}};
}
@@ -78,68 +80,30 @@ auto XiphOpusDecoder::BeginStream(const cpp::span<const std::byte> input)
auto XiphOpusDecoder::ContinueStream(cpp::span<const std::byte> input,
cpp::span<sample::Sample> output)
-> Result<OutputInfo> {
- size_t bytes_used = 0;
- if (pos_in_buffer_ >= samples_in_buffer_) {
- if (!ogg_.HasPacket()) {
- bytes_used = input.size();
- if (!ogg_.AddBytes(input)) {
- return {bytes_used, cpp::fail(Error::kOutOfInput)};
- }
- }
-
- auto packet = ogg_.Current();
- pos_in_buffer_ = 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) {
- 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)};
- }
+ 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 bytes_written =
+ op_read_stereo(opus_, staging_buffer.data(), staging_buffer.size());
+ if (bytes_written < 0) {
+ ESP_LOGE(kTag, "read failed %i", bytes_written);
+ return {pos_in_input_, cpp::fail(Error::kMalformedData)};
+ } else if (bytes_written == 0) {
+ return {pos_in_input_, cpp::fail(Error::kOutOfInput)};
}
- size_t samples_written = 0;
- while (pos_in_buffer_ < samples_in_buffer_ &&
- samples_written < output.size()) {
- output[samples_written++] =
- sample::FromSigned(sample_buffer_[pos_in_buffer_++], 16);
+ for (int i = 0; i < bytes_written / 2; i++) {
+ output[i] = sample::FromSigned(staging_buffer[i], 16);
}
- return {bytes_used,
+ return {pos_in_input_,
OutputInfo{
- .samples_written = samples_written,
- .is_finished_writing = pos_in_buffer_ >= samples_in_buffer_,
+ .samples_written = static_cast<size_t>(bytes_written / 2),
+ .is_finished_writing = bytes_written == 0,
}};
}
@@ -148,4 +112,12 @@ auto XiphOpusDecoder::SeekStream(cpp::span<const std::byte> input,
return {};
}
+auto XiphOpusDecoder::ReadCallback() -> cpp::span<const std::byte> {
+ return input_.subspan(pos_in_input_);
+}
+
+auto XiphOpusDecoder::AfterReadCallback(size_t bytes_read) -> void {
+ pos_in_input_ += bytes_read;
+}
+
} // namespace codecs