自定义音频流

简介

AudioStream是所有音频发射对象的基类。AudioStreamPlayer绑定到AudioStream以将PCM数据发送到管理音频驱动程序的AudioServer。

所有音频资源都需要两个基于音频的类:AudioStream和AudioStreamPlayback。作为数据容器,AudioStream包含资源并将其自身暴露给GDScript。AudioStream引用其自己的内部自定义AudioStreamPlayback,该AudioStreamPlayback将AudioStream转换为PCM数据。

本指南假设读者知道如何创建C++模块。如果没有,请参考该指南 自定义C++模块

可以做什么?

  • 绑定外部库(如Wwise、FMOD等)。
  • 添加自定义音频队列
  • 添加对更多音频格式的支持

创建一个音频流

AudioStream由三个组件组成:数据容器,流名称和AudioStreamPlayback朋友类生成器。音频数据可以通过多种方式加载,例如使用用于音调发生器的内部计数器、内部/外部缓冲区、或文件引用。

某些AudioStream需要是无状态的,例如从ResourceLoader加载的对象。ResourceLoader加载一次并引用同一对象,而不管在该特定资源上调用了多少次 load。因此,播放状态必须被包含在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.instance();
        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);
}

创建一个AudioStreamPlayback

AudioStreamPlayer使用 mix 回调来获取PCM数据。回调必须与采样率匹配并填充缓冲区。

由于AudioStreamPlayback由音频线程控制,因此禁止进行i/o和动态内存分配。

/*  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;
}

重采样

Godot's AudioServer currently uses 44100 Hz sample rate. When other sample rates are needed such as 48000, either provide one or use AudioStreamPlaybackResampled. Godot provides cubic interpolation for audio resampling.

AudioStreamPlaybackResampled不是重载 mix ,而是使用 _mix_internal 来查询AudioFrames,并使用 get_stream_sampling_rate 来查询当前的混合率。

#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;
}