SFML2.6 音频模块--自定义音频流

音频流?那是什么?

音频流与音乐类似(还记得sf::Music类吗?)。它几乎具有相同的函数和行为。唯一的区别是音频流不播放音频文件:相反,它播放您直接提供的自定义音频源。换句话说,定义自己的音频流允许您播放不仅限于文件的内容:从网络流式传输的声音,由您的程序生成的音乐,SFML不支持的音频格式等等。

事实上,sf::Music类只是一个专门的音频流,它从文件中获取其音频样本。

由于我们正在讨论流式传输,因此我们将处理无法完全加载到内存中的音频数据,并在播放时加载小块。如果您的声音可以完全加载并且可以适合内存,则音频流将无助于您:只需将音频数据加载到sf::SoundBuffer中,然后使用常规的sf::Sound来播放它。

sf::SoundStream

为了定义自己的音频流,您需要继承sf::SoundStream抽象基类。在您的派生类中有两个要重写的虚拟函数:onGetData和onSeek。

class MyAudioStream : public sf::SoundStream
{
    virtual bool onGetData(Chunk& data);

    virtual void onSeek(sf::Time timeOffset);
};

当基类用尽音频样本并需要更多音频样本时,onGetData会被调用。您需要通过填充data参数来提供新的音频样本:

bool MyAudioStream::onGetData(Chunk& data)
{
    data.samples = /* put the pointer to the new audio samples */;
    data.sampleCount = /* put the number of audio samples available in the new chunk */;
    return true;
}

当一切正常时,您必须返回true,如果发生错误或者没有更多的音频数据播放,则必须返回false以停止播放。

SFML会在onGetData返回时立即对音频样本进行内部拷贝,因此如果您不想保留原始数据,则无需保留。

当调用setPlayingOffset公共函数时,onSeek函数会被调用。它的目的是改变源数据的当前播放位置。参数是一个时间值,表示新位置,从声音的开头开始计算(而不是从当前位置开始)。在某些情况下,此函数可能无法实现。在这种情况下,请留空,并告诉您类的用户不支持更改播放位置。

现在,您的类几乎已经准备好工作了。SFML现在需要知道您的流的通道数和采样率,以便可以按预期进行播放。为了让基类知道这些参数,您必须在流类中尽快调用受保护的函数initialize,以便在流加载 / 初始化时知道它们。

// where this is done totally depends on how your stream class is designed
unsigned int channelCount = ...;
unsigned int sampleRate = ...;
initialize(channelCount, sampleRate);

线程问题

音频流始终在单独的线程中播放,因此了解发生的事情以及发生的位置非常重要。

onSeek直接由setPlayingOffset函数调用,因此它始终在调用线程中执行。但是,只要流正在播放,onGetData函数将被重复调用,在SFML创建的单独的线程中执行。如果您的流使用可能会同时在调用线程和播放线程中访问的数据,则必须保护它(例如使用互斥锁),以避免并发访问,这可能会导致未定义的行为 - 播放损坏的数据,崩溃等等。

如果您对线程不太熟悉,可以参考相应的教程获取更多信息。

使用你的音频流

现在您已经定义了自己的音频流类,让我们看看如何使用它。实际上,与关于sf :: Music的教程所示的非常相似。您可以使用play,pause,stop和setPlayingOffset函数控制播放。您还可以设置声音的属性,例如音量或音调。您可以参考API文档或其他音频教程以获取更多详细信息。

一个简单例子

下面是一个非常简单的自定义音频流类示例,它播放声音缓冲区的数据。这样的类可能看起来完全没有用,但重点是关注该类如何流式传输数据,而不管数据来自何处。

#include <SFML/Audio.hpp>
#include <vector>

// custom audio stream that plays a loaded buffer
class MyStream : public sf::SoundStream
{
public:

    void load(const sf::SoundBuffer& buffer)
    {
        // extract the audio samples from the sound buffer to our own container
        m_samples.assign(buffer.getSamples(), buffer.getSamples() + buffer.getSampleCount());

        // reset the current playing position 
        m_currentSample = 0;

        // initialize the base class
        initialize(buffer.getChannelCount(), buffer.getSampleRate());
    }

private:

    virtual bool onGetData(Chunk& data)
    {
        // number of samples to stream every time the function is called;
        // in a more robust implementation, it should be a fixed
        // amount of time rather than an arbitrary number of samples
        const int samplesToStream = 50000;

        // set the pointer to the next audio samples to be played
        data.samples = &m_samples[m_currentSample];

        // have we reached the end of the sound?
        if (m_currentSample + samplesToStream <= m_samples.size())
        {
            // end not reached: stream the samples and continue
            data.sampleCount = samplesToStream;
            m_currentSample += samplesToStream;
            return true;
        }
        else
        {
            // end of stream reached: stream the remaining samples and stop playback
            data.sampleCount = m_samples.size() - m_currentSample;
            m_currentSample = m_samples.size();
            return false;
        }
    }

    virtual void onSeek(sf::Time timeOffset)
    {
        // compute the corresponding sample index according to the sample rate and channel count
        m_currentSample = static_cast<std::size_t>(timeOffset.asSeconds() * getSampleRate() * getChannelCount());
    }

    std::vector<sf::Int16> m_samples;
    std::size_t m_currentSample;
};

int main()
{
    // load an audio buffer from a sound file
    sf::SoundBuffer buffer;
    buffer.loadFromFile("sound.wav");

    // initialize and play our custom stream
    MyStream stream;
    stream.load(buffer);
    stream.play();

    // let it play until it is finished
    while (stream.getStatus() == MyStream::Playing)
        sf::sleep(sf::seconds(0.1f));

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值