From 1b6811663caf07717ce15f3d3bbb1195397a1a33 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Tue, 8 Aug 2023 23:14:42 +1000 Subject: Add libogg for handling opus streams reasonably --- src/codecs/CMakeLists.txt | 11 ++++++-- src/codecs/include/ogg.hpp | 36 +++++++++++++++++++++++++ src/codecs/include/opus.hpp | 3 +++ src/codecs/ogg.cpp | 0 src/codecs/opus.cpp | 66 ++++++++++++++++++++++++--------------------- 5 files changed, 83 insertions(+), 33 deletions(-) create mode 100644 src/codecs/include/ogg.hpp create mode 100644 src/codecs/ogg.cpp (limited to 'src') diff --git a/src/codecs/CMakeLists.txt b/src/codecs/CMakeLists.txt index f84c46a3..e30dce06 100644 --- a/src/codecs/CMakeLists.txt +++ b/src/codecs/CMakeLists.txt @@ -9,6 +9,15 @@ idf_component_register( target_compile_options("${COMPONENT_LIB}" PRIVATE ${EXTRA_WARNINGS}) +set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) + +set(INSTALL_DOCS OFF) +set(INSTALL_PKG_CONFIG_MODULE OFF) +set(INSTALL_CMAKE_PACKAGE_MODULE OFF) + +add_subdirectory($ENV{PROJ_PATH}/lib/ogg ${CMAKE_CURRENT_BINARY_DIR}/ogg) +target_link_libraries(${COMPONENT_LIB} PUBLIC ogg) + set(OPUS_FIXED_POINT ON) set(OPUS_ENABLE_FLOAT_API OFF) set(OPUS_INSTALL_PKG_CONFIG_MODULE OFF) @@ -16,7 +25,5 @@ set(OPUS_INSTALL_CMAKE_CONFIG_MODULE OFF) set(OPUS_BUILD_TESTING OFF) set(OPUS_BUILD_SHARED_LIBS OFF) -set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) - 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 new file mode 100644 index 00000000..2d6ea8c5 --- /dev/null +++ b/src/codecs/include/ogg.hpp @@ -0,0 +1,36 @@ +/* + * 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) -> void; + auto HasNextPacket() -> bool; + auto NextPacket() -> cpp::span; + auto PeekPacket() -> cpp::span; + + private: + ogg_sync_state sync_; + ogg_stream_state stream_; + ogg_page page_; + ogg_packet packet_; +}; + +} // namespace codecs \ No newline at end of file diff --git a/src/codecs/include/opus.hpp b/src/codecs/include/opus.hpp index f824a7cb..50717b73 100644 --- a/src/codecs/include/opus.hpp +++ b/src/codecs/include/opus.hpp @@ -13,6 +13,8 @@ #include #include +#include "ogg.hpp" +#include "ogg/ogg.h" #include "opus.h" #include "sample.hpp" #include "span.hpp" @@ -44,6 +46,7 @@ class XiphOpusDecoder : public ICodec { -> Result override; private: + OggContainer ogg_; OpusDecoder* opus_; cpp::span sample_buffer_; int32_t pos_in_buffer_; diff --git a/src/codecs/ogg.cpp b/src/codecs/ogg.cpp new file mode 100644 index 00000000..e69de29b diff --git a/src/codecs/opus.cpp b/src/codecs/opus.cpp index 791c8e74..2529d9ec 100644 --- a/src/codecs/opus.cpp +++ b/src/codecs/opus.cpp @@ -18,6 +18,7 @@ #include "codec.hpp" #include "esp_log.h" +#include "ogg/ogg.h" #include "opus.h" #include "opus_types.h" #include "result.hpp" @@ -32,11 +33,7 @@ static constexpr char kTag[] = "opus"; // this function will not be capable of decoding some packets" static constexpr size_t kSampleBufferSize = 5760; -XiphOpusDecoder::XiphOpusDecoder() { - int err; - opus_ = opus_decoder_create(48000, 2, &err); - assert(err == OPUS_OK); - +XiphOpusDecoder::XiphOpusDecoder() : opus_(nullptr) { pos_in_buffer_ = 0; sample_buffer_ = {reinterpret_cast( heap_caps_calloc(kSampleBufferSize, sizeof(opus_int16), @@ -44,23 +41,36 @@ XiphOpusDecoder::XiphOpusDecoder() { kSampleBufferSize}; } XiphOpusDecoder::~XiphOpusDecoder() { - opus_decoder_destroy(opus_); + if (opus_ != nullptr) { + opus_decoder_destroy(opus_); + } heap_caps_free(sample_buffer_.data()); } auto XiphOpusDecoder::BeginStream(const cpp::span input) -> Result { - return {0, OutputFormat{ - .num_channels = 2, - .sample_rate_hz = 48000, - }}; -} + ogg_.AddBytes(input); + if (!ogg_.HasNextPacket()) { + return {input.size(), cpp::fail(Error::kOutOfInput)}; + } + auto packet = ogg_.NextPacket(); + int num_channels = opus_packet_get_nb_channels(packet.data()); + if (num_channels > 2) { + // Too many channels; we can't handle this. + // TODO: better error + return {input.size(), cpp::fail(Error::kMalformedData)}; + } -auto read_uint32(cpp::span src) -> uint32_t { - return static_cast(src[0] << 24) | - static_cast(src[1] << 16) | - static_cast(src[2] << 8) | - static_cast(src[3] << 0); + 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), + .sample_rate_hz = 48000, + }}; } auto XiphOpusDecoder::ContinueStream(cpp::span input, @@ -69,26 +79,20 @@ auto XiphOpusDecoder::ContinueStream(cpp::span input, size_t bytes_used = 0; if (pos_in_buffer_ >= samples_in_buffer_) { ESP_LOGI(kTag, "sample buffer is empty. parsing more."); - if (input.size() < 4) { - return {0, cpp::fail(Error::kOutOfInput)}; + if (!ogg_.HasNextPacket()) { + bytes_used = input.size(); + ogg_.AddBytes(input); } - uint32_t payload_length = read_uint32(input); - ESP_LOGI(kTag, "payload length is %lu", payload_length); - - if (input.size() - 4 < payload_length) { - ESP_LOGI(kTag, "input too small for payload"); - return {0, cpp::fail(Error::kOutOfInput)}; + if (!ogg_.HasNextPacket()) { + return {bytes_used, cpp::fail(Error::kOutOfInput)}; } - // Next 4 bytes are the 'final range'. - // uint32_t enc_final_range = read_uint32(input.subspan(4)); - - bytes_used = payload_length + 8; + auto packet = ogg_.NextPacket(); pos_in_buffer_ = 0; - samples_in_buffer_ = opus_decode( - opus_, reinterpret_cast(input.data() + 8), - payload_length, sample_buffer_.data(), sample_buffer_.size(), 0); + samples_in_buffer_ = + opus_decode(opus_, packet.data(), packet.size_bytes(), + sample_buffer_.data(), sample_buffer_.size(), 0); if (samples_in_buffer_ < 0) { ESP_LOGE(kTag, "error decoding stream"); -- cgit v1.2.3