Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

AudioStreams Personalizados

Introducción

AudioStream es la clase base de todos los objetos que emiten audio. AudioStreamPlayer se vincula a un AudioStream para emitir datos PCM en un AudioServer que gestiona los controladores de audio.

Todos los recursos de audio requieren dos clases basadas en audio: AudioStream y AudioStreamPlayback. Como container de datos, AudioStream contiene el recurso y se expone a GDScript. AudioStream hace referencia a su propio AudioStreamPlayback interno personalizado que traduce AudioStream en datos PCM.

This guide assumes the reader knows how to create C++ modules. If not, refer to this guide Módulos personalizados en C++.

Referencias:

¿Para qué?

  • Vinculando librerias externas (como Wwise, FMOD, etc).

  • Agregando colas de audio personalizados

  • Agregando soporte para mas formatos de audio

Creando un AudioStream

Un AudioStream consta de tres componentes: container de datos, nombre del stream y un generador de clases amigables AudioStreamPlayback. Los datos de audio pueden cargarse de varias maneras, por ejemplo, con un contador interno para un generador de tonos, una memoria intermedia interna/externa o una referencia de archivo.

Algunos AudioStreams necesitan ser sin estado, como los objetos cargados desde ResourceLoader. ResourceLoader carga una vez y hace referencia al mismo objeto sin importar cuántas veces se llame a load en un recurso específico. Por lo tanto, el estado de reproducción debe estar autocontenido en AudioStreamPlayback.

/* audiostream_mytone.h */

#include "core/reference.h"
#include "core/resource.h"
#include "servers/audio/audio_stream.h"

class AudioStreamMyTone : public AudioStream {
        GDCLASS(AudioStreamMyTone, AudioStream)

private:
        friend class AudioStreamPlaybackMyTone;
        uint64_t pos;
        int mix_rate;
        bool stereo;
        int hz;

public:
        void reset();
        void set_position(uint64_t pos);
        virtual Ref<AudioStreamPlayback> instance_playback();
        virtual String get_stream_name() const;
        void gen_tone(int16_t *pcm_buf, int size);
        virtual float get_length() const { return 0; } // if supported, otherwise return 0
        AudioStreamMyTone();

protected:
        static void _bind_methods();
};
/* audiostream_mytone.cpp */

#include "audiostream_mytone.h"

AudioStreamMyTone::AudioStreamMyTone()
                : mix_rate(44100), stereo(false), hz(639) {
}

Ref<AudioStreamPlayback> AudioStreamMyTone::instance_playback() {
        Ref<AudioStreamPlaybackMyTone> talking_tree;
        talking_tree.instantiate();
        talking_tree->base = Ref<AudioStreamMyTone>(this);
        return talking_tree;
}

String AudioStreamMyTone::get_stream_name() const {
        return "MyTone";
}
void AudioStreamMyTone::reset() {
        set_position(0);
}
void AudioStreamMyTone::set_position(uint64_t p) {
        pos = p;
}
void AudioStreamMyTone::gen_tone(int16_t *pcm_buf, int size) {
        for (int i = 0; i < size; i++) {
                pcm_buf[i] = 32767.0 * sin(2.0 * Math_PI * double(pos + i) / (double(mix_rate) / double(hz)));
        }
        pos += size;
}
void AudioStreamMyTone::_bind_methods() {
        ClassDB::bind_method(D_METHOD("reset"), &AudioStreamMyTone::reset);
        ClassDB::bind_method(D_METHOD("get_stream_name"), &AudioStreamMyTone::get_stream_name);
}

Referencias:

Creando un AudioStreamPlayback

El AudioStreamPlayer utiliza el callback mix para obtener datos PCM. El callback debe coincidir con la frecuencia de muestreo (sample rate) y llenar el búfer de audio.

Dado que AudioStreamPlayback está controlado por el hilo de audio, las operaciones de entrada/salida y la asignación dinámica de memoria están prohibidas.

/*  audiostreamplayer_mytone.h */

#include "core/reference.h"
#include "core/resource.h"
#include "servers/audio/audio_stream.h"

class AudioStreamPlaybackMyTone : public AudioStreamPlayback {
        GDCLASS(AudioStreamPlaybackMyTone, AudioStreamPlayback)
        friend class AudioStreamMyTone;

private:
        enum {
                PCM_BUFFER_SIZE = 4096
        };
        enum {
                MIX_FRAC_BITS = 13,
                MIX_FRAC_LEN = (1 << MIX_FRAC_BITS),
                MIX_FRAC_MASK = MIX_FRAC_LEN - 1,
        };
        void *pcm_buffer;
        Ref<AudioStreamMyTone> base;
        bool active;

public:
        virtual void start(float p_from_pos = 0.0);
        virtual void stop();
        virtual bool is_playing() const;
        virtual int get_loop_count() const; // times it looped
        virtual float get_playback_position() const;
        virtual void seek(float p_time);
        virtual void mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames);
        virtual float get_length() const; // if supported, otherwise return 0
        AudioStreamPlaybackMyTone();
        ~AudioStreamPlaybackMyTone();
};
/* audiostreamplayer_mytone.cpp */

#include "audiostreamplayer_mytone.h"

#include "core/math/math_funcs.h"
#include "core/print_string.h"

