SDL - 如何在不停止代码执行的情况下在 C++ 中异步播放音频?
SDL - How to play audio asynchronously in C++ without stopping code execution?
我正在用纯 C++ 开发 Asteroid 的克隆,为此,我需要为不同的事件添加声音,例如子弹发射和爆炸发生时。然而,问题是我对音频库没有任何经验。
我正在使用简单的 DirectMedia 层 (SDL) 并编写了一个名为 playsound() 的函数来在特定事件发生时播放声音。然而,问题是如果发生事件,将调用 playsound() 并且代码执行停止,直到声音完全播放完或直到我 return 从函数(我延迟 return 使用延迟功能)。
我想要做的是在后台播放声音,而不会对游戏的其余部分造成任何延迟。我在 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();
}
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
,这意味着将使用新线程。然后需要执行的可调用名称及其参数。不要忘记包括 <future>
header.
您想在初始化游戏时加载所有必要的资产。然后,当你想玩它们时,它们会加载到游戏内存中,不会有任何延迟。也可能在单独的线程中播放声音,因此它不会阻塞您的主线程。
您的延迟正在阻止您的代码执行,50 毫秒的延迟几乎是每帧 33 毫秒时的 2 帧或每帧 16 毫秒时的 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()函数可以接收你想要播放的频道。
所有这些功能 return 错误值,例如,如果没有未保留的频道可用 Mix_PlayChannel 将 return 一个错误代码。
你要记住的另一件事是,如果你多次播放相同的声音,它会开始变得 blurry/you 如果再次播放相同的声音,你将不会注意到。所以你可以给sample加上一个整数来计算一个sample可以播放多少次。
如果您真的想从主 SDL 线程中线程化您的 mixer/audio(并且仍然只使用 SDL),您可以在线程中生成一个新的 SDL 上下文并以某种方式发送信号来播放音频.
我正在用纯 C++ 开发 Asteroid 的克隆,为此,我需要为不同的事件添加声音,例如子弹发射和爆炸发生时。然而,问题是我对音频库没有任何经验。
我正在使用简单的 DirectMedia 层 (SDL) 并编写了一个名为 playsound() 的函数来在特定事件发生时播放声音。然而,问题是如果发生事件,将调用 playsound() 并且代码执行停止,直到声音完全播放完或直到我 return 从函数(我延迟 return 使用延迟功能)。
我想要做的是在后台播放声音,而不会对游戏的其余部分造成任何延迟。我在 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();
}
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
,这意味着将使用新线程。然后需要执行的可调用名称及其参数。不要忘记包括 <future>
header.
您想在初始化游戏时加载所有必要的资产。然后,当你想玩它们时,它们会加载到游戏内存中,不会有任何延迟。也可能在单独的线程中播放声音,因此它不会阻塞您的主线程。
您的延迟正在阻止您的代码执行,50 毫秒的延迟几乎是每帧 33 毫秒时的 2 帧或每帧 16 毫秒时的 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()函数可以接收你想要播放的频道。
所有这些功能 return 错误值,例如,如果没有未保留的频道可用 Mix_PlayChannel 将 return 一个错误代码。
你要记住的另一件事是,如果你多次播放相同的声音,它会开始变得 blurry/you 如果再次播放相同的声音,你将不会注意到。所以你可以给sample加上一个整数来计算一个sample可以播放多少次。
如果您真的想从主 SDL 线程中线程化您的 mixer/audio(并且仍然只使用 SDL),您可以在线程中生成一个新的 SDL 上下文并以某种方式发送信号来播放音频.