citra/src/core/audio/stream.cpp

186 lines
5.1 KiB
C++

#include "AL/al.h"
#include "AL/alc.h"
#include "AL/alext.h"
#include "common/logging/log.h"
#include "core/audio/stream.h"
#include <array>
#include <queue>
namespace Audio {
static const int BASE_SAMPLE_RATE = 22050;
struct Buffer {
u16 id;
ALuint buffer;
bool is_looping;
bool operator < (const Buffer& other) const {
if ((other.id - id) > 1000) return true;
if ((id - other.id) > 1000) return false;
return id > other.id;
}
};
struct OutputChannel {
ALuint source;
int mono_or_stereo;
Format format;
std::priority_queue<Buffer> queue;
std::queue<Buffer> playing;
u16 last_bufid;
};
OutputChannel chans[24];
int InitAL(void)
{
ALCdevice *device;
ALCcontext *ctx;
/* Open and initialize a device with default settings */
device = alcOpenDevice(NULL);
if (!device)
{
LOG_CRITICAL(Audio, "Could not open a device!");
return 1;
}
ctx = alcCreateContext(device, NULL);
if (ctx == NULL || alcMakeContextCurrent(ctx) == ALC_FALSE)
{
if (ctx != NULL)
alcDestroyContext(ctx);
alcCloseDevice(device);
LOG_CRITICAL(Audio, "Could not set a context!");
return 1;
}
LOG_INFO(Audio, "Opened \"%s\"", alcGetString(device, ALC_DEVICE_SPECIFIER));
return 0;
}
ALuint source, buffer;
ALCint dev_rate;
void Init() {
InitAL();
{
ALCdevice *device = alcGetContextsDevice(alcGetCurrentContext());
alcGetIntegerv(device, ALC_FREQUENCY, 1, &dev_rate);
if (alcGetError(device) != ALC_NO_ERROR) LOG_CRITICAL(Audio, "Failed to get device sample rate");
LOG_INFO(Audio, "Device Frequency: %i", dev_rate);
}
for (int i = 0; i < 24; i++) {
alGenSources(1, &chans[i].source);
if (alGetError() != AL_NO_ERROR) LOG_CRITICAL(Audio, "Failed to setup sound source");
}
}
void Shutdown() {}
void UpdateFormat(int chanid, int mono_or_stereo, Format format) {
chans[chanid].mono_or_stereo = mono_or_stereo;
chans[chanid].format = format;
LOG_WARNING(Audio, "(STUB)");
}
void EnqueueBuffer(int chanid, u16 buffer_id,
void* data, int sample_count,
bool has_adpcm, u16 adpcm_ps, s16 adpcm_yn[2],
bool is_looping) {
if (chans[chanid].format != FORMAT_PCM16) {
LOG_ERROR(Audio, "Unimplemented format");
return;
}
// TODO: ADPCM processing should happen here
ALuint b;
alGenBuffers(1, &b);
alBufferData(b, AL_FORMAT_MONO16, data, sample_count*2, BASE_SAMPLE_RATE);
if (alGetError() != AL_NO_ERROR) LOG_CRITICAL(Audio, "Failed to init buffer");
chans[chanid].queue.emplace( Buffer { buffer_id, b, is_looping });
}
void Tick(int chanid) {
auto& c = chans[chanid];
if (!c.queue.empty()) {
while (!c.queue.empty()) {
alSourceQueueBuffers(c.source, 1, &c.queue.top().buffer);
if (alGetError() != AL_NO_ERROR) LOG_CRITICAL(Audio, "Failed to enqueue buffer");
c.playing.emplace(c.queue.top());
LOG_INFO(Audio, "Enqueued buffer id %i", c.queue.top().id);
c.queue.pop();
}
ALint state;
alGetSourcei(c.source, AL_SOURCE_STATE, &state);
if (state != AL_PLAYING) {
alSourcePlay(c.source);
}
}
if (!c.playing.empty()) {
c.last_bufid = c.playing.front().id;
}
ALint processed;
alGetSourcei(c.source, AL_BUFFERS_PROCESSED, &processed);
while (processed > 0) {
ALuint buf;
alSourceUnqueueBuffers(c.source, 1, &buf);
processed--;
LOG_INFO(Audio, "Finished buffer id %i", c.playing.front().id);
while (!c.playing.empty() && c.playing.front().buffer != buf) {
c.playing.pop();
LOG_ERROR(Audio, "Audio is extremely funky. Should abort. (Desynced queue.)");
}
if (!c.playing.empty()) {
c.last_bufid = c.playing.front().id;
c.playing.pop();
} else {
LOG_ERROR(Audio, "Audio is extremely funky. Should abort. (Empty queue.)");
}
alDeleteBuffers(1, &buf);
}
if (!c.playing.empty()) {
c.last_bufid = c.playing.front().id;
}
}
std::tuple<bool, u16, u32> GetStatus(int chanid) {
auto& c = chans[chanid];
bool isplaying = false;
u16 bufid = 0;
u32 pos = 0;
ALint state, samples;
alGetSourcei(c.source, AL_SOURCE_STATE, &state);
alGetSourcei(c.source, AL_SAMPLE_OFFSET, &samples);
if (state == AL_PLAYING) isplaying = true;
bufid = c.last_bufid;
pos = samples;
return std::make_tuple(isplaying, bufid, pos);
}
};