bsnes/ruby/audio/alsa.cpp

241 lines
6.0 KiB
C++
Raw Normal View History

//audio.alsa (2009-11-30)
//authors: BearOso, byuu, Nach, RedDwarf
#include <alsa/asoundlib.h>
namespace ruby {
class pAudioALSA {
public:
struct {
snd_pcm_t* handle;
snd_pcm_format_t format;
snd_pcm_uframes_t buffer_size;
snd_pcm_uframes_t period_size;
int channels;
const char* name;
} device;
struct {
uint32_t* data;
unsigned length;
} buffer;
struct {
bool synchronize;
unsigned frequency;
unsigned latency;
} settings;
bool cap(const string& name) {
if(name == Audio::Synchronize) return true;
if(name == Audio::Frequency) return true;
if(name == Audio::Latency) return true;
return false;
}
any get(const string& name) {
if(name == Audio::Synchronize) return settings.synchronize;
if(name == Audio::Frequency) return settings.frequency;
if(name == Audio::Latency) return settings.latency;
return false;
}
bool set(const string& name, const any& value) {
if(name == Audio::Synchronize) {
if(settings.synchronize != any_cast<bool>(value)) {
settings.synchronize = any_cast<bool>(value);
if(device.handle) init();
}
return true;
}
if(name == Audio::Frequency) {
if(settings.frequency != any_cast<unsigned>(value)) {
settings.frequency = any_cast<unsigned>(value);
if(device.handle) init();
}
return true;
}
if(name == Audio::Latency) {
if(settings.latency != any_cast<unsigned>(value)) {
settings.latency = any_cast<unsigned>(value);
if(device.handle) init();
}
return true;
}
return false;
}
void sample(uint16_t left, uint16_t right) {
if(!device.handle) return;
buffer.data[buffer.length++] = left + (right << 16);
if(buffer.length < device.period_size) return;
snd_pcm_sframes_t avail;
do {
avail = snd_pcm_avail_update(device.handle);
if(avail < 0) snd_pcm_recover(device.handle, avail, 1);
if(avail < buffer.length) {
if(settings.synchronize == false) {
buffer.length = 0;
return;
}
int error = snd_pcm_wait(device.handle, -1);
if(error < 0) snd_pcm_recover(device.handle, error, 1);
}
} while(avail < buffer.length);
//below code has issues with PulseAudio sound server
#if 0
if(settings.synchronize == false) {
snd_pcm_sframes_t avail = snd_pcm_avail_update(device.handle);
if(avail < device.period_size) {
buffer.length = 0;
return;
}
}
#endif
uint32_t* buffer_ptr = buffer.data;
int i = 4;
while((buffer.length > 0) && i--) {
snd_pcm_sframes_t written = snd_pcm_writei(device.handle, buffer_ptr, buffer.length);
if(written < 0) {
//no samples written
snd_pcm_recover(device.handle, written, 1);
} else if(written <= buffer.length) {
buffer.length -= written;
buffer_ptr += written;
}
}
if(i < 0) {
if(buffer.data == buffer_ptr) {
buffer.length--;
buffer_ptr++;
}
memmove(buffer.data, buffer_ptr, buffer.length * sizeof(uint32_t));
}
}
void clear() {
}
bool init() {
term();
if(snd_pcm_open(&device.handle, device.name, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK) < 0) {
term();
return false;
}
//below code will not work with 24khz frequency rate (ALSA library bug)
#if 0
if(snd_pcm_set_params(device.handle, device.format, SND_PCM_ACCESS_RW_INTERLEAVED,
device.channels, settings.frequency, 1, settings.latency * 1000) < 0) {
//failed to set device parameters
term();
return false;
}
if(snd_pcm_get_params(device.handle, &device.buffer_size, &device.period_size) < 0) {
device.period_size = settings.latency * 1000 * 1e-6 * settings.frequency / 4;
}
#endif
snd_pcm_hw_params_t* hwparams;
snd_pcm_sw_params_t* swparams;
unsigned rate = settings.frequency;
unsigned buffer_time = settings.latency * 1000;
unsigned period_time = settings.latency * 1000 / 4;
snd_pcm_hw_params_alloca(&hwparams);
if(snd_pcm_hw_params_any(device.handle, hwparams) < 0) {
term();
return false;
}
if(snd_pcm_hw_params_set_access(device.handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0
|| snd_pcm_hw_params_set_format(device.handle, hwparams, device.format) < 0
|| snd_pcm_hw_params_set_channels(device.handle, hwparams, device.channels) < 0
|| snd_pcm_hw_params_set_rate_near(device.handle, hwparams, &rate, 0) < 0
|| snd_pcm_hw_params_set_period_time_near(device.handle, hwparams, &period_time, 0) < 0
|| snd_pcm_hw_params_set_buffer_time_near(device.handle, hwparams, &buffer_time, 0) < 0
) {
term();
return false;
}
if(snd_pcm_hw_params(device.handle, hwparams) < 0) {
term();
return false;
}
if(snd_pcm_get_params(device.handle, &device.buffer_size, &device.period_size) < 0) {
term();
return false;
}
snd_pcm_sw_params_alloca(&swparams);
if(snd_pcm_sw_params_current(device.handle, swparams) < 0) {
term();
return false;
}
if(snd_pcm_sw_params_set_start_threshold(device.handle, swparams,
(device.buffer_size / device.period_size) * device.period_size) < 0
) {
term();
return false;
}
if(snd_pcm_sw_params(device.handle, swparams) < 0) {
term();
return false;
}
buffer.data = new uint32_t[device.period_size];
return true;
}
void term() {
if(device.handle) {
2011-11-17 12:05:35 +00:00
//snd_pcm_drain(device.handle); //prevents popping noise; but causes multi-second lag
snd_pcm_close(device.handle);
device.handle = 0;
}
if(buffer.data) {
delete[] buffer.data;
buffer.data = 0;
}
}
pAudioALSA() {
device.handle = 0;
device.format = SND_PCM_FORMAT_S16_LE;
device.channels = 2;
device.name = "default";
buffer.data = 0;
buffer.length = 0;
settings.synchronize = false;
settings.frequency = 22050;
settings.latency = 60;
}
~pAudioALSA() {
term();
}
};
DeclareAudio(ALSA)
};