Replace stb_vorbis with libogg+libvorbis
This commit is contained in:
parent
0d5e13cd80
commit
f5d9c7b487
36 changed files with 1046 additions and 6041 deletions
|
|
@ -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
|
||||
|
|
|
|||
435
modules/vorbis/audio_stream_ogg_vorbis.cpp
Normal file
435
modules/vorbis/audio_stream_ogg_vorbis.cpp
Normal 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() {}
|
||||
134
modules/vorbis/audio_stream_ogg_vorbis.h
Normal file
134
modules/vorbis/audio_stream_ogg_vorbis.h
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
/*************************************************************************/
|
||||
/* audio_stream_ogg_vorbis.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#ifndef AUDIO_STREAM_LIBVORBIS_H
|
||||
#define AUDIO_STREAM_LIBVORBIS_H
|
||||
|
||||
#include "core/variant/variant.h"
|
||||
#include "modules/ogg/ogg_packet_sequence.h"
|
||||
#include "servers/audio/audio_stream.h"
|
||||
#include "thirdparty/libvorbis/vorbis/codec.h"
|
||||
|
||||
class AudioStreamOGGVorbis;
|
||||
|
||||
class AudioStreamPlaybackOGGVorbis : public AudioStreamPlaybackResampled {
|
||||
GDCLASS(AudioStreamPlaybackOGGVorbis, AudioStreamPlaybackResampled);
|
||||
|
||||
uint32_t frames_mixed = 0;
|
||||
bool active = false;
|
||||
int loops = 0;
|
||||
|
||||
vorbis_info info;
|
||||
vorbis_comment comment;
|
||||
vorbis_dsp_state dsp_state;
|
||||
vorbis_block block;
|
||||
|
||||
bool info_is_allocated = false;
|
||||
bool comment_is_allocated = false;
|
||||
bool dsp_state_is_allocated = false;
|
||||
bool block_is_allocated = false;
|
||||
|
||||
bool ready = false;
|
||||
|
||||
bool have_samples_left = false;
|
||||
bool have_packets_left = false;
|
||||
|
||||
friend class AudioStreamOGGVorbis;
|
||||
|
||||
Ref<OGGPacketSequence> vorbis_data;
|
||||
Ref<OGGPacketSequencePlayback> vorbis_data_playback;
|
||||
Ref<AudioStreamOGGVorbis> vorbis_stream;
|
||||
|
||||
int _mix_frames_vorbis(AudioFrame *p_buffer, int p_frames);
|
||||
|
||||
// Allocates vorbis data structures. Returns true upon success, false on failure.
|
||||
bool _alloc_vorbis();
|
||||
|
||||
protected:
|
||||
virtual int _mix_internal(AudioFrame *p_buffer, int p_frames) override;
|
||||
virtual float get_stream_sampling_rate() override;
|
||||
|
||||
public:
|
||||
virtual void start(float p_from_pos = 0.0) override;
|
||||
virtual void stop() override;
|
||||
virtual bool is_playing() const override;
|
||||
|
||||
virtual int get_loop_count() const override; //times it looped
|
||||
|
||||
virtual float get_playback_position() const override;
|
||||
virtual void seek(float p_time) override;
|
||||
|
||||
AudioStreamPlaybackOGGVorbis() {}
|
||||
~AudioStreamPlaybackOGGVorbis();
|
||||
};
|
||||
|
||||
class AudioStreamOGGVorbis : public AudioStream {
|
||||
GDCLASS(AudioStreamOGGVorbis, AudioStream);
|
||||
OBJ_SAVE_TYPE(AudioStream); // Saves derived classes with common type so they can be interchanged.
|
||||
RES_BASE_EXTENSION("oggvorbisstr");
|
||||
|
||||
friend class AudioStreamPlaybackOGGVorbis;
|
||||
|
||||
int channels = 1;
|
||||
float length = 0.0;
|
||||
bool loop = false;
|
||||
float loop_offset = 0.0;
|
||||
|
||||
// Performs a seek to the beginning of the stream, should not be called during playback!
|
||||
// Also causes allocation and deallocation.
|
||||
void maybe_update_info();
|
||||
|
||||
Ref<OGGPacketSequence> packet_sequence;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_loop(bool p_enable);
|
||||
bool has_loop() const;
|
||||
|
||||
void set_loop_offset(float p_seconds);
|
||||
float get_loop_offset() const;
|
||||
|
||||
virtual Ref<AudioStreamPlayback> instance_playback() override;
|
||||
virtual String get_stream_name() const override;
|
||||
|
||||
void set_packet_sequence(Ref<OGGPacketSequence> p_packet_sequence);
|
||||
Ref<OGGPacketSequence> get_packet_sequence() const;
|
||||
|
||||
virtual float get_length() const override; //if supported, otherwise return 0
|
||||
|
||||
virtual bool is_monophonic() const override;
|
||||
|
||||
AudioStreamOGGVorbis();
|
||||
virtual ~AudioStreamOGGVorbis();
|
||||
};
|
||||
|
||||
#endif // AUDIO_STREAM_LIBVORBIS_H
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
24
modules/vorbis/doc_classes/AudioStreamOGGVorbis.xml
Normal file
24
modules/vorbis/doc_classes/AudioStreamOGGVorbis.xml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="AudioStreamOGGVorbis" inherits="AudioStream" version="4.0">
|
||||
<brief_description>
|
||||
</brief_description>
|
||||
<description>
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
<methods>
|
||||
</methods>
|
||||
<members>
|
||||
<member name="loop" type="bool" setter="set_loop" getter="has_loop" default="false">
|
||||
If [code]true[/code], the stream will automatically loop when it reaches the end.
|
||||
</member>
|
||||
<member name="loop_offset" type="float" setter="set_loop_offset" getter="get_loop_offset" default="0.0">
|
||||
Time in seconds at which the stream starts after being looped.
|
||||
</member>
|
||||
<member name="packet_sequence" type="OGGPacketSequence" setter="set_packet_sequence" getter="get_packet_sequence">
|
||||
Contains the raw OGG data for this stream.
|
||||
</member>
|
||||
</members>
|
||||
<constants>
|
||||
</constants>
|
||||
</class>
|
||||
13
modules/vorbis/doc_classes/AudioStreamPlaybackOGGVorbis.xml
Normal file
13
modules/vorbis/doc_classes/AudioStreamPlaybackOGGVorbis.xml
Normal 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>
|
||||
|
|
@ -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() {}
|
||||
|
|
|
|||
190
modules/vorbis/resource_importer_ogg_vorbis.cpp
Normal file
190
modules/vorbis/resource_importer_ogg_vorbis.cpp
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
/*************************************************************************/
|
||||
/* resource_importer_ogg_vorbis.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "resource_importer_ogg_vorbis.h"
|
||||
|
||||
#include "audio_stream_ogg_vorbis.h"
|
||||
#include "core/io/file_access.h"
|
||||
#include "core/io/resource_saver.h"
|
||||
#include "scene/resources/texture.h"
|
||||
#include "thirdparty/libogg/ogg/ogg.h"
|
||||
#include "thirdparty/libvorbis/vorbis/codec.h"
|
||||
|
||||
String ResourceImporterOGGVorbis::get_importer_name() const {
|
||||
return "oggvorbisstr";
|
||||
}
|
||||
|
||||
String ResourceImporterOGGVorbis::get_visible_name() const {
|
||||
return "oggvorbisstr";
|
||||
}
|
||||
|
||||
void ResourceImporterOGGVorbis::get_recognized_extensions(List<String> *p_extensions) const {
|
||||
p_extensions->push_back("ogg");
|
||||
}
|
||||
|
||||
String ResourceImporterOGGVorbis::get_save_extension() const {
|
||||
return "oggvorbisstr";
|
||||
}
|
||||
|
||||
String ResourceImporterOGGVorbis::get_resource_type() const {
|
||||
return "AudioStreamOGGVorbis";
|
||||
}
|
||||
|
||||
bool ResourceImporterOGGVorbis::get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
int ResourceImporterOGGVorbis::get_preset_count() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
String ResourceImporterOGGVorbis::get_preset_name(int p_idx) const {
|
||||
return String();
|
||||
}
|
||||
|
||||
void ResourceImporterOGGVorbis::get_import_options(List<ImportOption> *r_options, int p_preset) const {
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "loop"), true));
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "loop_offset"), 0));
|
||||
}
|
||||
|
||||
Error ResourceImporterOGGVorbis::import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) {
|
||||
bool loop = p_options["loop"];
|
||||
float loop_offset = p_options["loop_offset"];
|
||||
|
||||
FileAccess *f = FileAccess::open(p_source_file, FileAccess::READ);
|
||||
|
||||
ERR_FAIL_COND_V_MSG(!f, ERR_CANT_OPEN, "Cannot open file '" + p_source_file + "'.");
|
||||
|
||||
uint64_t len = f->get_length();
|
||||
|
||||
Vector<uint8_t> file_data;
|
||||
file_data.resize(len);
|
||||
uint8_t *w = file_data.ptrw();
|
||||
|
||||
f->get_buffer(w, len);
|
||||
|
||||
memdelete(f);
|
||||
|
||||
Ref<AudioStreamOGGVorbis> ogg_vorbis_stream;
|
||||
ogg_vorbis_stream.instantiate();
|
||||
|
||||
Ref<OGGPacketSequence> ogg_packet_sequence;
|
||||
ogg_packet_sequence.instantiate();
|
||||
|
||||
ogg_stream_state stream_state;
|
||||
ogg_sync_state sync_state;
|
||||
ogg_page page;
|
||||
ogg_packet packet;
|
||||
bool initialized_stream = false;
|
||||
|
||||
ogg_sync_init(&sync_state);
|
||||
int err;
|
||||
size_t cursor = 0;
|
||||
size_t packet_count = 0;
|
||||
bool done = false;
|
||||
while (!done) {
|
||||
ERR_FAIL_COND_V_MSG((err = ogg_sync_check(&sync_state)), Error::ERR_INVALID_DATA, "Ogg sync error " + itos(err));
|
||||
while (ogg_sync_pageout(&sync_state, &page) != 1) {
|
||||
if (cursor >= len) {
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
ERR_FAIL_COND_V_MSG((err = ogg_sync_check(&sync_state)), Error::ERR_INVALID_DATA, "Ogg sync error " + itos(err));
|
||||
char *sync_buf = ogg_sync_buffer(&sync_state, OGG_SYNC_BUFFER_SIZE);
|
||||
ERR_FAIL_COND_V_MSG((err = ogg_sync_check(&sync_state)), Error::ERR_INVALID_DATA, "Ogg sync error " + itos(err));
|
||||
ERR_FAIL_COND_V(cursor > len, Error::ERR_INVALID_DATA);
|
||||
size_t copy_size = len - cursor;
|
||||
if (copy_size > OGG_SYNC_BUFFER_SIZE) {
|
||||
copy_size = OGG_SYNC_BUFFER_SIZE;
|
||||
}
|
||||
memcpy(sync_buf, &file_data[cursor], copy_size);
|
||||
ogg_sync_wrote(&sync_state, copy_size);
|
||||
cursor += copy_size;
|
||||
ERR_FAIL_COND_V_MSG((err = ogg_sync_check(&sync_state)), Error::ERR_INVALID_DATA, "Ogg sync error " + itos(err));
|
||||
}
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
ERR_FAIL_COND_V_MSG((err = ogg_sync_check(&sync_state)), Error::ERR_INVALID_DATA, "Ogg sync error " + itos(err));
|
||||
|
||||
// Have a page now.
|
||||
if (!initialized_stream) {
|
||||
ogg_stream_init(&stream_state, ogg_page_serialno(&page));
|
||||
ERR_FAIL_COND_V_MSG((err = ogg_stream_check(&stream_state)), Error::ERR_INVALID_DATA, "Ogg stream error " + itos(err));
|
||||
initialized_stream = true;
|
||||
}
|
||||
ERR_FAIL_COND_V_MSG((err = ogg_stream_check(&stream_state)), Error::ERR_INVALID_DATA, "Ogg stream error " + itos(err));
|
||||
ogg_stream_pagein(&stream_state, &page);
|
||||
ERR_FAIL_COND_V_MSG((err = ogg_stream_check(&stream_state)), Error::ERR_INVALID_DATA, "Ogg stream error " + itos(err));
|
||||
int desync_iters = 0;
|
||||
|
||||
Vector<Vector<uint8_t>> packet_data;
|
||||
int64_t granule_pos = 0;
|
||||
|
||||
while (true) {
|
||||
err = ogg_stream_packetout(&stream_state, &packet);
|
||||
if (err == -1) {
|
||||
// According to the docs this is usually recoverable, but don't sit here spinning forever.
|
||||
desync_iters++;
|
||||
ERR_FAIL_COND_V_MSG(desync_iters > 100, Error::ERR_INVALID_DATA, "Packet sync issue during ogg import");
|
||||
continue;
|
||||
} else if (err == 0) {
|
||||
// Not enough data to fully reconstruct a packet. Go on to the next page.
|
||||
break;
|
||||
}
|
||||
if (packet_count == 0 && vorbis_synthesis_idheader(&packet) == 0) {
|
||||
WARN_PRINT("Found a non-vorbis-header packet in a header position");
|
||||
// Clearly this logical stream is not a vorbis stream, so destroy it and try again with the next page.
|
||||
ogg_stream_destroy(&stream_state);
|
||||
initialized_stream = false;
|
||||
break;
|
||||
}
|
||||
granule_pos = packet.granulepos;
|
||||
|
||||
PackedByteArray data;
|
||||
data.resize(packet.bytes);
|
||||
memcpy(data.ptrw(), packet.packet, packet.bytes);
|
||||
packet_data.push_back(data);
|
||||
packet_count++;
|
||||
}
|
||||
if (initialized_stream) {
|
||||
ogg_packet_sequence->push_page(granule_pos, packet_data);
|
||||
}
|
||||
}
|
||||
|
||||
ogg_vorbis_stream->set_packet_sequence(ogg_packet_sequence);
|
||||
ogg_vorbis_stream->set_loop(loop);
|
||||
ogg_vorbis_stream->set_loop_offset(loop_offset);
|
||||
|
||||
return ResourceSaver::save(p_save_path + ".oggvorbisstr", ogg_vorbis_stream);
|
||||
}
|
||||
|
||||
ResourceImporterOGGVorbis::ResourceImporterOGGVorbis() {
|
||||
}
|
||||
62
modules/vorbis/resource_importer_ogg_vorbis.h
Normal file
62
modules/vorbis/resource_importer_ogg_vorbis.h
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/*************************************************************************/
|
||||
/* resource_importer_ogg_vorbis.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#ifndef RESOURCE_IMPORTER_OGG_VORBIS_H
|
||||
#define RESOURCE_IMPORTER_OGG_VORBIS_H
|
||||
|
||||
#include "core/io/resource_importer.h"
|
||||
|
||||
class ResourceImporterOGGVorbis : public ResourceImporter {
|
||||
GDCLASS(ResourceImporterOGGVorbis, ResourceImporter);
|
||||
|
||||
enum {
|
||||
OGG_SYNC_BUFFER_SIZE = 8192,
|
||||
};
|
||||
|
||||
private:
|
||||
// virtual int get_samples_in_packet(Vector<uint8_t> p_packet) = 0;
|
||||
|
||||
public:
|
||||
virtual void get_recognized_extensions(List<String> *p_extensions) const override;
|
||||
virtual String get_save_extension() const override;
|
||||
virtual String get_resource_type() const override;
|
||||
virtual String get_importer_name() const override;
|
||||
virtual String get_visible_name() const override;
|
||||
virtual int get_preset_count() const override;
|
||||
virtual String get_preset_name(int p_idx) const override;
|
||||
virtual void get_import_options(List<ImportOption> *r_options, int p_preset = 0) const override;
|
||||
virtual bool get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const override;
|
||||
|
||||
virtual Error import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
|
||||
|
||||
ResourceImporterOGGVorbis();
|
||||
};
|
||||
|
||||
#endif // RESOURCE_IMPORTER_OGG_VORBIS_H
|
||||
Loading…
Add table
Add a link
Reference in a new issue