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

标签 c++ asynchronous audio sdl-2

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

我正在使用简单 DirectMedia 层 (SDL) 并编写了一个名为 playsound() 的函数来在特定事件发生时播放声音。然而问题在于,如果事件发生,将调用 playsound() 并停止代码执行,直到声音完全播放完或直到我从函数返回(我使用延迟函数延迟返回)。

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

函数如下:

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();
}

最佳答案

您的延迟正在阻止您的代码执行,50 毫秒的延迟几乎是每帧 33 毫秒时的 2 帧或每帧 16 毫秒时的 3 帧,在这里丢帧可能没有问题,但您可以看到如何调用连续发出几个声音会使您的程序变慢。

这就是我使用 SDL2_mixer 在我的引擎中播放声音的方式,(短声音,对于音乐你有另一种方法叫做 Mix_PlayMusic),它可能对你有帮助。我没有延迟(而且我没有在我的代码中使用任何 sleep 或延迟)。一旦你调用 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 函数在不同的 channel 中播放声音,则可以执行此操作,并且play() 函数可以接收您希望播放的 channel 。

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

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

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

关于c++ - SDL - 如何在不停止代码执行的情况下在 C++ 中异步播放音频?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50240497/

相关文章:

spring - Spring 中的 Hibernate Multi-Tenancy 和异步执行(通过 @Async、@Scheduled 等)

c++ - 类型分组 模板的显式实例化

c++ - 为什么转换不起作用?

android - 创建线程以异步下载 xml 以在 UI 元素中使用

audio - 使用mplayer从音频流中提取冰冷的元数据(连续)

java - 旧曲结束后如何自动开始新曲

iphone - iOS:Audio Unit RemoteIO 在 iPhone 上不工作

C++ 删除队列中的节点

C++ 非静态数据成员初始化器,只是有点困惑

asynchronous - 为什么 Async.StartChild 返回 `Async<Async<' T>>`?