
With the ALSA backend in bsnes 0.032a the SBLive! crashes and AD1988 has distorsioned sound.
I think the crashes are because
should besnd_pcm_sframes_t written = snd_pcm_writei(device.handle, buffer.data, buffer.length);
since length is in frames and no in samples.snd_pcm_sframes_t written = snd_pcm_writei(device.handle, buffer.data, buffer.length/2);
Also adding a
Code: Select all
snd_output_t *setup;
int err = snd_output_stdio_attach(&setup, stdout, 0);
if (err < 0) {
printf("Ups!\n");
}
snd_pcm_dump_setup(device.handle, setup);
The SB Live! is configured this way:
The AD1988 is configured this way:stream : PLAYBACK
access : RW_INTERLEAVED
format : S16_LE
subformat : STD
channels : 2
rate : 32000
exact rate : 32000 (32000/1)
msbits : 16
buffer_size : 64
period_size : 16
period_time : 500
tstamp_mode : NONE
period_step : 1
avail_min : 16
start_threshold : 64
stop_threshold : 64
silence_threshold: 0
silence_size : 0
boundary : 4611686018427387904
So even if the code asked for a 90us latency, what we have is a 2ms latency for the SB Live! and a 43ms latency for AD1988.stream : PLAYBACK
access : RW_INTERLEAVED
format : S16_LE
subformat : STD
channels : 2
rate : 32000
exact rate : 32000 (32000/1)
msbits : 16
buffer_size : 1365
period_size : 682
period_time : 21333
tstamp_mode : NONE
period_step : 1
avail_min : 682
start_threshold : 1364
stop_threshold : 1365
silence_threshold: 0
silence_size : 0
boundary : 3073706745680363520
Now, was 90us really intended? It's very low, isn't? Since I had problems to make the SB Live! work with a 2ms latency without buffer underruns I have changed "device.latency = 90;" for "device.latency = 90000;". If 90us was really intented (what OSS archieves with policy = 4?) say me.
New configuration is:
- SB Live!:
Exactly 90ms latency, with 4 periods.stream : PLAYBACK
access : RW_INTERLEAVED
format : S16_LE
subformat : STD
channels : 2
rate : 32000
exact rate : 32000 (32000/1)
msbits : 16
buffer_size : 2880
period_size : 720
period_time : 22500
tstamp_mode : NONE
period_step : 1
avail_min : 720
start_threshold : 2880
stop_threshold : 2880
silence_threshold: 0
silence_size : 0
boundary : 6485183463413514240
- AD1988
~85ms latency, with ~4 periods.stream : PLAYBACK
access : RW_INTERLEAVED
format : S16_LE
subformat : STD
channels : 2
rate : 32000
exact rate : 32000 (32000/1)
msbits : 16
buffer_size : 2730
period_size : 682
period_time : 21333
tstamp_mode : NONE
period_step : 1
avail_min : 682
start_threshold : 2728
stop_threshold : 2730
silence_threshold: 0
silence_size : 0
boundary : 3073706745680363520
At the end, I have found that this alternative code works fine for both SB Live! and AD1988, speed regulation included.
Code: Select all
#include <alsa/asoundlib.h>
#include <ruby/ruby.h>
namespace ruby {
#include "alsa.h"
class pAudioALSA {
public:
AudioALSA &self;
struct {
snd_pcm_t *handle;
snd_pcm_format_t format;
int channels;
const char *name;
unsigned latency;
} device;
struct {
uint32_t *data;
unsigned length;
} buffer;
struct {
unsigned frequency;
snd_pcm_uframes_t buffer_size;
snd_pcm_uframes_t period_size;
} settings;
bool cap(Audio::Setting setting) {
if(setting == Audio::Frequency) return true;
return false;
}
uintptr_t get(Audio::Setting setting) {
if(setting == Audio::Frequency) return settings.frequency;
return false;
}
bool set(Audio::Setting setting, uintptr_t param) {
if(setting == Audio::Frequency) {
settings.frequency = param;
if(device.handle) {
term();
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 < settings.period_size) return;
uint32_t *buffer_ptr = buffer.data;
do {
snd_pcm_sframes_t written = snd_pcm_writei(device.handle, buffer_ptr, buffer.length);
if(written < 0) {
snd_pcm_recover(device.handle, written, 1);
} else if(written < buffer.length) {
//only some samples written
buffer.length -= written;
buffer_ptr += written;
} else {
//all samples written
buffer.length = 0;
}
} while(buffer.length != 0);
}
bool init() {
if(snd_pcm_open(&device.handle, device.name, SND_PCM_STREAM_PLAYBACK, 0) < 0) {
//failed to initialize
term();
return false;
}
if(snd_pcm_set_params(device.handle, device.format, SND_PCM_ACCESS_RW_INTERLEAVED,
device.channels, settings.frequency, 1, device.latency) < 0) {
//failed to set device parameters
term();
return false;
}
if(snd_pcm_get_params(device.handle, &settings.buffer_size, &settings.period_size) < 0) {
//I suppose ALSA always uses 4 periods/buffer, and I expect buffer_size to give me the exact latency requested... better than nothing
settings.period_size = device.latency*1e-6*settings.frequency/4;
}
buffer.data = new uint32_t[settings.period_size];
return true;
}
void term() {
if(device.handle) {
snd_pcm_drain(device.handle);
snd_pcm_close(device.handle);
device.handle = 0;
}
if(buffer.data) {
delete[] buffer.data;
buffer.data = 0;
}
}
pAudioALSA(AudioALSA &self_) : self(self_) {
device.handle = 0;
device.format = SND_PCM_FORMAT_S16_LE;
device.channels = 2;
device.name = "default";
device.latency = 100000;
buffer.data = 0;
buffer.length = 0;
settings.frequency = 32000;
}
~pAudioALSA() {
term();
}
};
bool AudioALSA::cap(Setting setting) { return p.cap(setting); }
uintptr_t AudioALSA::get(Setting setting) { return p.get(setting); }
bool AudioALSA::set(Setting setting, uintptr_t param) { return p.set(setting, param); }
void AudioALSA::sample(uint16_t left, uint16_t right) { p.sample(left, right); }
bool AudioALSA::init() { return p.init(); }
void AudioALSA::term() { p.term(); }
AudioALSA::AudioALSA() : p(*new pAudioALSA(*this)) {}
AudioALSA::~AudioALSA() { delete &p; }
} //namespace ruby
Code: Select all
--- src/lib/ruby/audio/alsa.cpp
+++ src/lib/ruby/audio/alsa.cpp
@@ -19,13 +19,14 @@
} device;
struct {
- uint16_t *data;
+ uint32_t *data;
unsigned length;
- unsigned size;
} buffer;
struct {
unsigned frequency;
+ snd_pcm_uframes_t buffer_size;
+ snd_pcm_uframes_t period_size;
} settings;
bool cap(Audio::Setting setting) {
@@ -53,30 +54,26 @@
void sample(uint16_t left, uint16_t right) {
if(!device.handle) return;
- buffer.data[buffer.length++] = left;
- buffer.data[buffer.length++] = right;
- if(buffer.length + 2 < buffer.size) return; //will crash in some cases if not stopped two before
-
- snd_pcm_sframes_t written = snd_pcm_writei(device.handle, buffer.data, buffer.length);
- if(written < 0) {
- snd_pcm_recover(device.handle, written, 1);
- //no samples written, drop one sample to prevent possible emulation stall
- buffer.length -= 2;
- memmove(buffer.data, buffer.data + 2, buffer.length * sizeof(uint16_t));
- } else if(written < buffer.length) {
- //only some samples written
- buffer.length -= written;
- memmove(buffer.data, buffer.data + written, buffer.length * sizeof(uint16_t));
- } else {
- //all samples written
- buffer.length = 0;
- }
+ buffer.data[buffer.length++] = left + (right << 16);
+ if(buffer.length < settings.period_size) return;
+ uint32_t *buffer_ptr = buffer.data;
+ do {
+ snd_pcm_sframes_t written = snd_pcm_writei(device.handle, buffer_ptr, buffer.length);
+ if(written < 0) {
+ snd_pcm_recover(device.handle, written, 1);
+ } else if(written < buffer.length) {
+ //only some samples written
+ buffer.length -= written;
+ buffer_ptr += written;
+ } else {
+ //all samples written
+ buffer.length = 0;
+ }
+ } while(buffer.length != 0);
}
bool init() {
- buffer.data = new uint16_t[buffer.size];
-
- if(snd_pcm_open(&device.handle, device.name, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK) < 0) {
+ if(snd_pcm_open(&device.handle, device.name, SND_PCM_STREAM_PLAYBACK, 0) < 0) {
//failed to initialize
term();
return false;
@@ -89,6 +86,13 @@
return false;
}
+ if(snd_pcm_get_params(device.handle, &settings.buffer_size, &settings.period_size) < 0) {
+ //I suppose ALSA always uses 4 periods/buffer, and I expect buffer_size to give me the exact latency requested... better than nothing
+ settings.period_size = device.latency*1e-6*settings.frequency/4;
+ }
+
+ buffer.data = new uint32_t[settings.period_size];
+
return true;
}
@@ -110,13 +114,12 @@
device.format = SND_PCM_FORMAT_S16_LE;
device.channels = 2;
device.name = "default";
- device.latency = 90;
+ device.latency = 100000;
buffer.data = 0;
buffer.length = 0;
- buffer.size = device.latency * 32;
- settings.frequency = 22050;
+ settings.frequency = 32000;
}
~pAudioALSA() {
If someone else also has problems try this alternative and say me...
Edit: Sorry for the error in the code. Corrected.
Edit2: Forget the blocking thing.
Edit3: Changed latency value to 100ms and readded application audio buffer to reduce CPU usage.
At this point I think the code is so good like it can be. Application buffer size and latency are the only two numbers that could be tweaked. So I open the poll.