SDL - 如何在不停止代码执行的情况下在C ++中异步播放音频?

时间:2018-05-08 18:53:32

标签: c++ asynchronous audio sdl-2

我正在用纯C ++开发一个小行星的克隆,为此,我需要为不同的事件添加声音,例如当发射子弹时和发生爆炸时。但问题是我对音频库没有任何经验。

我正在使用Simple DirectMedia Layer(SDL)并编写了一个名为playsound()的函数,以便在发生某个事件时播放声音。然而问题是,如果发生事件,则调用playsound()并且代码执行停止,直到声音完全播放或直到我从函数返回(我使用延迟函数延迟返回)。

我想要做的是声音在后台播放而不会为游戏的其余部分造成任何延迟。我在Ubuntu 16.04上开发,不能使用Windows PlaySound()来调用ASYNC标志。

这是功能:

void playsound(string path) {
    // Initialize SDL.
        if (SDL_Init(SDL_INIT_AUDIO) < 0)
                return;

        // local variables
        Uint32 wav_length; // length of our sample
        Uint8 *wav_buffer; // buffer containing our audio file
        SDL_AudioSpec wav_spec;
        if(SDL_LoadWAV(path.c_str(), &wav_spec, &wav_buffer, &wav_length) == NULL){
          return;
        }
        SDL_AudioDeviceID deviceId = SDL_OpenAudioDevice(NULL, 0, &wav_spec, NULL, 0);
        SDL_QueueAudio(deviceId, wav_buffer, wav_length);
        SDL_PauseAudioDevice(deviceId, 0);
        SDL_Delay(50);
        SDL_CloseAudioDevice(deviceId);
        SDL_FreeWAV(wav_buffer);
        SDL_Quit();
}

3 个答案:

答案 0 :(得分:3)

你的延迟是阻止你的代码执行,50ms的延迟几乎是每帧33ms的2帧或每帧16ms的3帧,这里有帧丢失并且可能没有问题,但你可以看到如何调用几个连续的声音会减慢你的节目。

这就是我在我的引擎中播放声音的方式,使用SDL2_mixer,(短音,对于音乐,你有另一种叫做Mix_PlayMusic的方法),它可能对你有所帮助。我没有滞后(我的代码中没有使用任何睡眠或延迟)。一旦你调用play(),声音就应该全部播放,除非你的代码暂停了。

#pragma once
#include <string>
#include <memory>
#include <SDL2/SDL_mixer.h>

class sample {
public:
    sample(const std::string &path, int volume);
    void play();
    void play(int times);
    void set_volume(int volume);

private:
    std::unique_ptr<Mix_Chunk, void (*)(Mix_Chunk *)> chunk;
};

和cpp文件

#include <sample.h>

sample::sample(const std::string &path, int volume)
    : chunk(Mix_LoadWAV(path.c_str()), Mix_FreeChunk) {
    if (!chunk.get()) {
        // LOG("Couldn't load audio sample: ", path);
    }

    Mix_VolumeChunk(chunk.get(), volume);
}

// -1 here means we let SDL_mixer pick the first channel that is free
// If no channel is free it'll return an err code.
void sample::play() {
    Mix_PlayChannel(-1, chunk.get(), 0);
}

void sample::play(int times) {
    Mix_PlayChannel(-1, chunk.get(), times - 1);
}

void sample::set_volume(int volume) {
    Mix_VolumeChunk(chunk.get(), volume);
}

请注意,我不需要模拟我的模型,每次触发声音播放时程序都会继续执行。 (我猜SDL_Mixer在主SDL线程中播放)。

为了实现这个目的,在你启动SDL的地方,你还需要启动调音台

if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 1024) < 0) {
    // Error message if can't initialize
}

// Amount of channels (Max amount of sounds playing at the same time)
Mix_AllocateChannels(32);

如何播放声音的例子是

// at some point loaded a sample s with sample(path to wave mp3 or whatever)
s.play();

一些评论,您不需要使用,但可以使用代码,它更像是使用SDL2_mixer的简单示例。

缺少这种平均功能,您可能需要更严格的声音处理,例如停止声音播放(由于某种原因),如果您使用Mix_HaltChannel功能在不同声道播放声音,则可以执行此操作,以及play()函数可以接收您想要播放的频道。

所有这些函数都返回错误值,例如,如果没有未保留的通道可用,Mix_PlayChannel将返回错误代码。

您要记住的另一件事是,如果您多次播放相同的声音,它将开始变得模糊/您不会注意到是否再次播放相同的声音。因此,您可以在样本中添加一个整数来计算样本可以播放的次数。

如果您真的想要从主SDL线程中线程化混音器/音频(并且仍然只使用SDL),您可以在线程中生成新的SDL上下文并以某种方式发送信号来播放音频。

答案 1 :(得分:0)

C ++中有几种用于异步操作的工具。您可以尝试most simple std::async

auto handle = std::async(std::launch::async,
                         playsound, std::string{"/path/to/cute/sound"});

// Some other stuff. Your game logic doesn't blocked here.

handle.get(); // This can actually block.

您应该指定标志std::launch::async,这意味着将使用新线程。然后需要执行callable的名称及其参数。不要忘记包含<future>标题。

答案 2 :(得分:0)

您希望在初始化游戏时加载所有必要的资源。然后,当你想要玩它们时,它们会加载到游戏内存中并且没有滞后。并且也可以在单独的线程中播放声音,因此它不会阻止您的主线程。