AudioStreamPlaybackMyTone::AudioStreamPlaybackMyTone()
                : active(false) {
        AudioServer::get_singleton()->lock();
        pcm_buffer = AudioServer::get_singleton()->audio_data_alloc(PCM_BUFFER_SIZE);
        zeromem(pcm_buffer, PCM_BUFFER_SIZE);
        AudioServer::get_singleton()->unlock();
}
AudioStreamPlaybackMyTone::~AudioStreamPlaybackMyTone() {
        if(pcm_buffer) {
                AudioServer::get_singleton()->audio_data_free(pcm_buffer);
                pcm_buffer = NULL;
        }
}
void AudioStreamPlaybackMyTone::stop() {
        active = false;
        base->reset();
}
void AudioStreamPlaybackMyTone::start(float p_from_pos) {
        seek(p_from_pos);
        active = true;
}
void AudioStreamPlaybackMyTone::seek(float p_time) {
        float max = get_length();
        if (p_time < 0) {
                        p_time = 0;
        }
        base->set_position(uint64_t(p_time * base->mix_rate) << MIX_FRAC_BITS);
}
void AudioStreamPlaybackMyTone::mix(AudioFrame *p_buffer, float p_rate, int p_frames) {
        ERR_FAIL_COND(!active);
        if (!active) {
                        return;
        }
        zeromem(pcm_buffer, PCM_BUFFER_SIZE);
        int16_t *buf = (int16_t *)pcm_buffer;
        base->gen_tone(buf, p_frames);

        for(int i = 0; i < p_frames; i++) {
                float sample = float(buf[i]) / 32767.0;
                p_buffer[i] = AudioFrame(sample, sample);
        }
}
int AudioStreamPlaybackMyTone::get_loop_count() const {
        return 0;
}
float AudioStreamPlaybackMyTone::get_playback_position() const {
        return 0.0;
}
float AudioStreamPlaybackMyTone::get_length() const {
        return 0.0;
}
bool AudioStreamPlaybackMyTone::is_playing() const {
        return active;
}

Remuestreo

El AudioServer de Godot actualmente utiliza una frecuencia de muestreo (sample rate) de 44100 Hz. Cuando se necesiten otras frecuencias de muestreo, como 48000 Hz, es necesario proporcionar una o utilizar AudioStreamPlaybackResampled. Godot ofrece interpolación cúbica para el remuestreo de audio.

En lugar de sobrecargar mix, AudioStreamPlaybackResampled utiliza _mix_internal para obtener AudioFrames y get_stream_sampling_rate para obtener la frecuencia de mezcla actual.

#include "core/reference.h"
#include "core/resource.h"
#include "servers/audio/audio_stream.h"

class AudioStreamMyToneResampled;

class AudioStreamPlaybackResampledMyTone : public AudioStreamPlaybackResampled {
        GDCLASS(AudioStreamPlaybackResampledMyTone, AudioStreamPlaybackResampled)
        friend class AudioStreamMyToneResampled;

private:
        enum {
                PCM_BUFFER_SIZE = 4096
        };
        enum {
                MIX_FRAC_BITS = 13,
                MIX_FRAC_LEN = (1 << MIX_FRAC_BITS),
                MIX_FRAC_MASK = MIX_FRAC_LEN - 1,
        };
        void *pcm_buffer;
        Ref<AudioStreamMyToneResampled> base;
        bool active;

protected:
        virtual void _mix_internal(AudioFrame *p_buffer, int p_frames);

public:
        virtual void start(float p_from_pos = 0.0);
        virtual void stop();
        virtual bool is_playing() const;
        virtual int get_loop_count() const; // times it looped
        virtual float get_playback_position() const;
        virtual void seek(float p_time);
        virtual float get_length() const; // if supported, otherwise return 0
        virtual float get_stream_sampling_rate();
        AudioStreamPlaybackResampledMyTone();
        ~AudioStreamPlaybackResampledMyTone();
};
#include "mytone_audiostream_resampled.h"

#include "core/math/math_funcs.h"
#include "core/print_string.h"

AudioStreamPlaybackResampledMyTone::AudioStreamPlaybackResampledMyTone()
                : active(false) {
        AudioServer::get_singleton()->lock();
        pcm_buffer = AudioServer::get_singleton()->audio_data_alloc(PCM_BUFFER_SIZE);
        zeromem(pcm_buffer, PCM_BUFFER_SIZE);
        AudioServer::get_singleton()->unlock();
}
AudioStreamPlaybackResampledMyTone::~AudioStreamPlaybackResampledMyTone() {
        if (pcm_buffer) {
                AudioServer::get_singleton()->audio_data_free(pcm_buffer);
                pcm_buffer = NULL;
        }
}
void AudioStreamPlaybackResampledMyTone::stop() {
        active = false;
        base->reset();
}
void AudioStreamPlaybackResampledMyTone::start(float p_from_pos) {
        seek(p_from_pos);
        active = true;
}
void AudioStreamPlaybackResampledMyTone::seek(float p_time) {
        float max = get_length();
        if (p_time < 0) {
                        p_time = 0;
        }
        base->set_position(uint64_t(p_time * base->mix_rate) << MIX_FRAC_BITS);
}
void AudioStreamPlaybackResampledMyTone::_mix_internal(AudioFrame *p_buffer, int p_frames) {
        ERR_FAIL_COND(!active);
        if (!active) {
                return;
        }
        zeromem(pcm_buffer, PCM_BUFFER_SIZE);
        int16_t *buf = (int16_t *)pcm_buffer;
        base->gen_tone(buf, p_frames);

        for(int i = 0;  i < p_frames; i++) {
                float sample = float(buf[i]) / 32767.0;
                        p_buffer[i] = AudioFrame(sample, sample);
        }
}
float AudioStreamPlaybackResampledMyTone::get_stream_sampling_rate() {
        return float(base->mix_rate);
}
int AudioStreamPlaybackResampledMyTone::get_loop_count() const {
        return 0;
}
float AudioStreamPlaybackResampledMyTone::get_playback_position() const {
        return 0.0;
}
float AudioStreamPlaybackResampledMyTone::get_length() const {
        return 0.0;
}
bool AudioStreamPlaybackResampledMyTone::is_playing() const {
        return active;
}

Referencias: