【问题标题】:SDL - How to play audio asynchronously in C++ without stopping code execution?SDL - 如何在不停止代码执行的情况下在 C++ 中异步播放音频?
【发布时间】:2018-05-08 18:53:32
【问题描述】:

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

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

我想做的是让声音在后台播放,而不会为游戏的其余部分造成任何延迟。我正在 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();
}

【问题讨论】:

  • 我建议选择一个合适的音频库,例如开放式。 SDL 音频接口相当欠缺。
  • 我不明白这个问题,很明显你已经知道延迟是你的障碍。您是否认为您需要在每次播放声音时初始化 SDL、打开音频设备、关闭它并取消初始化 SDL?您是否将 SDL 用于其他事情或仅用于音频(因为 SDL_Quit 在这里破坏了一切)?您需要并行播放多个声音吗?消除此操作的延迟并稍后执行清理似乎有什么问题?
  • 是的,我确实需要并行播放多个声音。我第一次使用 SDL,但并不知道我可以随时退出 SDL。我想我必须在我遵循的教程中告诉我之后不久就这样做。原谅我的无知。顺便说一句,我找到了一个使用 SDL_Mixer 的lazyfoo 教程,并且能够做我想做的事。无论如何,感谢您的帮助,我确实了解您的要点。如果您可以将其作为其他用户的答案,我将很乐意接受。

标签: c++ asynchronous audio sdl-2


【解决方案1】:

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

这就是我使用 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 将返回错误代码。

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

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

【讨论】:

  • 非常感谢阿拉姆。 10/10 进行解释。祝你好运。
  • 我遇到了 SDL 问题。正如您所说“如果您多次播放相同的声音,它开始变得模糊”。这是为什么?我们不应该多次播放相同的声音吗?
  • @MatiasBarrios 对不起,我的措辞很糟糕。我的意思是,如果你快速连续播放相同的声音(重叠)。
  • 这并不能解决问题...如果在游戏中使用 SDL2 或上面的 gui 正常工作并且您听到正在播放的音频,但是您的程序直接作为独立可执行文件执行以呈现音频您必须添加某种睡眠才能听到音频......就像程序在等待足够长的时间播放音频之前退出一样
  • @ScottStensland 这解决了 OP 问题,因为他在渲染的同一线程中使用睡眠,它可能无法解决所有音频 SDL2 问题,但这会引发不同的 SO 问题。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    C++ 中有几个用于异步操作的工具。你可以试试most simplestd::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,这意味着将使用新线程。然后需要执行可调用的名称及其参数。不要忘记包含&lt;future&gt; 标头。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-26
      • 1970-01-01
      • 1970-01-01
      • 2014-09-20
      • 1970-01-01
      • 2020-06-06
      相关资源
      最近更新 更多