Replace stb_vorbis with libogg+libvorbis

This commit is contained in:
Ellen Poe 2021-09-09 18:54:18 -07:00
parent 0d5e13cd80
commit f5d9c7b487
36 changed files with 1046 additions and 6041 deletions

1
.github/CODEOWNERS vendored
View File

@ -73,7 +73,6 @@ doc_classes/* @godotengine/documentation
/modules/minimp3/ @godotengine/audio
/modules/ogg/ @godotengine/audio
/modules/opus/ @godotengine/audio
/modules/stb_vorbis/ @godotengine/audio
/modules/theora/ @godotengine/audio
/modules/vorbis/ @godotengine/audio
/modules/webm/ @godotengine/audio

View File

@ -367,7 +367,6 @@ Copyright: 2016-2020, Aras Pranckevicius
License: public-domain or Unlicense or Expat
Files: ./thirdparty/misc/stb_rect_pack.h
./thirdparty/misc/stb_vorbis.c
Comment: stb libraries
Copyright: Sean Barrett
License: public-domain or Unlicense or Expat

View File

@ -92,7 +92,7 @@ public:
void append_array(Vector<T> p_other);
bool has(const T &p_val) {
bool has(const T &p_val) const {
return find(p_val, 0) != -1;
}

View File

@ -270,7 +270,7 @@
Converts UTF-8 encoded array to [String]. Slower than [method get_string_from_ascii] but supports UTF-8 encoded data. Use this function if you are unsure about the source of the data. For user input this function should always be preferred. Returns empty string if source array is not valid UTF-8 string.
</description>
</method>
<method name="has">
<method name="has" qualifiers="const">
<return type="bool" />
<argument index="0" name="value" type="int" />
<description>

View File

@ -56,7 +56,7 @@
Assigns the given value to all elements in the array. This can typically be used together with [method resize] to create an array with a given size and initialized elements.
</description>
</method>
<method name="has">
<method name="has" qualifiers="const">
<return type="bool" />
<argument index="0" name="value" type="Color" />
<description>

View File

@ -57,7 +57,7 @@
Assigns the given value to all elements in the array. This can typically be used together with [method resize] to create an array with a given size and initialized elements.
</description>
</method>
<method name="has">
<method name="has" qualifiers="const">
<return type="bool" />
<argument index="0" name="value" type="float" />
<description>

View File

@ -57,7 +57,7 @@
Assigns the given value to all elements in the array. This can typically be used together with [method resize] to create an array with a given size and initialized elements.
</description>
</method>
<method name="has">
<method name="has" qualifiers="const">
<return type="bool" />
<argument index="0" name="value" type="float" />
<description>

View File

@ -57,7 +57,7 @@
Assigns the given value to all elements in the array. This can typically be used together with [method resize] to create an array with a given size and initialized elements.
</description>
</method>
<method name="has">
<method name="has" qualifiers="const">
<return type="bool" />
<argument index="0" name="value" type="int" />
<description>

View File

@ -57,7 +57,7 @@
Assigns the given value to all elements in the array. This can typically be used together with [method resize] to create an array with a given size and initialized elements.
</description>
</method>
<method name="has">
<method name="has" qualifiers="const">
<return type="bool" />
<argument index="0" name="value" type="int" />
<description>

View File

@ -57,7 +57,7 @@
Assigns the given value to all elements in the array. This can typically be used together with [method resize] to create an array with a given size and initialized elements.
</description>
</method>
<method name="has">
<method name="has" qualifiers="const">
<return type="bool" />
<argument index="0" name="value" type="String" />
<description>

View File

@ -57,7 +57,7 @@
Assigns the given value to all elements in the array. This can typically be used together with [method resize] to create an array with a given size and initialized elements.
</description>
</method>
<method name="has">
<method name="has" qualifiers="const">
<return type="bool" />
<argument index="0" name="value" type="Vector2" />
<description>

View File

@ -56,7 +56,7 @@
Assigns the given value to all elements in the array. This can typically be used together with [method resize] to create an array with a given size and initialized elements.
</description>
</method>
<method name="has">
<method name="has" qualifiers="const">
<return type="bool" />
<argument index="0" name="value" type="Vector3" />
<description>

View File

@ -444,6 +444,10 @@ bool EditorFileSystem::_test_for_reimport(const String &p_path, bool p_only_impo
Ref<ResourceImporter> importer = ResourceFormatImporter::get_singleton()->get_importer_by_name(importer_name);
if (importer.is_null()) {
return true; // the importer has possibly changed, try to reimport.
}
if (importer->get_format_version() > version) {
return true; // version changed, reimport
}

View File

@ -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"

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="OGGPacketSequence" inherits="Resource" version="4.0">
<brief_description>
A sequence of OGG packets.
</brief_description>
<description>
A sequence of OGG packets.
</description>
<tutorials>
</tutorials>
<methods>
<method name="get_length" qualifiers="const">
<return type="float" />
<description>
The length of this stream, in seconds.
</description>
</method>
</methods>
<members>
<member name="granule_positions" type="Array" setter="set_packet_granule_positions" getter="get_packet_granule_positions" default="[]">
Contains the granule positions for each page in this packet sequence.
</member>
<member name="packet_data" type="Array" setter="set_packet_data" getter="get_packet_data" default="[]">
Contains the raw packets that make up this OGGPacketSequence.
</member>
<member name="sampling_rate" type="float" setter="set_sampling_rate" getter="get_sampling_rate" default="0.0">
Holds sample rate information about this sequence. Must be set by another class that actually understands the codec.
</member>
</members>
<constants>
</constants>
</class>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="OGGPacketSequencePlayback" inherits="RefCounted" version="4.0">
<brief_description>
</brief_description>
<description>
</description>
<tutorials>
</tutorials>
<methods>
</methods>
<constants>
</constants>
</class>

View File

@ -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<PackedByteArray> &p_data) {
Vector<PackedByteArray> 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<PackedByteArray>());
TypedArray<PackedByteArray> 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<PackedByteArray> &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<OGGPacketSequencePlayback> OGGPacketSequence::instance_playback() {
Ref<OGGPacketSequencePlayback> playback;
playback.instantiate();
playback->ogg_packet_sequence = Ref<OGGPacketSequence>(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;
}

View File

@ -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<Vector<PackedByteArray>> page_data;
// List of the granule position for each page.
Vector<uint64_t> 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<PackedByteArray> &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<OGGPacketSequencePlayback> instance_playback();
OGGPacketSequence() {}
virtual ~OGGPacketSequence() {}
};
class OGGPacketSequencePlayback : public RefCounted {
GDCLASS(OGGPacketSequencePlayback, RefCounted);
friend class OGGPacketSequence;
Ref<OGGPacketSequence> 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

View File

@ -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() {}

View File

@ -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)

View File

@ -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<AudioStreamPlayback> AudioStreamOGGVorbis::instance_playback() {
Ref<AudioStreamPlaybackOGGVorbis> 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<AudioStreamOGGVorbis>(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<AudioStreamPlaybackOGGVorbis>());
}
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<uint8_t> &p_data) {
int src_data_len = p_data.size();
uint32_t alloc_try = 1024;
Vector<char> 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<uint8_t> AudioStreamOGGVorbis::get_data() const {
Vector<uint8_t> 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();
}

View File

@ -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"

View File

@ -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<ResourceImporterOGGVorbis> ogg_import;
ogg_import.instantiate();
ResourceFormatImporter::get_singleton()->add_importer(ogg_import);
}
#endif
GDREGISTER_CLASS(AudioStreamOGGVorbis);
}
void unregister_stb_vorbis_types() {
}

View File

@ -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

View File

@ -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

View File

@ -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() {}

View File

@ -28,29 +28,49 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifndef AUDIO_STREAM_STB_VORBIS_H
#define AUDIO_STREAM_STB_VORBIS_H
#ifndef AUDIO_STREAM_LIBVORBIS_H
#define AUDIO_STREAM_LIBVORBIS_H
#include "core/io/resource_loader.h"
#include "core/variant/variant.h"
#include "modules/ogg/ogg_packet_sequence.h"
#include "servers/audio/audio_stream.h"
#include "thirdparty/misc/stb_vorbis.h"
#include "thirdparty/libvorbis/vorbis/codec.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;
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;
@ -72,20 +92,20 @@ public:
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");
RES_BASE_EXTENSION("oggvorbisstr");
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();
// 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();
@ -100,8 +120,8 @@ public:
virtual Ref<AudioStreamPlayback> instance_playback() override;
virtual String get_stream_name() const override;
void set_data(const Vector<uint8_t> &p_data);
Vector<uint8_t> get_data() const;
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
@ -111,4 +131,4 @@ public:
virtual ~AudioStreamOGGVorbis();
};
#endif
#endif // AUDIO_STREAM_LIBVORBIS_H

View File

@ -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"

View File

@ -1,25 +1,23 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="AudioStreamOGGVorbis" inherits="AudioStream" version="4.0">
<brief_description>
OGG Vorbis audio stream driver.
</brief_description>
<description>
OGG Vorbis audio stream driver.
</description>
<tutorials>
</tutorials>
<methods>
</methods>
<members>
<member name="data" type="PackedByteArray" setter="set_data" getter="get_data" default="PackedByteArray()">
Contains the audio data in bytes.
</member>
<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>

View File

@ -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>

View File

@ -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() {}

View File

@ -30,16 +30,19 @@
#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 "ogg_vorbis";
return "oggvorbisstr";
}
String ResourceImporterOGGVorbis::get_visible_name() const {
return "OGGVorbis";
return "oggvorbisstr";
}
void ResourceImporterOGGVorbis::get_recognized_extensions(List<String> *p_extensions) const {
@ -47,7 +50,7 @@ void ResourceImporterOGGVorbis::get_recognized_extensions(List<String> *p_extens
}
String ResourceImporterOGGVorbis::get_save_extension() const {
return "oggstr";
return "oggvorbisstr";
}
String ResourceImporterOGGVorbis::get_resource_type() const {
@ -81,23 +84,106 @@ Error ResourceImporterOGGVorbis::import(const String &p_source_file, const Strin
uint64_t len = f->get_length();
Vector<uint8_t> data;
data.resize(len);
uint8_t *w = data.ptrw();
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_stream;
ogg_stream.instantiate();
Ref<AudioStreamOGGVorbis> ogg_vorbis_stream;
ogg_vorbis_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);
Ref<OGGPacketSequence> ogg_packet_sequence;
ogg_packet_sequence.instantiate();
return ResourceSaver::save(p_save_path + ".oggstr", ogg_stream);
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() {

View File

@ -28,25 +28,29 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifndef RESOURCEIMPORTEROGGVORBIS_H
#define RESOURCEIMPORTEROGGVORBIS_H
#ifndef RESOURCE_IMPORTER_OGG_VORBIS_H
#define RESOURCE_IMPORTER_OGG_VORBIS_H
#include "audio_stream_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 String get_importer_name() const override;
virtual String get_visible_name() const override;
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;
@ -55,4 +59,4 @@ public:
ResourceImporterOGGVorbis();
};
#endif // RESOURCEIMPORTEROGGVORBIS_H
#endif // RESOURCE_IMPORTER_OGG_VORBIS_H

View File

@ -472,10 +472,6 @@ Collection of single-file libraries used in Godot components.
* Upstream: https://github.com/nothings/stb
* Version: 1.00 (2bb4a0accd4003c1db4c24533981e01b1adfd656, 2019)
* License: Public Domain or Unlicense or MIT
- `stb_vorbis.c`
* Upstream: https://github.com/nothings/stb
* Version: 1.20 (314d0a6f9af5af27e585336eecea333e95c5a2d8, 2020)
* License: Public Domain or Unlicense or MIT
- `yuv2rgb.h`
* Upstream: http://wss.co.uk/pinknoise/yuv2rgb/ (to check)
* Version: ?

File diff suppressed because it is too large Load Diff

View File

@ -1,2 +0,0 @@
#define STB_VORBIS_HEADER_ONLY
#include "stb_vorbis.c"