From bc1f4f1a493ec024ea266c508ebed0a8e4e21e89 Mon Sep 17 00:00:00 2001 From: weil Date: Tue, 16 Jan 2024 16:58:59 +0100 Subject: [PATCH] Add encode/decode opus bindings --- .gitignore | 6 +++ SConstruct | 45 +++++++++++++++++++ src/GodotOpus.cpp | 100 +++++++++++++++++++++++++++++++++++++++++ src/GodotOpus.h | 35 +++++++++++++++ src/register_types.cpp | 52 +++++++++++++++++++++ src/register_types.h | 8 ++++ 6 files changed, 246 insertions(+) create mode 100644 .gitignore create mode 100644 SConstruct create mode 100644 src/GodotOpus.cpp create mode 100644 src/GodotOpus.h create mode 100644 src/register_types.cpp create mode 100644 src/register_types.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a72298f --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# Godot 4+ specific ignores +.godot/ +export +addons/**/~*.dll +.sconsign.dblite +*.obj \ No newline at end of file diff --git a/SConstruct b/SConstruct new file mode 100644 index 0000000..110007a --- /dev/null +++ b/SConstruct @@ -0,0 +1,45 @@ +#!/usr/bin/env python +from glob import glob +from pathlib import Path +from SCons.Script import MSVSProject + +env = SConscript("godot-cpp/SConstruct") + +# Sources +env.Append(CPPPATH=["src/"]) +sources = Glob("src/*.cpp") + +# Opus (Windows x64) +env.Append(CPPPATH=['#3rdparty/opus/include']) +env.Append(LIBPATH=['#3rdparty/opus/lib']) +env.Append(LIBS=['opus']) + +(extension_path,) = glob("export/addons/*/*.gdextension") +addon_path = Path(extension_path).parent +project_name = Path(extension_path).stem +debug_or_release = "release" if env["target"] == "template_release" else "debug" + +if env["platform"] == "macos": + library = env.SharedLibrary( + "{0}/lib/lib{1}.{2}.{3}.framework/{1}.{2}.{3}".format( + addon_path, + project_name, + env["platform"], + debug_or_release, + ), + source=sources, + ) +else: + library = env.SharedLibrary( + "{}/lib/lib{}.{}.{}.{}{}".format( + addon_path, + project_name, + env["platform"], + debug_or_release, + env["arch"], + env["SHLIBSUFFIX"], + ), + source=sources, + ) + +Default(library) diff --git a/src/GodotOpus.cpp b/src/GodotOpus.cpp new file mode 100644 index 0000000..67ec746 --- /dev/null +++ b/src/GodotOpus.cpp @@ -0,0 +1,100 @@ +#include "GodotOpus.h" + +#include +#include + +namespace godot { + +Opus *Opus::singleton = nullptr; +constexpr auto sampleFrames = 480; + +void Opus::_bind_methods() +{ + ClassDB::bind_method(D_METHOD("encode"), &Opus::encode); + ClassDB::bind_method(D_METHOD("decode"), &Opus::decode); +} + +Opus *Opus::get_singleton() +{ + return singleton; +} + +Opus::Opus() +{ + ERR_FAIL_COND(singleton != nullptr); + singleton = this; + + int err{}; + + m_encoder = opus_encoder_create(48000, 1, OPUS_APPLICATION_VOIP, &err); + ERR_FAIL_COND(err != OPUS_OK); + ERR_FAIL_COND(m_encoder == nullptr); + + m_decoder = opus_decoder_create(48000, 1, &err); + ERR_FAIL_COND(err != OPUS_OK); + ERR_FAIL_COND(m_decoder == nullptr); + + err = opus_encoder_ctl(m_encoder, OPUS_SET_BANDWIDTH(OPUS_BANDWIDTH_SUPERWIDEBAND)); + ERR_FAIL_COND(err < 0); + + err = opus_encoder_ctl(m_encoder, OPUS_SET_BITRATE(24000)); + ERR_FAIL_COND(err < 0); +} + +Opus::~Opus() +{ + ERR_FAIL_COND(singleton != this); + opus_encoder_destroy(m_encoder); + opus_decoder_destroy(m_decoder); + singleton = nullptr; +} + +PackedFloat32Array Opus::encode(PackedVector2Array input) +{ + if (input.size() < sampleFrames) { + return {}; + } + + std::vector data(sampleFrames); + for (size_t i = 0; i < sampleFrames; i++) { + data[i] = input[i].x; + } + + std::vector output(sampleFrames * 2); + const auto r = opus_encode_float(m_encoder, data.data(), sampleFrames, output.data(), output.size()); + if (r == -1) { + return {}; + } + + auto outputArray = PackedFloat32Array{}; + outputArray.resize(r); + for (size_t i = 0; i < r; i++) { + outputArray[i] = output[i]; + } + + return outputArray; +} + +PackedVector2Array Opus::decode(PackedFloat32Array input) +{ + std::vector inputData(sampleFrames*2); + for (size_t i = 0; i < input.size(); i++) { + inputData[i] = input[i]; + } + + std::vector output(sampleFrames*2); + const auto r = opus_decode_float(m_decoder, inputData.data(), input.size(), output.data(), sampleFrames, 0); + if (r != sampleFrames) { + return {}; + } + + auto packedOutput = PackedVector2Array{}; + packedOutput.resize(r); + for (size_t i = 0; i < r; i++) { + packedOutput[i] = Vector2{output[i], output[i]}; + } + + return packedOutput; +} + +} diff --git a/src/GodotOpus.h b/src/GodotOpus.h new file mode 100644 index 0000000..893b709 --- /dev/null +++ b/src/GodotOpus.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include + +#include "opus.h" + +using namespace godot; + +namespace godot { + +class Opus : public Object +{ + GDCLASS(Opus, Object); + static Opus *singleton; + +protected: + static void _bind_methods(); + +public: + static Opus *get_singleton(); + + Opus(); + ~Opus(); + + PackedFloat32Array encode(PackedVector2Array input); + PackedVector2Array decode(PackedFloat32Array input); +private: + OpusEncoder* m_encoder; + OpusDecoder* m_decoder; + +}; + +} diff --git a/src/register_types.cpp b/src/register_types.cpp new file mode 100644 index 0000000..fbed701 --- /dev/null +++ b/src/register_types.cpp @@ -0,0 +1,52 @@ +#include "register_types.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "GodotOpus.h" + +using namespace godot; + +static Opus* _godot_opus_singleton; + +void gdextension_initialize(ModuleInitializationLevel p_level) +{ + if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) + { + ClassDB::register_class(); + + _godot_opus_singleton = memnew(Opus); + Engine::get_singleton()->register_singleton("Opus", Opus::get_singleton()); + } +} + +void gdextension_terminate(ModuleInitializationLevel p_level) +{ + if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) + { + Engine::get_singleton()->unregister_singleton("Opus"); + memdelete(_godot_opus_singleton); + } +} + +extern "C" +{ + GDExtensionBool GDE_EXPORT gdextension_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) + { + godot::GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization); + + init_obj.register_initializer(gdextension_initialize); + init_obj.register_terminator(gdextension_terminate); + init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE); + + return init_obj.init(); + } +} diff --git a/src/register_types.h b/src/register_types.h new file mode 100644 index 0000000..e5586bb --- /dev/null +++ b/src/register_types.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +using namespace godot; + +void gdextension_initialize(ModuleInitializationLevel p_level); +void gdextension_terminate(ModuleInitializationLevel p_level);