Add AudioStreamMP3 load_from_file/load_from_buffer and harmonize other audio streams
Move OggVorbis and MP3 loading code to their AudioStream class, matching how it's done for WAV. The duplicate functions in ResourceImporterOggVorbis are now deprecated. Co-authored-by: MaxIsJoe <34368774+MaxIsJoe@users.noreply.github.com>
This commit is contained in:
@ -30,8 +30,6 @@
|
||||
|
||||
#include "audio_stream_ogg_vorbis.h"
|
||||
|
||||
#include "modules/vorbis/resource_importer_ogg_vorbis.h"
|
||||
|
||||
#include <ogg/ogg.h>
|
||||
|
||||
int AudioStreamPlaybackOggVorbis::_mix_internal(AudioFrame *p_buffer, int p_frames) {
|
||||
@ -546,8 +544,134 @@ Ref<AudioSample> AudioStreamOggVorbis::generate_sample() const {
|
||||
return sample;
|
||||
}
|
||||
|
||||
Ref<AudioStreamOggVorbis> AudioStreamOggVorbis::load_from_buffer(const Vector<uint8_t> &p_stream_data) {
|
||||
Ref<AudioStreamOggVorbis> ogg_vorbis_stream;
|
||||
ogg_vorbis_stream.instantiate();
|
||||
|
||||
Ref<OggPacketSequence> ogg_packet_sequence;
|
||||
ogg_packet_sequence.instantiate();
|
||||
|
||||
ogg_stream_state stream_state;
|
||||
ogg_sync_state sync_state;
|
||||
ogg_page page;
|
||||
ogg_packet packet;
|
||||
bool initialized_stream = false;
|
||||
|
||||
ogg_sync_init(&sync_state);
|
||||
const long OGG_SYNC_BUFFER_SIZE = 8192;
|
||||
int err;
|
||||
size_t cursor = 0;
|
||||
size_t packet_count = 0;
|
||||
bool done = false;
|
||||
while (!done) {
|
||||
err = ogg_sync_check(&sync_state);
|
||||
ERR_FAIL_COND_V_MSG(err != 0, Ref<AudioStreamOggVorbis>(), "Ogg sync error " + itos(err));
|
||||
while (ogg_sync_pageout(&sync_state, &page) != 1) {
|
||||
if (cursor >= size_t(p_stream_data.size())) {
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
err = ogg_sync_check(&sync_state);
|
||||
ERR_FAIL_COND_V_MSG(err != 0, Ref<AudioStreamOggVorbis>(), "Ogg sync error " + itos(err));
|
||||
char *sync_buf = ogg_sync_buffer(&sync_state, OGG_SYNC_BUFFER_SIZE);
|
||||
err = ogg_sync_check(&sync_state);
|
||||
ERR_FAIL_COND_V_MSG(err != 0, Ref<AudioStreamOggVorbis>(), "Ogg sync error " + itos(err));
|
||||
size_t copy_size = p_stream_data.size() - cursor;
|
||||
if (copy_size > OGG_SYNC_BUFFER_SIZE) {
|
||||
copy_size = OGG_SYNC_BUFFER_SIZE;
|
||||
}
|
||||
memcpy(sync_buf, &p_stream_data[cursor], copy_size);
|
||||
ogg_sync_wrote(&sync_state, copy_size);
|
||||
cursor += copy_size;
|
||||
err = ogg_sync_check(&sync_state);
|
||||
ERR_FAIL_COND_V_MSG(err != 0, Ref<AudioStreamOggVorbis>(), "Ogg sync error " + itos(err));
|
||||
}
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
err = ogg_sync_check(&sync_state);
|
||||
ERR_FAIL_COND_V_MSG(err != 0, Ref<AudioStreamOggVorbis>(), "Ogg sync error " + itos(err));
|
||||
|
||||
// Have a page now.
|
||||
if (!initialized_stream) {
|
||||
if (ogg_stream_init(&stream_state, ogg_page_serialno(&page))) {
|
||||
ERR_FAIL_V_MSG(Ref<AudioStreamOggVorbis>(), "Failed allocating memory for Ogg Vorbis stream.");
|
||||
}
|
||||
initialized_stream = true;
|
||||
}
|
||||
ogg_stream_pagein(&stream_state, &page);
|
||||
err = ogg_stream_check(&stream_state);
|
||||
ERR_FAIL_COND_V_MSG(err != 0, Ref<AudioStreamOggVorbis>(), "Ogg stream error " + itos(err));
|
||||
int desync_iters = 0;
|
||||
|
||||
RBMap<uint64_t, Vector<Vector<uint8_t>>> sorted_packets;
|
||||
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++;
|
||||
WARN_PRINT_ONCE("Desync during ogg import.");
|
||||
ERR_FAIL_COND_V_MSG(desync_iters > 100, Ref<AudioStreamOggVorbis>(), "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) {
|
||||
print_verbose("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.
|
||||
if (initialized_stream) {
|
||||
ogg_stream_clear(&stream_state);
|
||||
initialized_stream = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (packet.granulepos > granule_pos) {
|
||||
granule_pos = packet.granulepos;
|
||||
}
|
||||
|
||||
if (packet.bytes > 0) {
|
||||
PackedByteArray data;
|
||||
data.resize(packet.bytes);
|
||||
memcpy(data.ptrw(), packet.packet, packet.bytes);
|
||||
sorted_packets[granule_pos].push_back(data);
|
||||
packet_count++;
|
||||
}
|
||||
}
|
||||
Vector<Vector<uint8_t>> packet_data;
|
||||
for (const KeyValue<uint64_t, Vector<Vector<uint8_t>>> &pair : sorted_packets) {
|
||||
for (const Vector<uint8_t> &packets : pair.value) {
|
||||
packet_data.push_back(packets);
|
||||
}
|
||||
}
|
||||
if (initialized_stream && packet_data.size() > 0) {
|
||||
ogg_packet_sequence->push_page(ogg_page_granulepos(&page), packet_data);
|
||||
}
|
||||
}
|
||||
if (initialized_stream) {
|
||||
ogg_stream_clear(&stream_state);
|
||||
}
|
||||
ogg_sync_clear(&sync_state);
|
||||
|
||||
if (ogg_packet_sequence->get_packet_granule_positions().is_empty()) {
|
||||
ERR_FAIL_V_MSG(Ref<AudioStreamOggVorbis>(), "Ogg Vorbis decoding failed. Check that your data is a valid Ogg Vorbis audio stream.");
|
||||
}
|
||||
|
||||
ogg_vorbis_stream->set_packet_sequence(ogg_packet_sequence);
|
||||
|
||||
return ogg_vorbis_stream;
|
||||
}
|
||||
|
||||
Ref<AudioStreamOggVorbis> AudioStreamOggVorbis::load_from_file(const String &p_path) {
|
||||
const Vector<uint8_t> stream_data = FileAccess::get_file_as_bytes(p_path);
|
||||
ERR_FAIL_COND_V_MSG(stream_data.is_empty(), Ref<AudioStreamOggVorbis>(), vformat("Cannot open file '%s'.", p_path));
|
||||
return load_from_buffer(stream_data);
|
||||
}
|
||||
|
||||
void AudioStreamOggVorbis::_bind_methods() {
|
||||
ClassDB::bind_static_method("AudioStreamOggVorbis", D_METHOD("load_from_buffer", "buffer"), &AudioStreamOggVorbis::load_from_buffer);
|
||||
ClassDB::bind_static_method("AudioStreamOggVorbis", D_METHOD("load_from_buffer", "stream_data"), &AudioStreamOggVorbis::load_from_buffer);
|
||||
ClassDB::bind_static_method("AudioStreamOggVorbis", D_METHOD("load_from_file", "path"), &AudioStreamOggVorbis::load_from_file);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_packet_sequence", "packet_sequence"), &AudioStreamOggVorbis::set_packet_sequence);
|
||||
@ -579,11 +703,3 @@ void AudioStreamOggVorbis::_bind_methods() {
|
||||
AudioStreamOggVorbis::AudioStreamOggVorbis() {}
|
||||
|
||||
AudioStreamOggVorbis::~AudioStreamOggVorbis() {}
|
||||
|
||||
Ref<AudioStreamOggVorbis> AudioStreamOggVorbis::load_from_buffer(const Vector<uint8_t> &file_data) {
|
||||
return ResourceImporterOggVorbis::load_from_buffer(file_data);
|
||||
}
|
||||
|
||||
Ref<AudioStreamOggVorbis> AudioStreamOggVorbis::load_from_file(const String &p_path) {
|
||||
return ResourceImporterOggVorbis::load_from_file(p_path);
|
||||
}
|
||||
|
||||
@ -140,7 +140,8 @@ protected:
|
||||
|
||||
public:
|
||||
static Ref<AudioStreamOggVorbis> load_from_file(const String &p_path);
|
||||
static Ref<AudioStreamOggVorbis> load_from_buffer(const Vector<uint8_t> &file_data);
|
||||
static Ref<AudioStreamOggVorbis> load_from_buffer(const Vector<uint8_t> &p_stream_data);
|
||||
|
||||
void set_loop(bool p_enable);
|
||||
virtual bool has_loop() const override;
|
||||
|
||||
|
||||
@ -12,16 +12,16 @@
|
||||
<methods>
|
||||
<method name="load_from_buffer" qualifiers="static">
|
||||
<return type="AudioStreamOggVorbis" />
|
||||
<param index="0" name="buffer" type="PackedByteArray" />
|
||||
<param index="0" name="stream_data" type="PackedByteArray" />
|
||||
<description>
|
||||
Creates a new AudioStreamOggVorbis instance from the given buffer. The buffer must contain Ogg Vorbis data.
|
||||
Creates a new [AudioStreamOggVorbis] instance from the given buffer. The buffer must contain Ogg Vorbis data.
|
||||
</description>
|
||||
</method>
|
||||
<method name="load_from_file" qualifiers="static">
|
||||
<return type="AudioStreamOggVorbis" />
|
||||
<param index="0" name="path" type="String" />
|
||||
<description>
|
||||
Creates a new AudioStreamOggVorbis instance from the given file path. The file must be in Ogg Vorbis format.
|
||||
Creates a new [AudioStreamOggVorbis] instance from the given file path. The file must be in Ogg Vorbis format.
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
|
||||
@ -12,18 +12,18 @@
|
||||
<link title="Importing audio samples">$DOCS_URL/tutorials/assets_pipeline/importing_audio_samples.html</link>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="load_from_buffer" qualifiers="static">
|
||||
<method name="load_from_buffer" qualifiers="static" deprecated="Use [method AudioStreamOggVorbis.load_from_buffer] instead.">
|
||||
<return type="AudioStreamOggVorbis" />
|
||||
<param index="0" name="buffer" type="PackedByteArray" />
|
||||
<param index="0" name="stream_data" type="PackedByteArray" />
|
||||
<description>
|
||||
This method loads audio data from a PackedByteArray buffer into an AudioStreamOggVorbis object.
|
||||
Creates a new [AudioStreamOggVorbis] instance from the given buffer. The buffer must contain Ogg Vorbis data.
|
||||
</description>
|
||||
</method>
|
||||
<method name="load_from_file" qualifiers="static">
|
||||
<method name="load_from_file" qualifiers="static" deprecated="Use [method AudioStreamOggVorbis.load_from_file] instead.">
|
||||
<return type="AudioStreamOggVorbis" />
|
||||
<param index="0" name="path" type="String" />
|
||||
<description>
|
||||
This method loads audio data from a file into an AudioStreamOggVorbis object. The file path is provided as a string.
|
||||
Creates a new [AudioStreamOggVorbis] instance from the given file path. The file must be in Ogg Vorbis format.
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
|
||||
@ -81,13 +81,12 @@ void ResourceImporterOggVorbis::get_import_options(const String &p_path, List<Im
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
|
||||
bool ResourceImporterOggVorbis::has_advanced_options() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void ResourceImporterOggVorbis::show_advanced_options(const String &p_path) {
|
||||
Ref<AudioStreamOggVorbis> ogg_stream = load_from_file(p_path);
|
||||
Ref<AudioStreamOggVorbis> ogg_stream = AudioStreamOggVorbis::load_from_file(p_path);
|
||||
if (ogg_stream.is_valid()) {
|
||||
AudioStreamImportSettingsDialog::get_singleton()->edit(p_path, "oggvorbisstr", ogg_stream);
|
||||
}
|
||||
@ -101,7 +100,7 @@ Error ResourceImporterOggVorbis::import(ResourceUID::ID p_source_id, const Strin
|
||||
int beat_count = p_options["beat_count"];
|
||||
int bar_beats = p_options["bar_beats"];
|
||||
|
||||
Ref<AudioStreamOggVorbis> ogg_vorbis_stream = load_from_file(p_source_file);
|
||||
Ref<AudioStreamOggVorbis> ogg_vorbis_stream = AudioStreamOggVorbis::load_from_file(p_source_file);
|
||||
if (ogg_vorbis_stream.is_null()) {
|
||||
return ERR_CANT_OPEN;
|
||||
}
|
||||
@ -115,135 +114,22 @@ Error ResourceImporterOggVorbis::import(ResourceUID::ID p_source_id, const Strin
|
||||
return ResourceSaver::save(ogg_vorbis_stream, p_save_path + ".oggvorbisstr");
|
||||
}
|
||||
|
||||
ResourceImporterOggVorbis::ResourceImporterOggVorbis() {
|
||||
}
|
||||
|
||||
void ResourceImporterOggVorbis::_bind_methods() {
|
||||
ClassDB::bind_static_method("ResourceImporterOggVorbis", D_METHOD("load_from_buffer", "buffer"), &ResourceImporterOggVorbis::load_from_buffer);
|
||||
ClassDB::bind_static_method("ResourceImporterOggVorbis", D_METHOD("load_from_file", "path"), &ResourceImporterOggVorbis::load_from_file);
|
||||
}
|
||||
|
||||
Ref<AudioStreamOggVorbis> ResourceImporterOggVorbis::load_from_buffer(const Vector<uint8_t> &file_data) {
|
||||
Ref<AudioStreamOggVorbis> ogg_vorbis_stream;
|
||||
ogg_vorbis_stream.instantiate();
|
||||
|
||||
Ref<OggPacketSequence> ogg_packet_sequence;
|
||||
ogg_packet_sequence.instantiate();
|
||||
|
||||
ogg_stream_state stream_state;
|
||||
ogg_sync_state sync_state;
|
||||
ogg_page page;
|
||||
ogg_packet packet;
|
||||
bool initialized_stream = false;
|
||||
|
||||
ogg_sync_init(&sync_state);
|
||||
int err;
|
||||
size_t cursor = 0;
|
||||
size_t packet_count = 0;
|
||||
bool done = false;
|
||||
while (!done) {
|
||||
err = ogg_sync_check(&sync_state);
|
||||
ERR_FAIL_COND_V_MSG(err != 0, Ref<AudioStreamOggVorbis>(), "Ogg sync error " + itos(err));
|
||||
while (ogg_sync_pageout(&sync_state, &page) != 1) {
|
||||
if (cursor >= size_t(file_data.size())) {
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
err = ogg_sync_check(&sync_state);
|
||||
ERR_FAIL_COND_V_MSG(err != 0, Ref<AudioStreamOggVorbis>(), "Ogg sync error " + itos(err));
|
||||
char *sync_buf = ogg_sync_buffer(&sync_state, OGG_SYNC_BUFFER_SIZE);
|
||||
err = ogg_sync_check(&sync_state);
|
||||
ERR_FAIL_COND_V_MSG(err != 0, Ref<AudioStreamOggVorbis>(), "Ogg sync error " + itos(err));
|
||||
size_t copy_size = file_data.size() - 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 = ogg_sync_check(&sync_state);
|
||||
ERR_FAIL_COND_V_MSG(err != 0, Ref<AudioStreamOggVorbis>(), "Ogg sync error " + itos(err));
|
||||
}
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
err = ogg_sync_check(&sync_state);
|
||||
ERR_FAIL_COND_V_MSG(err != 0, Ref<AudioStreamOggVorbis>(), "Ogg sync error " + itos(err));
|
||||
|
||||
// Have a page now.
|
||||
if (!initialized_stream) {
|
||||
if (ogg_stream_init(&stream_state, ogg_page_serialno(&page))) {
|
||||
ERR_FAIL_V_MSG(Ref<AudioStreamOggVorbis>(), "Failed allocating memory for Ogg Vorbis stream.");
|
||||
}
|
||||
initialized_stream = true;
|
||||
}
|
||||
ogg_stream_pagein(&stream_state, &page);
|
||||
err = ogg_stream_check(&stream_state);
|
||||
ERR_FAIL_COND_V_MSG(err != 0, Ref<AudioStreamOggVorbis>(), "Ogg stream error " + itos(err));
|
||||
int desync_iters = 0;
|
||||
|
||||
RBMap<uint64_t, Vector<Vector<uint8_t>>> sorted_packets;
|
||||
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++;
|
||||
WARN_PRINT_ONCE("Desync during ogg import.");
|
||||
ERR_FAIL_COND_V_MSG(desync_iters > 100, Ref<AudioStreamOggVorbis>(), "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) {
|
||||
print_verbose("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.
|
||||
if (initialized_stream) {
|
||||
ogg_stream_clear(&stream_state);
|
||||
initialized_stream = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (packet.granulepos > granule_pos) {
|
||||
granule_pos = packet.granulepos;
|
||||
}
|
||||
|
||||
if (packet.bytes > 0) {
|
||||
PackedByteArray data;
|
||||
data.resize(packet.bytes);
|
||||
memcpy(data.ptrw(), packet.packet, packet.bytes);
|
||||
sorted_packets[granule_pos].push_back(data);
|
||||
packet_count++;
|
||||
}
|
||||
}
|
||||
Vector<Vector<uint8_t>> packet_data;
|
||||
for (const KeyValue<uint64_t, Vector<Vector<uint8_t>>> &pair : sorted_packets) {
|
||||
for (const Vector<uint8_t> &packets : pair.value) {
|
||||
packet_data.push_back(packets);
|
||||
}
|
||||
}
|
||||
if (initialized_stream && packet_data.size() > 0) {
|
||||
ogg_packet_sequence->push_page(ogg_page_granulepos(&page), packet_data);
|
||||
}
|
||||
}
|
||||
if (initialized_stream) {
|
||||
ogg_stream_clear(&stream_state);
|
||||
}
|
||||
ogg_sync_clear(&sync_state);
|
||||
|
||||
if (ogg_packet_sequence->get_packet_granule_positions().is_empty()) {
|
||||
ERR_FAIL_V_MSG(Ref<AudioStreamOggVorbis>(), "Ogg Vorbis decoding failed. Check that your data is a valid Ogg Vorbis audio stream.");
|
||||
}
|
||||
|
||||
ogg_vorbis_stream->set_packet_sequence(ogg_packet_sequence);
|
||||
|
||||
return ogg_vorbis_stream;
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
Ref<AudioStreamOggVorbis> ResourceImporterOggVorbis::load_from_buffer(const Vector<uint8_t> &p_stream_data) {
|
||||
return AudioStreamOggVorbis::load_from_buffer(p_stream_data);
|
||||
}
|
||||
|
||||
Ref<AudioStreamOggVorbis> ResourceImporterOggVorbis::load_from_file(const String &p_path) {
|
||||
Vector<uint8_t> file_data = FileAccess::get_file_as_bytes(p_path);
|
||||
ERR_FAIL_COND_V_MSG(file_data.is_empty(), Ref<AudioStreamOggVorbis>(), "Cannot open file '" + p_path + "'.");
|
||||
return load_from_buffer(file_data);
|
||||
return AudioStreamOggVorbis::load_from_file(p_path);
|
||||
}
|
||||
#endif
|
||||
|
||||
void ResourceImporterOggVorbis::_bind_methods() {
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
ClassDB::bind_static_method("ResourceImporterOggVorbis", D_METHOD("load_from_buffer", "stream_data"), &ResourceImporterOggVorbis::load_from_buffer);
|
||||
ClassDB::bind_static_method("ResourceImporterOggVorbis", D_METHOD("load_from_file", "path"), &ResourceImporterOggVorbis::load_from_file);
|
||||
#endif
|
||||
}
|
||||
|
||||
ResourceImporterOggVorbis::ResourceImporterOggVorbis() {
|
||||
}
|
||||
|
||||
@ -38,10 +38,6 @@
|
||||
class ResourceImporterOggVorbis : public ResourceImporter {
|
||||
GDCLASS(ResourceImporterOggVorbis, ResourceImporter);
|
||||
|
||||
enum {
|
||||
OGG_SYNC_BUFFER_SIZE = 8192,
|
||||
};
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
@ -51,8 +47,11 @@ public:
|
||||
virtual void show_advanced_options(const String &p_path) override;
|
||||
#endif
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
static Ref<AudioStreamOggVorbis> load_from_file(const String &p_path);
|
||||
static Ref<AudioStreamOggVorbis> load_from_buffer(const Vector<uint8_t> &file_data);
|
||||
static Ref<AudioStreamOggVorbis> load_from_buffer(const Vector<uint8_t> &p_stream_data);
|
||||
#endif
|
||||
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user