summaryrefslogtreecommitdiff
path: root/modules/vorbis
diff options
context:
space:
mode:
authorEllen Poe <ellenhp@google.com>2021-09-09 18:54:18 -0700
committerEllen Poe <ellenhp@google.com>2021-09-09 19:39:04 -0700
commitf5d9c7b487166562a833fc86363d78468d711070 (patch)
tree3893f6fa0efdf3d657726074a8a1486a6f890742 /modules/vorbis
parent0d5e13cd805a1aa69c5f395483051d3501bcfcd3 (diff)
Replace stb_vorbis with libogg+libvorbis
Diffstat (limited to 'modules/vorbis')
-rw-r--r--modules/vorbis/SCsub3
-rw-r--r--modules/vorbis/audio_stream_ogg_vorbis.cpp435
-rw-r--r--modules/vorbis/audio_stream_ogg_vorbis.h134
-rw-r--r--modules/vorbis/config.py11
-rw-r--r--modules/vorbis/doc_classes/AudioStreamOGGVorbis.xml24
-rw-r--r--modules/vorbis/doc_classes/AudioStreamPlaybackOGGVorbis.xml13
-rw-r--r--modules/vorbis/register_types.cpp15
-rw-r--r--modules/vorbis/resource_importer_ogg_vorbis.cpp190
-rw-r--r--modules/vorbis/resource_importer_ogg_vorbis.h62
9 files changed, 882 insertions, 5 deletions
diff --git a/modules/vorbis/SCsub b/modules/vorbis/SCsub
index bc31fff066..322314487f 100644
--- a/modules/vorbis/SCsub
+++ b/modules/vorbis/SCsub
@@ -3,9 +3,6 @@
Import("env")
Import("env_modules")
-# Only kept to build the thirdparty library used by the theora and webm
-# modules. We now use stb_vorbis for AudioStreamOGGVorbis.
-
env_vorbis = env_modules.Clone()
# Thirdparty source files
diff --git a/modules/vorbis/audio_stream_ogg_vorbis.cpp b/modules/vorbis/audio_stream_ogg_vorbis.cpp
new file mode 100644
index 0000000000..e4a80c339f
--- /dev/null
+++ b/modules/vorbis/audio_stream_ogg_vorbis.cpp
@@ -0,0 +1,435 @@
+/*************************************************************************/
+/* audio_stream_ogg_vorbis.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "audio_stream_ogg_vorbis.h"
+
+#include "core/io/file_access.h"
+#include "core/variant/typed_array.h"
+#include "thirdparty/libogg/ogg/ogg.h"
+
+int AudioStreamPlaybackOGGVorbis::_mix_internal(AudioFrame *p_buffer, int p_frames) {
+ ERR_FAIL_COND_V(!ready, 0);
+ ERR_FAIL_COND_V(!active, 0);
+
+ int todo = p_frames;
+
+ int start_buffer = 0;
+
+ int frames_mixed_this_step = p_frames;
+
+ while (todo && active) {
+ AudioFrame *buffer = p_buffer;
+ if (start_buffer > 0) {
+ buffer = buffer + start_buffer;
+ }
+ int mixed = _mix_frames_vorbis(buffer, todo);
+ if (mixed < 0) {
+ return 0;
+ }
+ todo -= mixed;
+ frames_mixed += mixed;
+ start_buffer += mixed;
+ if (!have_packets_left) {
+ //end of file!
+ bool is_not_empty = mixed > 0 || vorbis_stream->get_length() > 0;
+ if (vorbis_stream->loop && is_not_empty) {
+ //loop
+
+ seek(vorbis_stream->loop_offset);
+ loops++;
+ // we still have buffer to fill, start from this element in the next iteration.
+ start_buffer = p_frames - todo;
+ } else {
+ frames_mixed_this_step = p_frames - todo;
+ for (int i = p_frames - todo; i < p_frames; i++) {
+ p_buffer[i] = AudioFrame(0, 0);
+ }
+ active = false;
+ todo = 0;
+ }
+ }
+ }
+ return frames_mixed_this_step;
+}
+
+int AudioStreamPlaybackOGGVorbis::_mix_frames_vorbis(AudioFrame *p_buffer, int p_frames) {
+ ERR_FAIL_COND_V(!ready, 0);
+ if (!have_samples_left) {
+ ogg_packet *packet = nullptr;
+ int err;
+
+ if (!vorbis_data_playback->next_ogg_packet(&packet)) {
+ have_packets_left = false;
+ WARN_PRINT("ran out of packets in stream");
+ return -1;
+ }
+
+ ERR_FAIL_COND_V_MSG((err = vorbis_synthesis(&block, packet)), 0, "Error during vorbis synthesis " + itos(err));
+ ERR_FAIL_COND_V_MSG((err = vorbis_synthesis_blockin(&dsp_state, &block)), 0, "Error during vorbis block processing " + itos(err));
+
+ have_packets_left = !packet->e_o_s;
+ }
+
+ float **pcm; // Accessed with pcm[channel_idx][sample_idx].
+
+ int frames = vorbis_synthesis_pcmout(&dsp_state, &pcm);
+ if (frames > p_frames) {
+ frames = p_frames;
+ have_samples_left = true;
+ } else {
+ have_samples_left = false;
+ }
+
+ if (info.channels > 1) {
+ for (int frame = 0; frame < frames; frame++) {
+ p_buffer[frame].l = pcm[0][frame];
+ p_buffer[frame].r = pcm[0][frame];
+ }
+ } else {
+ for (int frame = 0; frame < frames; frame++) {
+ p_buffer[frame].l = pcm[0][frame];
+ p_buffer[frame].r = pcm[0][frame];
+ }
+ }
+ vorbis_synthesis_read(&dsp_state, frames);
+ return frames;
+}
+
+float AudioStreamPlaybackOGGVorbis::get_stream_sampling_rate() {
+ return vorbis_data->get_sampling_rate();
+}
+
+bool AudioStreamPlaybackOGGVorbis::_alloc_vorbis() {
+ vorbis_info_init(&info);
+ info_is_allocated = true;
+ vorbis_comment_init(&comment);
+ comment_is_allocated = true;
+
+ ERR_FAIL_COND_V(vorbis_data.is_null(), false);
+ vorbis_data_playback = vorbis_data->instance_playback();
+
+ ogg_packet *packet;
+ int err;
+
+ for (int i = 0; i < 3; i++) {
+ if (!vorbis_data_playback->next_ogg_packet(&packet)) {
+ WARN_PRINT("Not enough packets to parse header");
+ return false;
+ }
+
+ err = vorbis_synthesis_headerin(&info, &comment, packet);
+ ERR_FAIL_COND_V_MSG(err != 0, false, "Error parsing header");
+ }
+
+ err = vorbis_synthesis_init(&dsp_state, &info);
+ ERR_FAIL_COND_V_MSG(err != 0, false, "Error initializing dsp state");
+ dsp_state_is_allocated = true;
+
+ err = vorbis_block_init(&dsp_state, &block);
+ ERR_FAIL_COND_V_MSG(err != 0, false, "Error initializing block");
+ block_is_allocated = true;
+
+ ready = true;
+
+ return true;
+}
+
+void AudioStreamPlaybackOGGVorbis::start(float p_from_pos) {
+ ERR_FAIL_COND(!ready);
+ active = true;
+ seek(p_from_pos);
+ loops = 0;
+ _begin_resample();
+}
+
+void AudioStreamPlaybackOGGVorbis::stop() {
+ active = false;
+}
+
+bool AudioStreamPlaybackOGGVorbis::is_playing() const {
+ return active;
+}
+
+int AudioStreamPlaybackOGGVorbis::get_loop_count() const {
+ return loops;
+}
+
+float AudioStreamPlaybackOGGVorbis::get_playback_position() const {
+ return float(frames_mixed) / vorbis_data->get_sampling_rate();
+}
+
+void AudioStreamPlaybackOGGVorbis::seek(float p_time) {
+ ERR_FAIL_COND(!ready);
+ ERR_FAIL_COND(vorbis_stream.is_null());
+ if (!active) {
+ return;
+ }
+
+ vorbis_synthesis_restart(&dsp_state);
+
+ if (p_time >= vorbis_stream->get_length()) {
+ p_time = 0;
+ }
+ frames_mixed = uint32_t(vorbis_data->get_sampling_rate() * p_time);
+
+ const int64_t desired_sample = p_time * get_stream_sampling_rate();
+
+ if (!vorbis_data_playback->seek_page(desired_sample)) {
+ WARN_PRINT("seek failed");
+ return;
+ }
+
+ ogg_packet *packet;
+ if (!vorbis_data_playback->next_ogg_packet(&packet)) {
+ WARN_PRINT_ONCE("seeking beyond limits");
+ return;
+ }
+
+ // The granule position of the page we're seeking through.
+ int64_t granule_pos = 0;
+
+ int headers_remaining = 0;
+ int samples_in_page = 0;
+ int err;
+ while (true) {
+ if (vorbis_synthesis_idheader(packet)) {
+ headers_remaining = 3;
+ }
+ if (!headers_remaining) {
+ ERR_FAIL_COND_MSG((err = vorbis_synthesis(&block, packet)), "Error during vorbis synthesis " + itos(err));
+ ERR_FAIL_COND_MSG((err = vorbis_synthesis_blockin(&dsp_state, &block)), "Error during vorbis block processing " + itos(err));
+
+ int samples_out = vorbis_synthesis_pcmout(&dsp_state, nullptr);
+ ERR_FAIL_COND_MSG((err = vorbis_synthesis_read(&dsp_state, samples_out)), "Error during vorbis read updating " + itos(err));
+
+ samples_in_page += samples_out;
+
+ } else {
+ headers_remaining--;
+ }
+ if (packet->granulepos != -1 && headers_remaining == 0) {
+ // This indicates the end of the page.
+ granule_pos = packet->granulepos;
+ break;
+ }
+ if (packet->e_o_s) {
+ break;
+ }
+ if (!vorbis_data_playback->next_ogg_packet(&packet)) {
+ // We should get an e_o_s flag before this happens.
+ WARN_PRINT("Vorbis file ended without warning.");
+ break;
+ }
+ }
+
+ int64_t samples_to_burn = samples_in_page - (granule_pos - desired_sample);
+
+ if (samples_to_burn > samples_in_page) {
+ WARN_PRINT("Burning more samples than we have in this page. Check seek algorithm.");
+ } else if (samples_to_burn < 0) {
+ WARN_PRINT("Burning negative samples doesn't make sense. Check seek algorithm.");
+ }
+
+ // Seek again, this time we'll burn a specific number of samples instead of all of them.
+ if (!vorbis_data_playback->seek_page(desired_sample)) {
+ WARN_PRINT("seek failed");
+ return;
+ }
+
+ if (!vorbis_data_playback->next_ogg_packet(&packet)) {
+ WARN_PRINT_ONCE("seeking beyond limits");
+ return;
+ }
+ vorbis_synthesis_restart(&dsp_state);
+
+ while (true) {
+ if (vorbis_synthesis_idheader(packet)) {
+ headers_remaining = 3;
+ }
+ if (!headers_remaining) {
+ ERR_FAIL_COND_MSG((err = vorbis_synthesis(&block, packet)), "Error during vorbis synthesis " + itos(err));
+ ERR_FAIL_COND_MSG((err = vorbis_synthesis_blockin(&dsp_state, &block)), "Error during vorbis block processing " + itos(err));
+
+ int samples_out = vorbis_synthesis_pcmout(&dsp_state, nullptr);
+ int read_samples = samples_to_burn > samples_out ? samples_out : samples_to_burn;
+ ERR_FAIL_COND_MSG((err = vorbis_synthesis_read(&dsp_state, samples_out)), "Error during vorbis read updating " + itos(err));
+ samples_to_burn -= read_samples;
+
+ if (samples_to_burn <= 0) {
+ break;
+ }
+ } else {
+ headers_remaining--;
+ }
+ if (packet->granulepos != -1 && headers_remaining == 0) {
+ // This indicates the end of the page.
+ break;
+ }
+ if (packet->e_o_s) {
+ break;
+ }
+ if (!vorbis_data_playback->next_ogg_packet(&packet)) {
+ // We should get an e_o_s flag before this happens.
+ WARN_PRINT("Vorbis file ended without warning.");
+ break;
+ }
+ }
+}
+
+AudioStreamPlaybackOGGVorbis::~AudioStreamPlaybackOGGVorbis() {
+ if (block_is_allocated) {
+ vorbis_block_clear(&block);
+ }
+ if (dsp_state_is_allocated) {
+ vorbis_dsp_clear(&dsp_state);
+ }
+ if (comment_is_allocated) {
+ vorbis_comment_clear(&comment);
+ }
+ if (info_is_allocated) {
+ vorbis_info_clear(&info);
+ }
+}
+
+Ref<AudioStreamPlayback> AudioStreamOGGVorbis::instance_playback() {
+ Ref<AudioStreamPlaybackOGGVorbis> ovs;
+
+ ERR_FAIL_COND_V(packet_sequence.is_null(), nullptr);
+
+ ovs.instantiate();
+ ovs->vorbis_stream = Ref<AudioStreamOGGVorbis>(this);
+ ovs->vorbis_data = packet_sequence;
+ ovs->frames_mixed = 0;
+ ovs->active = false;
+ ovs->loops = 0;
+ if (ovs->_alloc_vorbis()) {
+ return ovs;
+ }
+ // Failed to allocate data structures.
+ return nullptr;
+}
+
+String AudioStreamOGGVorbis::get_stream_name() const {
+ return ""; //return stream_name;
+}
+
+void AudioStreamOGGVorbis::maybe_update_info() {
+ ERR_FAIL_COND(packet_sequence.is_null());
+
+ vorbis_info info;
+ vorbis_comment comment;
+ int err;
+
+ vorbis_info_init(&info);
+ vorbis_comment_init(&comment);
+
+ int packet_count = 0;
+ Ref<OGGPacketSequencePlayback> packet_sequence_playback = packet_sequence->instance_playback();
+
+ for (int i = 0; i < 3; i++) {
+ ogg_packet *packet;
+ if (!packet_sequence_playback->next_ogg_packet(&packet)) {
+ WARN_PRINT("Failed to get header packet");
+ break;
+ }
+ if (i == 0) {
+ packet->b_o_s = 1;
+ }
+
+ if (i == 0) {
+ ERR_FAIL_COND(!vorbis_synthesis_idheader(packet));
+ }
+
+ err = vorbis_synthesis_headerin(&info, &comment, packet);
+ ERR_FAIL_COND_MSG(err != 0, "Error parsing header packet " + itos(i) + ": " + itos(err));
+
+ packet_count++;
+ }
+
+ packet_sequence->set_sampling_rate(info.rate);
+
+ vorbis_comment_clear(&comment);
+ vorbis_info_clear(&info);
+}
+
+void AudioStreamOGGVorbis::set_packet_sequence(Ref<OGGPacketSequence> p_packet_sequence) {
+ packet_sequence = p_packet_sequence;
+ if (packet_sequence.is_valid()) {
+ maybe_update_info();
+ }
+}
+
+Ref<OGGPacketSequence> AudioStreamOGGVorbis::get_packet_sequence() const {
+ return packet_sequence;
+}
+
+void AudioStreamOGGVorbis::set_loop(bool p_enable) {
+ loop = p_enable;
+}
+
+bool AudioStreamOGGVorbis::has_loop() const {
+ return loop;
+}
+
+void AudioStreamOGGVorbis::set_loop_offset(float p_seconds) {
+ loop_offset = p_seconds;
+}
+
+float AudioStreamOGGVorbis::get_loop_offset() const {
+ return loop_offset;
+}
+
+float AudioStreamOGGVorbis::get_length() const {
+ ERR_FAIL_COND_V(packet_sequence.is_null(), 0);
+ return packet_sequence->get_length();
+}
+
+bool AudioStreamOGGVorbis::is_monophonic() const {
+ return false;
+}
+
+void AudioStreamOGGVorbis::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_packet_sequence", "packet_sequence"), &AudioStreamOGGVorbis::set_packet_sequence);
+ ClassDB::bind_method(D_METHOD("get_packet_sequence"), &AudioStreamOGGVorbis::get_packet_sequence);
+
+ ClassDB::bind_method(D_METHOD("set_loop", "enable"), &AudioStreamOGGVorbis::set_loop);
+ ClassDB::bind_method(D_METHOD("has_loop"), &AudioStreamOGGVorbis::has_loop);
+
+ ClassDB::bind_method(D_METHOD("set_loop_offset", "seconds"), &AudioStreamOGGVorbis::set_loop_offset);
+ ClassDB::bind_method(D_METHOD("get_loop_offset"), &AudioStreamOGGVorbis::get_loop_offset);
+
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "packet_sequence", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_packet_sequence", "get_packet_sequence");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "loop"), "set_loop", "has_loop");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "loop_offset"), "set_loop_offset", "get_loop_offset");
+}
+
+AudioStreamOGGVorbis::AudioStreamOGGVorbis() {}
+
+AudioStreamOGGVorbis::~AudioStreamOGGVorbis() {}
diff --git a/modules/vorbis/audio_stream_ogg_vorbis.h b/modules/vorbis/audio_stream_ogg_vorbis.h
new file mode 100644
index 0000000000..59a1318a6b
--- /dev/null
+++ b/modules/vorbis/audio_stream_ogg_vorbis.h
@@ -0,0 +1,134 @@
+/*************************************************************************/
+/* audio_stream_ogg_vorbis.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef AUDIO_STREAM_LIBVORBIS_H
+#define AUDIO_STREAM_LIBVORBIS_H
+
+#include "core/variant/variant.h"
+#include "modules/ogg/ogg_packet_sequence.h"
+#include "servers/audio/audio_stream.h"
+#include "thirdparty/libvorbis/vorbis/codec.h"
+
+class AudioStreamOGGVorbis;
+
+class AudioStreamPlaybackOGGVorbis : public AudioStreamPlaybackResampled {
+ GDCLASS(AudioStreamPlaybackOGGVorbis, AudioStreamPlaybackResampled);
+
+ uint32_t frames_mixed = 0;
+ bool active = false;
+ int loops = 0;
+
+ vorbis_info info;
+ vorbis_comment comment;
+ vorbis_dsp_state dsp_state;
+ vorbis_block block;
+
+ bool info_is_allocated = false;
+ bool comment_is_allocated = false;
+ bool dsp_state_is_allocated = false;
+ bool block_is_allocated = false;
+
+ bool ready = false;
+
+ bool have_samples_left = false;
+ bool have_packets_left = false;
+
+ friend class AudioStreamOGGVorbis;
+
+ Ref<OGGPacketSequence> vorbis_data;
+ Ref<OGGPacketSequencePlayback> vorbis_data_playback;
+ Ref<AudioStreamOGGVorbis> vorbis_stream;
+
+ int _mix_frames_vorbis(AudioFrame *p_buffer, int p_frames);
+
+ // Allocates vorbis data structures. Returns true upon success, false on failure.
+ bool _alloc_vorbis();
+
+protected:
+ virtual int _mix_internal(AudioFrame *p_buffer, int p_frames) override;
+ virtual float get_stream_sampling_rate() override;
+
+public:
+ virtual void start(float p_from_pos = 0.0) override;
+ virtual void stop() override;
+ virtual bool is_playing() const override;
+
+ virtual int get_loop_count() const override; //times it looped
+
+ virtual float get_playback_position() const override;
+ virtual void seek(float p_time) override;
+
+ AudioStreamPlaybackOGGVorbis() {}
+ ~AudioStreamPlaybackOGGVorbis();
+};
+
+class AudioStreamOGGVorbis : public AudioStream {
+ GDCLASS(AudioStreamOGGVorbis, AudioStream);
+ OBJ_SAVE_TYPE(AudioStream); // Saves derived classes with common type so they can be interchanged.
+ RES_BASE_EXTENSION("oggvorbisstr");
+
+ friend class AudioStreamPlaybackOGGVorbis;
+
+ int channels = 1;
+ float length = 0.0;
+ bool loop = false;
+ float loop_offset = 0.0;
+
+ // Performs a seek to the beginning of the stream, should not be called during playback!
+ // Also causes allocation and deallocation.
+ void maybe_update_info();
+
+ Ref<OGGPacketSequence> packet_sequence;
+
+protected:
+ static void _bind_methods();
+
+public:
+ void set_loop(bool p_enable);
+ bool has_loop() const;
+
+ void set_loop_offset(float p_seconds);
+ float get_loop_offset() const;
+
+ virtual Ref<AudioStreamPlayback> instance_playback() override;
+ virtual String get_stream_name() const override;
+
+ void set_packet_sequence(Ref<OGGPacketSequence> p_packet_sequence);
+ Ref<OGGPacketSequence> get_packet_sequence() const;
+
+ virtual float get_length() const override; //if supported, otherwise return 0
+
+ virtual bool is_monophonic() const override;
+
+ AudioStreamOGGVorbis();
+ virtual ~AudioStreamOGGVorbis();
+};
+
+#endif // AUDIO_STREAM_LIBVORBIS_H
diff --git a/modules/vorbis/config.py b/modules/vorbis/config.py
index 8a384e3066..978eccb29f 100644
--- a/modules/vorbis/config.py
+++ b/modules/vorbis/config.py
@@ -4,3 +4,14 @@ def can_build(env, platform):
def configure(env):
pass
+
+
+def get_doc_classes():
+ return [
+ "AudioStreamOGGVorbis",
+ "AudioStreamPlaybackOGGVorbis",
+ ]
+
+
+def get_doc_path():
+ return "doc_classes"
diff --git a/modules/vorbis/doc_classes/AudioStreamOGGVorbis.xml b/modules/vorbis/doc_classes/AudioStreamOGGVorbis.xml
new file mode 100644
index 0000000000..a680a2f999
--- /dev/null
+++ b/modules/vorbis/doc_classes/AudioStreamOGGVorbis.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="AudioStreamOGGVorbis" inherits="AudioStream" version="4.0">
+ <brief_description>
+ </brief_description>
+ <description>
+ </description>
+ <tutorials>
+ </tutorials>
+ <methods>
+ </methods>
+ <members>
+ <member name="loop" type="bool" setter="set_loop" getter="has_loop" default="false">
+ If [code]true[/code], the stream will automatically loop when it reaches the end.
+ </member>
+ <member name="loop_offset" type="float" setter="set_loop_offset" getter="get_loop_offset" default="0.0">
+ Time in seconds at which the stream starts after being looped.
+ </member>
+ <member name="packet_sequence" type="OGGPacketSequence" setter="set_packet_sequence" getter="get_packet_sequence">
+ Contains the raw OGG data for this stream.
+ </member>
+ </members>
+ <constants>
+ </constants>
+</class>
diff --git a/modules/vorbis/doc_classes/AudioStreamPlaybackOGGVorbis.xml b/modules/vorbis/doc_classes/AudioStreamPlaybackOGGVorbis.xml
new file mode 100644
index 0000000000..3120f2a9e6
--- /dev/null
+++ b/modules/vorbis/doc_classes/AudioStreamPlaybackOGGVorbis.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="AudioStreamPlaybackOGGVorbis" inherits="AudioStreamPlaybackResampled" version="4.0">
+ <brief_description>
+ </brief_description>
+ <description>
+ </description>
+ <tutorials>
+ </tutorials>
+ <methods>
+ </methods>
+ <constants>
+ </constants>
+</class>
diff --git a/modules/vorbis/register_types.cpp b/modules/vorbis/register_types.cpp
index d3e77ea629..de3f41afdd 100644
--- a/modules/vorbis/register_types.cpp
+++ b/modules/vorbis/register_types.cpp
@@ -30,8 +30,19 @@
#include "register_types.h"
-// Dummy module as libvorbis is needed by other modules (theora ...)
+#include "audio_stream_ogg_vorbis.h"
+#include "resource_importer_ogg_vorbis.h"
-void register_vorbis_types() {}
+void register_vorbis_types() {
+#ifdef TOOLS_ENABLED
+ if (Engine::get_singleton()->is_editor_hint()) {
+ Ref<ResourceImporterOGGVorbis> ogg_vorbis_importer;
+ ogg_vorbis_importer.instantiate();
+ ResourceFormatImporter::get_singleton()->add_importer(ogg_vorbis_importer);
+ }
+#endif
+ GDREGISTER_CLASS(AudioStreamOGGVorbis);
+ GDREGISTER_CLASS(AudioStreamPlaybackOGGVorbis);
+}
void unregister_vorbis_types() {}
diff --git a/modules/vorbis/resource_importer_ogg_vorbis.cpp b/modules/vorbis/resource_importer_ogg_vorbis.cpp
new file mode 100644
index 0000000000..33ee6cf359
--- /dev/null
+++ b/modules/vorbis/resource_importer_ogg_vorbis.cpp
@@ -0,0 +1,190 @@
+/*************************************************************************/
+/* resource_importer_ogg_vorbis.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "resource_importer_ogg_vorbis.h"
+
+#include "audio_stream_ogg_vorbis.h"
+#include "core/io/file_access.h"
+#include "core/io/resource_saver.h"
+#include "scene/resources/texture.h"
+#include "thirdparty/libogg/ogg/ogg.h"
+#include "thirdparty/libvorbis/vorbis/codec.h"
+
+String ResourceImporterOGGVorbis::get_importer_name() const {
+ return "oggvorbisstr";
+}
+
+String ResourceImporterOGGVorbis::get_visible_name() const {
+ return "oggvorbisstr";
+}
+
+void ResourceImporterOGGVorbis::get_recognized_extensions(List<String> *p_extensions) const {
+ p_extensions->push_back("ogg");
+}
+
+String ResourceImporterOGGVorbis::get_save_extension() const {
+ return "oggvorbisstr";
+}
+
+String ResourceImporterOGGVorbis::get_resource_type() const {
+ return "AudioStreamOGGVorbis";
+}
+
+bool ResourceImporterOGGVorbis::get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const {
+ return true;
+}
+
+int ResourceImporterOGGVorbis::get_preset_count() const {
+ return 0;
+}
+
+String ResourceImporterOGGVorbis::get_preset_name(int p_idx) const {
+ return String();
+}
+
+void ResourceImporterOGGVorbis::get_import_options(List<ImportOption> *r_options, int p_preset) const {
+ r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "loop"), true));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "loop_offset"), 0));
+}
+
+Error ResourceImporterOGGVorbis::import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) {
+ bool loop = p_options["loop"];
+ float loop_offset = p_options["loop_offset"];
+
+ FileAccess *f = FileAccess::open(p_source_file, FileAccess::READ);
+
+ ERR_FAIL_COND_V_MSG(!f, ERR_CANT_OPEN, "Cannot open file '" + p_source_file + "'.");
+
+ uint64_t len = f->get_length();
+
+ Vector<uint8_t> file_data;
+ file_data.resize(len);
+ uint8_t *w = file_data.ptrw();
+
+ f->get_buffer(w, len);
+
+ memdelete(f);
+
+ Ref<AudioStreamOGGVorbis> ogg_vorbis_stream;
+ ogg_vorbis_stream.instantiate();
+
+ Ref<OGGPacketSequence> ogg_packet_sequence;
+ ogg_packet_sequence.instantiate();
+
+ ogg_stream_state stream_state;
+ ogg_sync_state sync_state;
+ ogg_page page;
+ ogg_packet packet;
+ bool initialized_stream = false;
+
+ ogg_sync_init(&sync_state);
+ int err;
+ size_t cursor = 0;
+ size_t packet_count = 0;
+ bool done = false;
+ while (!done) {
+ ERR_FAIL_COND_V_MSG((err = ogg_sync_check(&sync_state)), Error::ERR_INVALID_DATA, "Ogg sync error " + itos(err));
+ while (ogg_sync_pageout(&sync_state, &page) != 1) {
+ if (cursor >= len) {
+ done = true;
+ break;
+ }
+ ERR_FAIL_COND_V_MSG((err = ogg_sync_check(&sync_state)), Error::ERR_INVALID_DATA, "Ogg sync error " + itos(err));
+ char *sync_buf = ogg_sync_buffer(&sync_state, OGG_SYNC_BUFFER_SIZE);
+ ERR_FAIL_COND_V_MSG((err = ogg_sync_check(&sync_state)), Error::ERR_INVALID_DATA, "Ogg sync error " + itos(err));
+ ERR_FAIL_COND_V(cursor > len, Error::ERR_INVALID_DATA);
+ size_t copy_size = len - cursor;
+ if (copy_size > OGG_SYNC_BUFFER_SIZE) {
+ copy_size = OGG_SYNC_BUFFER_SIZE;
+ }
+ memcpy(sync_buf, &file_data[cursor], copy_size);
+ ogg_sync_wrote(&sync_state, copy_size);
+ cursor += copy_size;
+ ERR_FAIL_COND_V_MSG((err = ogg_sync_check(&sync_state)), Error::ERR_INVALID_DATA, "Ogg sync error " + itos(err));
+ }
+ if (done) {
+ break;
+ }
+ ERR_FAIL_COND_V_MSG((err = ogg_sync_check(&sync_state)), Error::ERR_INVALID_DATA, "Ogg sync error " + itos(err));
+
+ // Have a page now.
+ if (!initialized_stream) {
+ ogg_stream_init(&stream_state, ogg_page_serialno(&page));
+ ERR_FAIL_COND_V_MSG((err = ogg_stream_check(&stream_state)), Error::ERR_INVALID_DATA, "Ogg stream error " + itos(err));
+ initialized_stream = true;
+ }
+ ERR_FAIL_COND_V_MSG((err = ogg_stream_check(&stream_state)), Error::ERR_INVALID_DATA, "Ogg stream error " + itos(err));
+ ogg_stream_pagein(&stream_state, &page);
+ ERR_FAIL_COND_V_MSG((err = ogg_stream_check(&stream_state)), Error::ERR_INVALID_DATA, "Ogg stream error " + itos(err));
+ int desync_iters = 0;
+
+ Vector<Vector<uint8_t>> packet_data;
+ int64_t granule_pos = 0;
+
+ while (true) {
+ err = ogg_stream_packetout(&stream_state, &packet);
+ if (err == -1) {
+ // According to the docs this is usually recoverable, but don't sit here spinning forever.
+ desync_iters++;
+ ERR_FAIL_COND_V_MSG(desync_iters > 100, Error::ERR_INVALID_DATA, "Packet sync issue during ogg import");
+ continue;
+ } else if (err == 0) {
+ // Not enough data to fully reconstruct a packet. Go on to the next page.
+ break;
+ }
+ if (packet_count == 0 && vorbis_synthesis_idheader(&packet) == 0) {
+ WARN_PRINT("Found a non-vorbis-header packet in a header position");
+ // Clearly this logical stream is not a vorbis stream, so destroy it and try again with the next page.
+ ogg_stream_destroy(&stream_state);
+ initialized_stream = false;
+ break;
+ }
+ granule_pos = packet.granulepos;
+
+ PackedByteArray data;
+ data.resize(packet.bytes);
+ memcpy(data.ptrw(), packet.packet, packet.bytes);
+ packet_data.push_back(data);
+ packet_count++;
+ }
+ if (initialized_stream) {
+ ogg_packet_sequence->push_page(granule_pos, packet_data);
+ }
+ }
+
+ ogg_vorbis_stream->set_packet_sequence(ogg_packet_sequence);
+ ogg_vorbis_stream->set_loop(loop);
+ ogg_vorbis_stream->set_loop_offset(loop_offset);
+
+ return ResourceSaver::save(p_save_path + ".oggvorbisstr", ogg_vorbis_stream);
+}
+
+ResourceImporterOGGVorbis::ResourceImporterOGGVorbis() {
+}
diff --git a/modules/vorbis/resource_importer_ogg_vorbis.h b/modules/vorbis/resource_importer_ogg_vorbis.h
new file mode 100644
index 0000000000..acdc1a3d38
--- /dev/null
+++ b/modules/vorbis/resource_importer_ogg_vorbis.h
@@ -0,0 +1,62 @@
+/*************************************************************************/
+/* resource_importer_ogg_vorbis.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef RESOURCE_IMPORTER_OGG_VORBIS_H
+#define RESOURCE_IMPORTER_OGG_VORBIS_H
+
+#include "core/io/resource_importer.h"
+
+class ResourceImporterOGGVorbis : public ResourceImporter {
+ GDCLASS(ResourceImporterOGGVorbis, ResourceImporter);
+
+ enum {
+ OGG_SYNC_BUFFER_SIZE = 8192,
+ };
+
+private:
+ // virtual int get_samples_in_packet(Vector<uint8_t> p_packet) = 0;
+
+public:
+ virtual void get_recognized_extensions(List<String> *p_extensions) const override;
+ virtual String get_save_extension() const override;
+ virtual String get_resource_type() const override;
+ virtual String get_importer_name() const override;
+ virtual String get_visible_name() const override;
+ virtual int get_preset_count() const override;
+ virtual String get_preset_name(int p_idx) const override;
+ virtual void get_import_options(List<ImportOption> *r_options, int p_preset = 0) const override;
+ virtual bool get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const override;
+
+ virtual Error import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
+
+ ResourceImporterOGGVorbis();
+};
+
+#endif // RESOURCE_IMPORTER_OGG_VORBIS_H