From 67caeb6e3cda44205ba8fe783274b20dc7ea216e Mon Sep 17 00:00:00 2001 From: jacqueline Date: Wed, 9 Aug 2023 12:00:02 +1000 Subject: Use opusfile instead of working directly with ogg and opus --- src/codecs/CMakeLists.txt | 16 +---- src/codecs/include/ogg.hpp | 43 ------------ src/codecs/include/opus.hpp | 16 ++--- src/codecs/include/vorbis.hpp | 1 - src/codecs/ogg.cpp | 109 ------------------------------ src/codecs/opus.cpp | 152 +++++++++++++++++------------------------- 6 files changed, 71 insertions(+), 266 deletions(-) delete mode 100644 src/codecs/include/ogg.hpp delete mode 100644 src/codecs/ogg.cpp (limited to 'src') 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 - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include -#include -#include -#include - -#include "ogg/ogg.h" -#include "span.hpp" - -namespace codecs { - -class OggContainer { - public: - OggContainer(); - ~OggContainer(); - - auto AddBytes(cpp::span) -> bool; - - auto Next() -> bool; - auto Current() -> cpp::span; - 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 #include -#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 input, std::size_t target_sample) -> Result override; - private: - OggContainer ogg_; - OpusDecoder* opus_; - cpp::span sample_buffer_; - int32_t pos_in_buffer_; - int32_t samples_in_buffer_; + auto ReadCallback() -> cpp::span; + auto AfterReadCallback(size_t bytes_read) -> void; + private: + OggOpusFile* opus_; + cpp::span 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 #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 - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#include "ogg.hpp" -#include - -#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 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 { - if (!has_packet_) { - return {}; - } - ESP_LOGI(kTag, "getting packet, location %p size %li", packet_.packet, - packet_.bytes); - return {packet_.packet, static_cast(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( - 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(instance); + auto input = dec->ReadCallback(); + size_t amount_to_read = std::min(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 input) -> Result { - 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(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(num_channels), + .num_channels = 2, .sample_rate_hz = 48000, }}; } @@ -78,68 +80,30 @@ auto XiphOpusDecoder::BeginStream(const cpp::span input) auto XiphOpusDecoder::ContinueStream(cpp::span input, cpp::span output) -> Result { - 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 staging_buffer{ + reinterpret_cast(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(bytes_written / 2), + .is_finished_writing = bytes_written == 0, }}; } @@ -148,4 +112,12 @@ auto XiphOpusDecoder::SeekStream(cpp::span input, return {}; } +auto XiphOpusDecoder::ReadCallback() -> cpp::span { + return input_.subspan(pos_in_input_); +} + +auto XiphOpusDecoder::AfterReadCallback(size_t bytes_read) -> void { + pos_in_input_ += bytes_read; +} + } // namespace codecs -- cgit v1.2.3