@@ -30,24 +30,75 @@
// “[…] refers to the size of the audio buffer in sample frames. A sample frame
// is a chunk of audio data of the size specified in format multiplied by the
// number of channels.”
#define LOL_AUDIO_SAMPLE_FRAMES 1024
#define LOL_AUDIO_CHANNELS 2
#define LOL_AUDIO_DEFAULT_FRAMES 1024
#define LOL_AUDIO_DEFAULT_TRACKS 8
#define LOL_AUDIO_DEFAULT_CHANNELS 2
#define LOL_AUDIO_DEFAULT_FORMAT AUDIO_S16
#define LOL_AUDIO_DEFAULT_RATE 22050
namespace lol
{
struct audio_streamer
{
int m_channel ;
int m_track = -1 ;
std::function<void(void *, int)> m_callback;
std::array<uint16_t, LOL_AUDIO_CHANNELS * LOL_AUDIO_SAMPLE_FRAMES> m_empty; // SDL needs a reference to this
// Buffer where the streaming client will write, and where the SDL
// audio conversion may happen.
std::vector<uint8_t> m_convert_buffer;
// Remaining bytes in the conversion buffer.
int m_extra_bytes = 0;
// Buffer used to write the converted audio data for mixing.
std::vector<uint8_t> m_output_buffer;
#if defined LOL_USE_SDL_MIXER
Mix_Chunk *m_chunk;
SDL_AudioCVT m_convert;
Mix_Chunk *m_chunk = nullptr;
#endif
};
std::unordered_set<std::shared_ptr<audio_streamer>> audio::m_streamers;
// The global audio format
static int g_frequency, g_channels;
static audio::format g_format;
static audio::format sdl2lol_format(Uint16 sdl_format)
{
switch (sdl_format)
{
case AUDIO_U8: return audio::format::uint8;
case AUDIO_S8: return audio::format::int8;
case AUDIO_U16LSB: return audio::format::uint16le;
case AUDIO_U16MSB: return audio::format::uint16be;
case AUDIO_S16LSB: return audio::format::int16le;
case AUDIO_S16MSB: return audio::format::int16be;
case AUDIO_S32LSB: return audio::format::int32le;
case AUDIO_S32MSB: return audio::format::int32be;
case AUDIO_F32LSB: return audio::format::float32le;
case AUDIO_F32MSB: return audio::format::float32be;
default: return audio::format::unknown;
}
}
static int lol2sdl_format(audio::format format)
{
switch (format)
{
case audio::format::uint8: return AUDIO_U8;
case audio::format::int8: return AUDIO_S8;
case audio::format::uint16le: return AUDIO_U16LSB;
case audio::format::uint16be: return AUDIO_U16MSB;
case audio::format::int16le: return AUDIO_S16LSB;
case audio::format::int16be: return AUDIO_S16MSB;
case audio::format::int32le: return AUDIO_S32LSB;
case audio::format::int32be: return AUDIO_S32MSB;
case audio::format::float32le: return AUDIO_F32LSB;
case audio::format::float32be: return AUDIO_F32MSB;
default: return 0;
}
}
/*
* Public audio class
*/
@@ -55,48 +106,40 @@ std::unordered_set<std::shared_ptr<audio_streamer>> audio::m_streamers;
#if defined LOL_USE_SDL_MIXER
void audio::init()
{
if (Mix_OpenAudio(22050, AUDIO_S16, LOL_AUDIO_CHANNELS, LOL_AUDIO_SAMPLE _FRAMES) < 0)
if (Mix_OpenAudio(LOL_AUDIO_DEFAULT_RATE, LOL_AUDIO_DEFAULT_FORMAT, LOL_AUDIO_DEFAULT_CHANNELS, LOL_AUDIO_DEFAULT _FRAMES) < 0)
{
msg::error("error opening audio: %s\n", Mix_GetError());
return;
}
set_channels(8 );
set_tracks(LOL_AUDIO_DEFAULT_TRACKS );
int frequency, channels;
Uint16 format;
if (Mix_QuerySpec(&frequency, &format, &channels) == 0)
Uint16 sdl_format;
if (Mix_QuerySpec(&g_frequency, &sdl_format, &g_channels) == 0)
{
msg::error("error querying audio: %s\n", Mix_GetError());
return;
}
char const *sformat = "unknown";
switch (format)
{
case AUDIO_U8: sformat = "uint8"; break;
case AUDIO_S8: sformat = "int8"; break;
case AUDIO_U16LSB: sformat = "uint16le"; break;
case AUDIO_U16MSB: sformat = "uint16be"; break;
case AUDIO_S16LSB: sformat = "int16le"; break;
case AUDIO_S16MSB: sformat = "int16be"; break;
case AUDIO_S32LSB: sformat = "int32le"; break;
case AUDIO_S32MSB: sformat = "int32be"; break;
case AUDIO_F32LSB: sformat = "float32le"; break;
case AUDIO_F32MSB: sformat = "float32be"; break;
}
msg::info("audio freq=%dHz format=%s channels=%d\n",
frequency, sformat, channels);
g_format = sdl2lol_format(sdl_format);
char const *u = (SDL_AUDIO_ISFLOAT(sdl_format) || SDL_AUDIO_ISSIGNED(sdl_format)) ? "" : "u";
char const *t = SDL_AUDIO_ISFLOAT(sdl_format) ? "float" : "int";
int b = SDL_AUDIO_BITSIZE(sdl_format);
char const *e = b <= 8 ? "" : SDL_AUDIO_ISLITTLEENDIAN(sdl_format) ? "le" : "be";
msg::info("audio initialised: freq=%dHz format=%s%s%d%s channels=%d\n",
g_frequency, u, t, b, e, g_channels);
}
void audio::set_channels(int channel s)
void audio::set_tracks(int tracks)
{
Mix_AllocateChannels(channel s);
Mix_AllocateChannels(tracks);
}
void audio::set_volume(int channel , int volume)
void audio::set_volume(int track , int volume)
{
Mix_Volume(channel , volume);
Mix_Volume(track , volume);
}
void audio::mute_all()
@@ -109,35 +152,72 @@ void audio::unmute_all()
Mix_Volume(-1, MIX_MAX_VOLUME);
}
int audio::start_streaming(std::function<void(void *, int)> const &f)
int audio::start_streaming(std::function<void(void *, int)> const &f,
enum audio::format format /* = audio::format::uint16le */,
int frequency /* = 22050 */,
int channels /* = 2 */)
{
static auto trampoline = [](int, void *stream, int bytes, void *udata)
{
auto s = (audio_streamer *)udata;
s->m_callback(stream, bytes);
// If there were still bytes from a previous conversion, copy them.
if (s->m_extra_bytes)
{
int tocopy = lol::min(bytes, s->m_extra_bytes);
memcpy(stream, s->m_convert.buf + s->m_convert.len_cvt - s->m_extra_bytes, tocopy);
s->m_extra_bytes -= tocopy;
bytes -= tocopy;
stream = (void *)((uint8_t *)stream + tocopy);
}
// Ask the callback for more bytes as long as we need them.
while (bytes > 0)
{
s->m_callback(s->m_convert.buf, s->m_convert.len);
if (s->m_convert.needed)
SDL_ConvertAudio(&s->m_convert);
int tocopy = lol::min(bytes, s->m_convert.len_cvt);
s->m_extra_bytes = s->m_convert.len_cvt - tocopy;
memcpy(stream, s->m_convert.buf, tocopy);
bytes -= tocopy;
stream = (void *)((uint8_t *)stream + tocopy);
}
};
auto s = std::make_shared<audio_streamer>();
m_streamers.insert(s);
Uint8* audio_data = (Uint8*)s->m_empty.data();
Uint32 audio_size = (Uint32)(s->m_empty.size() * sizeof(s->m_empty[0]));
memset(audio_data, 17, audio_size);
s->m_chunk = Mix_QuickLoad_RAW(audio_data, audio_size);
s->m_channel = Mix_PlayChannel(-1, s->m_chunk, -1);
// Build an audio converter that converts from a given streaming format
// to the format SDL was currently initialised with, if necessary.
Uint16 sdl_format = lol2sdl_format(format);
Uint16 sdl_g_format = lol2sdl_format(g_format);
SDL_BuildAudioCVT(&s->m_convert, sdl_format, channels, frequency, sdl_g_format, g_channels, g_frequency);
// This is how many bytes we will ask the streaming callback
s->m_convert.len = LOL_AUDIO_DEFAULT_FRAMES * channels * SDL_AUDIO_BITSIZE(sdl_format) / 8;
s->m_convert_buffer.resize(s->m_convert.len * s->m_convert.len_mult);
s->m_convert.buf = s->m_convert_buffer.data();
// This is how many bytes we will send to the SDL mixer
Uint32 output_bytes = LOL_AUDIO_DEFAULT_FRAMES * g_channels * SDL_AUDIO_BITSIZE(sdl_g_format) / 8;
s->m_output_buffer.resize(output_bytes);
Uint8* audio_data = (Uint8*)s->m_output_buffer.data();
s->m_chunk = Mix_QuickLoad_RAW(audio_data, output_bytes);
s->m_track = Mix_PlayChannel(-1, s->m_chunk, -1);
s->m_callback = f;
Mix_RegisterEffect(s->m_channel, trampoline, nullptr, s.get());
Mix_RegisterEffect(s->m_track , trampoline, nullptr, s.get());
return s->m_channel;
return s->m_track ;
}
void audio::stop_streaming(int stream)
void audio::stop_streaming(int track )
{
for (auto streamer : m_streamers)
{
if (streamer->m_channel == stream)
if (streamer->m_track == track )
{
Mix_HaltChannel(stream );
Mix_HaltChannel(track );
m_streamers.erase(streamer);
break;
}
@@ -146,7 +226,7 @@ void audio::stop_streaming(int stream)
#else
void audio::init() {}
void audio::set_channel s(int) {}
void audio::set_track s(int) {}
void audio::set_volume(int, int) {}
void audio::mute_all() {}
void audio::unmute_all() {}