Преглед изворни кода

audio: refactor audio streaming mechanism.

Rename “channels” to “tracks” when talking about the mixing channels, to
avoid confusion with channels in the context of mono/stereo/5.1. Also allow
conversions between various formats and frequencies.

To do: allow to query the current audio device’s preferred frequency when
the client application is able to send the best possible data.
legacy
Sam Hocevar пре 5 година
родитељ
комит
68c2530b16
3 измењених фајлова са 151 додато и 56 уклоњено
  1. +6
    -8
      doc/tutorial/09_sound.cpp
  2. +123
    -43
      src/audio/audio.cpp
  3. +22
    -5
      src/lol/audio/audio.h

+ 6
- 8
doc/tutorial/09_sound.cpp Прегледај датотеку

@@ -31,7 +31,7 @@ public:
auto f = std::bind(&sound_demo::synth, this, i,
std::placeholders::_1,
std::placeholders::_2);
m_streams[i] = audio::start_streaming(f);
m_streams[i] = audio::start_streaming(f, audio::format::int16le, 22050, 1);
}

for (size_t i = 0; i < m_instrument.size(); ++i)
@@ -54,20 +54,18 @@ public:
int mode = (1 << channel) & m_mask;

int16_t *stream = (int16_t *)buf;
for (int i = 0; i < bytes / 2; i += 2)
for (int i = 0; i < bytes / 2; ++i)
{
switch (mode)
{
case 2: // square / triangle signals
stream[i] = 800 * (i % 128 > 64 ? -1 : 1);
stream[i + 1] = (i % 128 - 64) * 15;
case 2: // triangle signal
stream[i] = (i % 128 - 64) * 8;
break;
case 1: // white noise
stream[i] = lol::rand(-2000, 2000);
stream[i + 1] = lol::rand(-1000, 1000);
stream[i] = lol::rand(-2048, 2048);
break;
case 0: // inactive
stream[i] = stream[i + 1] = 0;
stream[i] = 0;
break;
}
}


+ 123
- 43
src/audio/audio.cpp Прегледај датотеку

@@ -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 channels)
void audio::set_tracks(int tracks)
{
Mix_AllocateChannels(channels);
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_channels(int) {}
void audio::set_tracks(int) {}
void audio::set_volume(int, int) {}
void audio::mute_all() {}
void audio::unmute_all() {}


+ 22
- 5
src/lol/audio/audio.h Прегледај датотеку

@@ -1,7 +1,7 @@
//
// Lol Engine
//
// Copyright © 2010—2016 Sam Hocevar <sam@hocevar.net>
// Copyright © 2010—2019 Sam Hocevar <sam@hocevar.net>
//
// Lol Engine is free software. It comes without any warranty, to
// the extent permitted by applicable law. You can redistribute it
@@ -27,15 +27,32 @@ namespace lol
class audio
{
public:
enum class format : uint8_t
{
unknown = 0,
uint8,
int8,
uint16le, uint16be,
int16le, int16be,
int32le, int32be,
float32le, float32be,
};

static void init();

static void set_channels(int channels);
static void set_volume(int channel,int volume);
// Set the number of audio tracks that can be mixed together
static void set_tracks(int tracks);
// Set the volume of a specific track
static void set_volume(int track, int volume);
static void mute_all();
static void unmute_all();

static int start_streaming(std::function<void(void *, int)> const &f);
static void stop_streaming(int channel);
static int start_streaming(std::function<void(void *, int)> const &f,
format format = audio::format::uint16le,
int frequency = 22050,
int channels = 2);

static void stop_streaming(int track);

private:
audio() {}


Loading…
Откажи
Сачувај