自訂 AudioStream

前言

AudioStream 是所有音訊發出物件的基底類別。AudioStreamPlayer 會繫結到 AudioStream 上,將 PCM 資料傳送至 AudioServer,由 AudioServer 管理音訊驅動程式。

所有音訊資源都需有兩個基礎類別:AudioStream 以及 AudioStreamPlayback。AudioStream 作為資料容器,會包含音訊資源並對 GDScript 開放存取。AudioStream 會參照其內部自訂的 AudioStreamPlayback,將 AudioStream 轉換成 PCM 資料。

本指南假設讀者已熟悉如何建立 C++ 模組。如尚未了解,請先參考這篇指南:以 C++ 自訂模組

參考資料:

用途說明?

  • 繫結外部函式庫(如 Wwise、FMOD 等)。

  • 新增自訂音訊佇列

  • 增加更多音訊格式的支援

建立 AudioStream

一個 AudioStream 由三個組成元件構成:資料容器、串流名稱,以及 AudioStreamPlayback 友類產生器。音訊資料可透過多種方式載入,例如作為音調產生器的內部計數器、內部或外部緩衝區,或是檔案參照。

有些 AudioStream 需為無狀態(stateless),如由 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.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);
}

參考資料:

建立 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 的 AudioServer 目前採用 44100 Hz 取樣率。若需其他取樣率(如 48000 Hz),可自行提供或使用 AudioStreamPlaybackResampled。Godot 提供三次方內插(cubic interpolation)進行音訊重新取樣。

AudioStreamPlaybackResampled 並非複寫 mix,而是使用 _mix_internal 查詢 AudioFrame,並以 get_stream_sampling_rate 取得目前混音取樣率。

mytone_audiostream_resampled.h
#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();
};
mytone_audiostream_resampled.cpp
#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;
}

參考資料: