From f5d9c7b487166562a833fc86363d78468d711070 Mon Sep 17 00:00:00 2001 From: Ellen Poe Date: Thu, 9 Sep 2021 18:54:18 -0700 Subject: Replace stb_vorbis with libogg+libvorbis --- modules/ogg/config.py | 11 + modules/ogg/doc_classes/OGGPacketSequence.xml | 32 ++ .../ogg/doc_classes/OGGPacketSequencePlayback.xml | 13 + modules/ogg/ogg_packet_sequence.cpp | 220 +++++++++++ modules/ogg/ogg_packet_sequence.h | 128 ++++++ modules/ogg/register_types.cpp | 7 +- modules/stb_vorbis/SCsub | 27 -- modules/stb_vorbis/audio_stream_ogg_vorbis.cpp | 278 ------------- modules/stb_vorbis/audio_stream_ogg_vorbis.h | 114 ------ modules/stb_vorbis/config.py | 16 - .../doc_classes/AudioStreamOGGVorbis.xml | 26 -- modules/stb_vorbis/register_types.cpp | 52 --- modules/stb_vorbis/register_types.h | 37 -- .../stb_vorbis/resource_importer_ogg_vorbis.cpp | 104 ----- modules/stb_vorbis/resource_importer_ogg_vorbis.h | 58 --- modules/vorbis/SCsub | 3 - modules/vorbis/audio_stream_ogg_vorbis.cpp | 435 +++++++++++++++++++++ modules/vorbis/audio_stream_ogg_vorbis.h | 134 +++++++ modules/vorbis/config.py | 11 + .../vorbis/doc_classes/AudioStreamOGGVorbis.xml | 24 ++ .../doc_classes/AudioStreamPlaybackOGGVorbis.xml | 13 + modules/vorbis/register_types.cpp | 15 +- modules/vorbis/resource_importer_ogg_vorbis.cpp | 190 +++++++++ modules/vorbis/resource_importer_ogg_vorbis.h | 62 +++ 24 files changed, 1291 insertions(+), 719 deletions(-) create mode 100644 modules/ogg/doc_classes/OGGPacketSequence.xml create mode 100644 modules/ogg/doc_classes/OGGPacketSequencePlayback.xml create mode 100644 modules/ogg/ogg_packet_sequence.cpp create mode 100644 modules/ogg/ogg_packet_sequence.h delete mode 100644 modules/stb_vorbis/SCsub delete mode 100644 modules/stb_vorbis/audio_stream_ogg_vorbis.cpp delete mode 100644 modules/stb_vorbis/audio_stream_ogg_vorbis.h delete mode 100644 modules/stb_vorbis/config.py delete mode 100644 modules/stb_vorbis/doc_classes/AudioStreamOGGVorbis.xml delete mode 100644 modules/stb_vorbis/register_types.cpp delete mode 100644 modules/stb_vorbis/register_types.h delete mode 100644 modules/stb_vorbis/resource_importer_ogg_vorbis.cpp delete mode 100644 modules/stb_vorbis/resource_importer_ogg_vorbis.h create mode 100644 modules/vorbis/audio_stream_ogg_vorbis.cpp create mode 100644 modules/vorbis/audio_stream_ogg_vorbis.h create mode 100644 modules/vorbis/doc_classes/AudioStreamOGGVorbis.xml create mode 100644 modules/vorbis/doc_classes/AudioStreamPlaybackOGGVorbis.xml create mode 100644 modules/vorbis/resource_importer_ogg_vorbis.cpp create mode 100644 modules/vorbis/resource_importer_ogg_vorbis.h (limited to 'modules') diff --git a/modules/ogg/config.py b/modules/ogg/config.py index d22f9454ed..5a417ba8dd 100644 --- a/modules/ogg/config.py +++ b/modules/ogg/config.py @@ -4,3 +4,14 @@ def can_build(env, platform): def configure(env): pass + + +def get_doc_classes(): + return [ + "OGGPacketSequence", + "OGGPacketSequencePlayback", + ] + + +def get_doc_path(): + return "doc_classes" diff --git a/modules/ogg/doc_classes/OGGPacketSequence.xml b/modules/ogg/doc_classes/OGGPacketSequence.xml new file mode 100644 index 0000000000..9d3789cb07 --- /dev/null +++ b/modules/ogg/doc_classes/OGGPacketSequence.xml @@ -0,0 +1,32 @@ + + + + A sequence of OGG packets. + + + A sequence of OGG packets. + + + + + + + + The length of this stream, in seconds. + + + + + + Contains the granule positions for each page in this packet sequence. + + + Contains the raw packets that make up this OGGPacketSequence. + + + Holds sample rate information about this sequence. Must be set by another class that actually understands the codec. + + + + + diff --git a/modules/ogg/doc_classes/OGGPacketSequencePlayback.xml b/modules/ogg/doc_classes/OGGPacketSequencePlayback.xml new file mode 100644 index 0000000000..49e32f0d6e --- /dev/null +++ b/modules/ogg/doc_classes/OGGPacketSequencePlayback.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/modules/ogg/ogg_packet_sequence.cpp b/modules/ogg/ogg_packet_sequence.cpp new file mode 100644 index 0000000000..b7a3ad2876 --- /dev/null +++ b/modules/ogg/ogg_packet_sequence.cpp @@ -0,0 +1,220 @@ +/*************************************************************************/ +/* ogg_packet_sequence.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 "ogg_packet_sequence.h" +#include "core/variant/typed_array.h" + +void OGGPacketSequence::push_page(int64_t p_granule_pos, const Vector &p_data) { + Vector data_stored; + for (int i = 0; i < p_data.size(); i++) { + data_stored.push_back(p_data[i]); + } + page_granule_positions.push_back(p_granule_pos); + page_data.push_back(data_stored); + data_version++; +} + +void OGGPacketSequence::set_packet_data(const Array &p_data) { + data_version++; // Update the data version so old playbacks know that they can't rely on us anymore. + page_data.clear(); + for (int page_idx = 0; page_idx < p_data.size(); page_idx++) { + // Push a new page. We cleared the vector so this will be at index `page_idx`. + page_data.push_back(Vector()); + TypedArray this_page_data = p_data[page_idx]; + for (int packet = 0; packet < this_page_data.size(); packet++) { + page_data.write[page_idx].push_back(this_page_data[packet]); + } + } +} + +Array OGGPacketSequence::get_packet_data() const { + Array ret; + for (const Vector &page : page_data) { + Array page_variant; + for (const PackedByteArray &packet : page) { + page_variant.push_back(packet); + } + ret.push_back(page_variant); + } + return ret; +} + +void OGGPacketSequence::set_packet_granule_positions(const Array &p_granule_positions) { + data_version++; // Update the data version so old playbacks know that they can't rely on us anymore. + page_granule_positions.clear(); + for (int page_idx = 0; page_idx < p_granule_positions.size(); page_idx++) { + int64_t granule_pos = p_granule_positions[page_idx]; + page_granule_positions.push_back(granule_pos); + } +} + +Array OGGPacketSequence::get_packet_granule_positions() const { + Array ret; + for (int64_t granule_pos : page_granule_positions) { + ret.push_back(granule_pos); + } + return ret; +} + +void OGGPacketSequence::set_sampling_rate(float p_sampling_rate) { + sampling_rate = p_sampling_rate; +} + +float OGGPacketSequence::get_sampling_rate() const { + return sampling_rate; +} + +int64_t OGGPacketSequence::get_final_granule_pos() const { + if (!page_granule_positions.is_empty()) { + return page_granule_positions[page_granule_positions.size() - 1]; + } + return -1; +} + +float OGGPacketSequence::get_length() const { + int64_t granule_pos = get_final_granule_pos(); + if (granule_pos < 0) { + return 0; + } + return granule_pos / sampling_rate; +} + +Ref OGGPacketSequence::instance_playback() { + Ref playback; + playback.instantiate(); + playback->ogg_packet_sequence = Ref(this); + playback->data_version = data_version; + + return playback; +} + +void OGGPacketSequence::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_packet_data", "packet_data"), &OGGPacketSequence::set_packet_data); + ClassDB::bind_method(D_METHOD("get_packet_data"), &OGGPacketSequence::get_packet_data); + + ClassDB::bind_method(D_METHOD("set_packet_granule_positions", "granule_positions"), &OGGPacketSequence::set_packet_granule_positions); + ClassDB::bind_method(D_METHOD("get_packet_granule_positions"), &OGGPacketSequence::get_packet_granule_positions); + + ClassDB::bind_method(D_METHOD("set_sampling_rate", "sampling_rate"), &OGGPacketSequence::set_sampling_rate); + ClassDB::bind_method(D_METHOD("get_sampling_rate"), &OGGPacketSequence::get_sampling_rate); + + ClassDB::bind_method(D_METHOD("get_length"), &OGGPacketSequence::get_length); + + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "packet_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_packet_data", "get_packet_data"); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "granule_positions", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_packet_granule_positions", "get_packet_granule_positions"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sampling_rate", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_sampling_rate", "get_sampling_rate"); +} + +bool OGGPacketSequencePlayback::next_ogg_packet(ogg_packet **p_packet) const { + ERR_FAIL_COND_V(data_version != ogg_packet_sequence->data_version, false); + ERR_FAIL_COND_V(ogg_packet_sequence->page_data.is_empty(), false); + ERR_FAIL_COND_V(ogg_packet_sequence->page_granule_positions.is_empty(), false); + // Move on to the next page if need be. This happens first to help simplify seek logic. + while (packet_cursor >= ogg_packet_sequence->page_data[page_cursor].size()) { + packet_cursor = 0; + page_cursor++; + if (page_cursor >= ogg_packet_sequence->page_data.size()) { + return false; + } + } + + ERR_FAIL_COND_V(page_cursor >= ogg_packet_sequence->page_data.size(), false); + + packet->b_o_s = page_cursor == 0 && packet_cursor == 0; + packet->e_o_s = page_cursor == ogg_packet_sequence->page_data.size() - 1 && packet_cursor == ogg_packet_sequence->page_data[page_cursor].size() - 1; + packet->granulepos = packet_cursor == ogg_packet_sequence->page_data[page_cursor].size() - 1 ? ogg_packet_sequence->page_granule_positions[page_cursor] : -1; + packet->packetno = packetno++; + packet->bytes = ogg_packet_sequence->page_data[page_cursor][packet_cursor].size(); + packet->packet = (unsigned char *)(ogg_packet_sequence->page_data[page_cursor][packet_cursor].ptr()); + + *p_packet = packet; + + packet_cursor++; + + return true; +} + +uint32_t OGGPacketSequencePlayback::seek_page_internal(int64_t granule, uint32_t after_page_inclusive, uint32_t before_page_inclusive) { + if (before_page_inclusive == after_page_inclusive) { + return before_page_inclusive; + } + uint32_t actual_middle_page = after_page_inclusive + (before_page_inclusive - after_page_inclusive) / 2; + // Complicating the bisection search algorithm, the middle page might not have a packet that ends on it, + // which means it might not have a correct granule position. Find a nearby page that does have a packet ending on it. + uint32_t bisection_page = -1; + for (uint32_t test_page = actual_middle_page; test_page <= before_page_inclusive; test_page++) { + if (ogg_packet_sequence->page_data[test_page].size() > 0) { + bisection_page = test_page; + break; + } + } + // Check if we have to go backwards. + if (bisection_page == (unsigned int)-1) { + for (uint32_t test_page = actual_middle_page; test_page >= after_page_inclusive; test_page--) { + if (ogg_packet_sequence->page_data[test_page].size() > 0) { + bisection_page = test_page; + break; + } + } + } + if (bisection_page == (unsigned int)-1) { + return -1; + } + + int64_t bisection_granule_pos = ogg_packet_sequence->page_granule_positions[bisection_page]; + if (granule > bisection_granule_pos) { + return seek_page_internal(granule, bisection_page + 1, before_page_inclusive); + } else { + return seek_page_internal(granule, after_page_inclusive, bisection_page); + } +} + +bool OGGPacketSequencePlayback::seek_page(int64_t p_granule_pos) { + int correct_page = seek_page_internal(p_granule_pos, 0, ogg_packet_sequence->page_data.size() - 1); + if (correct_page == -1) { + return false; + } + + packet_cursor = 0; + page_cursor = correct_page; + + // Don't pretend subsequent packets are contiguous with previous ones. + packetno = 0; + + return true; +} + +OGGPacketSequencePlayback::OGGPacketSequencePlayback() { + packet = new ogg_packet(); +} + +OGGPacketSequencePlayback::~OGGPacketSequencePlayback() { + delete packet; +} diff --git a/modules/ogg/ogg_packet_sequence.h b/modules/ogg/ogg_packet_sequence.h new file mode 100644 index 0000000000..b00ada06c1 --- /dev/null +++ b/modules/ogg/ogg_packet_sequence.h @@ -0,0 +1,128 @@ +/*************************************************************************/ +/* ogg_packet_sequence.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 OGG_PACKET_SEQUENCE_H +#define OGG_PACKET_SEQUENCE_H + +#include "core/io/resource.h" +#include "core/object/gdvirtual.gen.inc" +#include "core/variant/native_ptr.h" +#include "core/variant/typed_array.h" +#include "core/variant/variant.h" +#include "thirdparty/libogg/ogg/ogg.h" + +class OGGPacketSequencePlayback; + +class OGGPacketSequence : public Resource { + GDCLASS(OGGPacketSequence, Resource); + + friend class OGGPacketSequencePlayback; + + // List of pages, each of which is a list of packets on that page. The innermost PackedByteArrays contain complete ogg packets. + Vector> page_data; + + // List of the granule position for each page. + Vector page_granule_positions; + + // The page after the current last page. Similar semantics to an end() iterator. + int64_t end_page = 0; + + uint64_t data_version = 0; + + float sampling_rate = 0; + float length = 0; + +protected: + static void _bind_methods(); + +public: + // Pushes information about all the pages that ended on this page. + // This should be called for each page, even for pages that no packets ended on. + void push_page(int64_t p_granule_pos, const Vector &p_data); + + void set_packet_data(const Array &p_data); + Array get_packet_data() const; + + void set_packet_granule_positions(const Array &p_granule_positions); + Array get_packet_granule_positions() const; + + // Sets a sampling rate associated with this object. OGGPacketSequence doesn't understand codecs, + // so this value is naively stored as a convenience. + void set_sampling_rate(float p_sampling_rate); + + // Returns a sampling rate previously set by set_sampling_rate(). + float get_sampling_rate() const; + + // Returns a length previously set by set_length(). + float get_length() const; + + // Returns the granule position of the last page in this sequence. + int64_t get_final_granule_pos() const; + + Ref instance_playback(); + + OGGPacketSequence() {} + virtual ~OGGPacketSequence() {} +}; + +class OGGPacketSequencePlayback : public RefCounted { + GDCLASS(OGGPacketSequencePlayback, RefCounted); + + friend class OGGPacketSequence; + + Ref ogg_packet_sequence; + + mutable int64_t page_cursor = 0; + mutable int32_t packet_cursor = 0; + + mutable ogg_packet *packet; + + uint64_t data_version; + + mutable int64_t packetno = 0; + + // Recursive bisection search for the correct page. + uint32_t seek_page_internal(int64_t granule, uint32_t after_page_inclusive, uint32_t before_page_inclusive); + +public: + // Calling functions must not modify this packet. + // Returns true on success, false on error or if there is no next packet. + bool next_ogg_packet(ogg_packet **p_packet) const; + + // Seeks to the page such that the previous page has a granule position less than or equal to this value, + // and the current page has a granule position greater than this value. + // Returns true on success, false on failure. + bool seek_page(int64_t p_granule_pos); + + OGGPacketSequencePlayback(); + virtual ~OGGPacketSequencePlayback(); +}; + +#endif // OGG_PACKET_SEQUENCE_H diff --git a/modules/ogg/register_types.cpp b/modules/ogg/register_types.cpp index b23ea65378..3448e7063a 100644 --- a/modules/ogg/register_types.cpp +++ b/modules/ogg/register_types.cpp @@ -30,8 +30,11 @@ #include "register_types.h" -// Dummy module as libogg is needed by other modules (vorbis, theora, opus, ...) +#include "ogg_packet_sequence.h" -void register_ogg_types() {} +void register_ogg_types() { + GDREGISTER_CLASS(OGGPacketSequence); + GDREGISTER_CLASS(OGGPacketSequencePlayback); +} void unregister_ogg_types() {} diff --git a/modules/stb_vorbis/SCsub b/modules/stb_vorbis/SCsub deleted file mode 100644 index 8fddb23dc8..0000000000 --- a/modules/stb_vorbis/SCsub +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python - -Import("env") -Import("env_modules") - -env_stb_vorbis = env_modules.Clone() - -# Thirdparty source files - -thirdparty_obj = [] - -thirdparty_sources = ["#thirdparty/misc/stb_vorbis.c"] - -env_thirdparty = env_stb_vorbis.Clone() -env_thirdparty.disable_warnings() -env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) -env.modules_sources += thirdparty_obj - -# Godot source files - -module_obj = [] - -env_stb_vorbis.add_source_files(module_obj, "*.cpp") -env.modules_sources += module_obj - -# Needed to force rebuilding the module files when the thirdparty library is updated. -env.Depends(module_obj, thirdparty_obj) diff --git a/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp b/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp deleted file mode 100644 index 6554c6e274..0000000000 --- a/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp +++ /dev/null @@ -1,278 +0,0 @@ -/*************************************************************************/ -/* 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" - -int AudioStreamPlaybackOGGVorbis::_mix_internal(AudioFrame *p_buffer, int p_frames) { - ERR_FAIL_COND_V(!active, 0); - - int todo = p_frames; - - int start_buffer = 0; - - int frames_mixed_this_step = p_frames; - - while (todo && active) { - float *buffer = (float *)p_buffer; - if (start_buffer > 0) { - buffer = (buffer + start_buffer * 2); - } - int mixed = stb_vorbis_get_samples_float_interleaved(ogg_stream, 2, buffer, todo * 2); - if (vorbis_stream->channels == 1 && mixed > 0) { - //mix mono to stereo - for (int i = start_buffer; i < start_buffer + mixed; i++) { - p_buffer[i].r = p_buffer[i].l; - } - } - todo -= mixed; - frames_mixed += mixed; - - if (todo) { - //end of file! - bool is_not_empty = mixed > 0 || stb_vorbis_stream_length_in_samples(ogg_stream) > 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; -} - -float AudioStreamPlaybackOGGVorbis::get_stream_sampling_rate() { - return vorbis_stream->sample_rate; -} - -void AudioStreamPlaybackOGGVorbis::start(float p_from_pos) { - 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_stream->sample_rate; -} - -void AudioStreamPlaybackOGGVorbis::seek(float p_time) { - if (!active) { - return; - } - - if (p_time >= vorbis_stream->get_length()) { - p_time = 0; - } - frames_mixed = uint32_t(vorbis_stream->sample_rate * p_time); - - stb_vorbis_seek(ogg_stream, frames_mixed); -} - -AudioStreamPlaybackOGGVorbis::~AudioStreamPlaybackOGGVorbis() { - if (ogg_alloc.alloc_buffer) { - stb_vorbis_close(ogg_stream); - memfree(ogg_alloc.alloc_buffer); - } -} - -Ref AudioStreamOGGVorbis::instance_playback() { - Ref ovs; - - ERR_FAIL_COND_V_MSG(data == nullptr, ovs, - "This AudioStreamOGGVorbis does not have an audio file assigned " - "to it. AudioStreamOGGVorbis should not be created from the " - "inspector or with `.new()`. Instead, load an audio file."); - - ovs.instantiate(); - ovs->vorbis_stream = Ref(this); - ovs->ogg_alloc.alloc_buffer = (char *)memalloc(decode_mem_size); - ovs->ogg_alloc.alloc_buffer_length_in_bytes = decode_mem_size; - ovs->frames_mixed = 0; - ovs->active = false; - ovs->loops = 0; - int error; - ovs->ogg_stream = stb_vorbis_open_memory((const unsigned char *)data, data_len, &error, &ovs->ogg_alloc); - if (!ovs->ogg_stream) { - memfree(ovs->ogg_alloc.alloc_buffer); - ovs->ogg_alloc.alloc_buffer = nullptr; - ERR_FAIL_COND_V(!ovs->ogg_stream, Ref()); - } - - return ovs; -} - -String AudioStreamOGGVorbis::get_stream_name() const { - return ""; //return stream_name; -} - -void AudioStreamOGGVorbis::clear_data() { - if (data) { - memfree(data); - data = nullptr; - data_len = 0; - } -} - -void AudioStreamOGGVorbis::set_data(const Vector &p_data) { - int src_data_len = p_data.size(); - uint32_t alloc_try = 1024; - Vector alloc_mem; - char *w; - stb_vorbis *ogg_stream = nullptr; - stb_vorbis_alloc ogg_alloc; - - // Vorbis comments may be up to UINT32_MAX, but that's arguably pretty rare. - // Let's go with 2^30 so we don't risk going out of bounds. - const uint32_t MAX_TEST_MEM = 1 << 30; - - while (alloc_try < MAX_TEST_MEM) { - alloc_mem.resize(alloc_try); - w = alloc_mem.ptrw(); - - ogg_alloc.alloc_buffer = w; - ogg_alloc.alloc_buffer_length_in_bytes = alloc_try; - - const uint8_t *src_datar = p_data.ptr(); - - int error; - ogg_stream = stb_vorbis_open_memory((const unsigned char *)src_datar, src_data_len, &error, &ogg_alloc); - - if (!ogg_stream && error == VORBIS_outofmem) { - alloc_try *= 2; - } else { - ERR_FAIL_COND(alloc_try == MAX_TEST_MEM); - ERR_FAIL_COND(ogg_stream == nullptr); - - stb_vorbis_info info = stb_vorbis_get_info(ogg_stream); - - channels = info.channels; - sample_rate = info.sample_rate; - decode_mem_size = alloc_try; - //does this work? (it's less mem..) - //decode_mem_size = ogg_alloc.alloc_buffer_length_in_bytes + info.setup_memory_required + info.temp_memory_required + info.max_frame_size; - - length = stb_vorbis_stream_length_in_seconds(ogg_stream); - stb_vorbis_close(ogg_stream); - - // free any existing data - clear_data(); - - data = memalloc(src_data_len); - memcpy(data, src_datar, src_data_len); - data_len = src_data_len; - - break; - } - } - - ERR_FAIL_COND_MSG(alloc_try == MAX_TEST_MEM, vformat("Couldn't set vorbis data even with an alloc buffer of %d bytes, report bug.", MAX_TEST_MEM)); -} - -Vector AudioStreamOGGVorbis::get_data() const { - Vector vdata; - - if (data_len && data) { - vdata.resize(data_len); - { - uint8_t *w = vdata.ptrw(); - memcpy(w, data, data_len); - } - } - - return vdata; -} - -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 { - return length; -} - -bool AudioStreamOGGVorbis::is_monophonic() const { - return false; -} - -void AudioStreamOGGVorbis::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_data", "data"), &AudioStreamOGGVorbis::set_data); - ClassDB::bind_method(D_METHOD("get_data"), &AudioStreamOGGVorbis::get_data); - - 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::PACKED_BYTE_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_data", "get_data"); - 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() { - clear_data(); -} diff --git a/modules/stb_vorbis/audio_stream_ogg_vorbis.h b/modules/stb_vorbis/audio_stream_ogg_vorbis.h deleted file mode 100644 index 1311c4ce7a..0000000000 --- a/modules/stb_vorbis/audio_stream_ogg_vorbis.h +++ /dev/null @@ -1,114 +0,0 @@ -/*************************************************************************/ -/* 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_STB_VORBIS_H -#define AUDIO_STREAM_STB_VORBIS_H - -#include "core/io/resource_loader.h" -#include "servers/audio/audio_stream.h" - -#include "thirdparty/misc/stb_vorbis.h" - -class AudioStreamOGGVorbis; - -class AudioStreamPlaybackOGGVorbis : public AudioStreamPlaybackResampled { - GDCLASS(AudioStreamPlaybackOGGVorbis, AudioStreamPlaybackResampled); - - stb_vorbis *ogg_stream = nullptr; - stb_vorbis_alloc ogg_alloc; - uint32_t frames_mixed = 0; - bool active = false; - int loops = 0; - - friend class AudioStreamOGGVorbis; - - Ref vorbis_stream; - -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("oggstr"); - - friend class AudioStreamPlaybackOGGVorbis; - - void *data = nullptr; - uint32_t data_len = 0; - - int decode_mem_size = 0; - float sample_rate = 1.0; - int channels = 1; - float length = 0.0; - bool loop = false; - float loop_offset = 0.0; - void clear_data(); - -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 instance_playback() override; - virtual String get_stream_name() const override; - - void set_data(const Vector &p_data); - Vector get_data() const; - - virtual float get_length() const override; //if supported, otherwise return 0 - - virtual bool is_monophonic() const override; - - AudioStreamOGGVorbis(); - virtual ~AudioStreamOGGVorbis(); -}; - -#endif diff --git a/modules/stb_vorbis/config.py b/modules/stb_vorbis/config.py deleted file mode 100644 index 1eb0a8cf33..0000000000 --- a/modules/stb_vorbis/config.py +++ /dev/null @@ -1,16 +0,0 @@ -def can_build(env, platform): - return True - - -def configure(env): - pass - - -def get_doc_classes(): - return [ - "AudioStreamOGGVorbis", - ] - - -def get_doc_path(): - return "doc_classes" diff --git a/modules/stb_vorbis/doc_classes/AudioStreamOGGVorbis.xml b/modules/stb_vorbis/doc_classes/AudioStreamOGGVorbis.xml deleted file mode 100644 index 94fdff5d43..0000000000 --- a/modules/stb_vorbis/doc_classes/AudioStreamOGGVorbis.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - OGG Vorbis audio stream driver. - - - OGG Vorbis audio stream driver. - - - - - - - - Contains the audio data in bytes. - - - If [code]true[/code], the stream will automatically loop when it reaches the end. - - - Time in seconds at which the stream starts after being looped. - - - - - diff --git a/modules/stb_vorbis/register_types.cpp b/modules/stb_vorbis/register_types.cpp deleted file mode 100644 index bdb1cf69cf..0000000000 --- a/modules/stb_vorbis/register_types.cpp +++ /dev/null @@ -1,52 +0,0 @@ -/*************************************************************************/ -/* register_types.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 "register_types.h" - -#include "audio_stream_ogg_vorbis.h" - -#ifdef TOOLS_ENABLED -#include "core/config/engine.h" -#include "resource_importer_ogg_vorbis.h" -#endif - -void register_stb_vorbis_types() { -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - Ref ogg_import; - ogg_import.instantiate(); - ResourceFormatImporter::get_singleton()->add_importer(ogg_import); - } -#endif - GDREGISTER_CLASS(AudioStreamOGGVorbis); -} - -void unregister_stb_vorbis_types() { -} diff --git a/modules/stb_vorbis/register_types.h b/modules/stb_vorbis/register_types.h deleted file mode 100644 index d36d87606c..0000000000 --- a/modules/stb_vorbis/register_types.h +++ /dev/null @@ -1,37 +0,0 @@ -/*************************************************************************/ -/* register_types.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 STB_VORBIS_REGISTER_TYPES_H -#define STB_VORBIS_REGISTER_TYPES_H - -void register_stb_vorbis_types(); -void unregister_stb_vorbis_types(); - -#endif // STB_VORBIS_REGISTER_TYPES_H diff --git a/modules/stb_vorbis/resource_importer_ogg_vorbis.cpp b/modules/stb_vorbis/resource_importer_ogg_vorbis.cpp deleted file mode 100644 index 85de698efd..0000000000 --- a/modules/stb_vorbis/resource_importer_ogg_vorbis.cpp +++ /dev/null @@ -1,104 +0,0 @@ -/*************************************************************************/ -/* 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 "core/io/file_access.h" -#include "core/io/resource_saver.h" -#include "scene/resources/texture.h" - -String ResourceImporterOGGVorbis::get_importer_name() const { - return "ogg_vorbis"; -} - -String ResourceImporterOGGVorbis::get_visible_name() const { - return "OGGVorbis"; -} - -void ResourceImporterOGGVorbis::get_recognized_extensions(List *p_extensions) const { - p_extensions->push_back("ogg"); -} - -String ResourceImporterOGGVorbis::get_save_extension() const { - return "oggstr"; -} - -String ResourceImporterOGGVorbis::get_resource_type() const { - return "AudioStreamOGGVorbis"; -} - -bool ResourceImporterOGGVorbis::get_option_visibility(const String &p_option, const Map &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 *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 &p_options, List *r_platform_variants, List *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 data; - data.resize(len); - uint8_t *w = data.ptrw(); - - f->get_buffer(w, len); - - memdelete(f); - - Ref ogg_stream; - ogg_stream.instantiate(); - - ogg_stream->set_data(data); - ERR_FAIL_COND_V(!ogg_stream->get_data().size(), ERR_FILE_CORRUPT); - ogg_stream->set_loop(loop); - ogg_stream->set_loop_offset(loop_offset); - - return ResourceSaver::save(p_save_path + ".oggstr", ogg_stream); -} - -ResourceImporterOGGVorbis::ResourceImporterOGGVorbis() { -} diff --git a/modules/stb_vorbis/resource_importer_ogg_vorbis.h b/modules/stb_vorbis/resource_importer_ogg_vorbis.h deleted file mode 100644 index 60fe3381fb..0000000000 --- a/modules/stb_vorbis/resource_importer_ogg_vorbis.h +++ /dev/null @@ -1,58 +0,0 @@ -/*************************************************************************/ -/* 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 RESOURCEIMPORTEROGGVORBIS_H -#define RESOURCEIMPORTEROGGVORBIS_H - -#include "audio_stream_ogg_vorbis.h" -#include "core/io/resource_importer.h" - -class ResourceImporterOGGVorbis : public ResourceImporter { - GDCLASS(ResourceImporterOGGVorbis, ResourceImporter); - -public: - virtual String get_importer_name() const override; - virtual String get_visible_name() const override; - virtual void get_recognized_extensions(List *p_extensions) const override; - virtual String get_save_extension() const override; - virtual String get_resource_type() 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 *r_options, int p_preset = 0) const override; - virtual bool get_option_visibility(const String &p_option, const Map &p_options) const override; - - virtual Error import(const String &p_source_file, const String &p_save_path, const Map &p_options, List *r_platform_variants, List *r_gen_files = nullptr, Variant *r_metadata = nullptr) override; - - ResourceImporterOGGVorbis(); -}; - -#endif // RESOURCEIMPORTEROGGVORBIS_H 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 AudioStreamOGGVorbis::instance_playback() { + Ref ovs; + + ERR_FAIL_COND_V(packet_sequence.is_null(), nullptr); + + ovs.instantiate(); + ovs->vorbis_stream = Ref(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 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 p_packet_sequence) { + packet_sequence = p_packet_sequence; + if (packet_sequence.is_valid()) { + maybe_update_info(); + } +} + +Ref 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 vorbis_data; + Ref vorbis_data_playback; + Ref 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 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 instance_playback() override; + virtual String get_stream_name() const override; + + void set_packet_sequence(Ref p_packet_sequence); + Ref 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 @@ + + + + + + + + + + + + + If [code]true[/code], the stream will automatically loop when it reaches the end. + + + Time in seconds at which the stream starts after being looped. + + + Contains the raw OGG data for this stream. + + + + + 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 @@ + + + + + + + + + + + + + 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 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 *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 &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 *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 &p_options, List *r_platform_variants, List *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 file_data; + file_data.resize(len); + uint8_t *w = file_data.ptrw(); + + f->get_buffer(w, len); + + memdelete(f); + + Ref ogg_vorbis_stream; + ogg_vorbis_stream.instantiate(); + + Ref 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> 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 p_packet) = 0; + +public: + virtual void get_recognized_extensions(List *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 *r_options, int p_preset = 0) const override; + virtual bool get_option_visibility(const String &p_option, const Map &p_options) const override; + + virtual Error import(const String &p_source_file, const String &p_save_path, const Map &p_options, List *r_platform_variants, List *r_gen_files = nullptr, Variant *r_metadata = nullptr) override; + + ResourceImporterOGGVorbis(); +}; + +#endif // RESOURCE_IMPORTER_OGG_VORBIS_H -- cgit v1.2.3