Improving the ALSA backend

Archived bsnes development news, feature requests and bug reports. Forum is now located at http://board.byuu.org/
Locked

Which ALSA code works better?

This one
3
60%
Original bsnes 0.032a one
2
40%
 
Total votes: 5

RedDwarf
Rookie
Posts: 37
Joined: Thu Jan 27, 2005 7:28 pm

Improving the ALSA backend

Post by RedDwarf »

I have an openSUSE 10.3 x86-64, Core2Duo E6420, ASUS P5B system with two sound cards: a Sound Blaster Live! and a AD1988 integrated codec. ALSA drivers are "1.0.16.20080601" and alsa-lib is "1.0.16.hg20080510" (your learn to love the openSUSE Build Service ;-) ).
With the ALSA backend in bsnes 0.032a the SBLive! crashes and AD1988 has distorsioned sound.
I think the crashes are because
snd_pcm_sframes_t written = snd_pcm_writei(device.handle, buffer.data, buffer.length);
should be
snd_pcm_sframes_t written = snd_pcm_writei(device.handle, buffer.data, buffer.length/2);
since length is in frames and no in samples.

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);
to the end of the init() function to see exactly how the sound card has been configured.

The SB Live! 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
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 : 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
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.
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!:
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
Exactly 90ms latency, with 4 periods.

- AD1988
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
~85ms latency, with ~4 periods.


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
Patch:

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() {
Also would be good to add an option to select the device. With SB Live! I have no problems using HQ2x and so, but since the AD1988 doesn't makes hardware mixing the CPU usage is too high when using it with HQ2x. Using "plughw" insted of "default" the CPU usage with AD1988 is lower (even if I loss sound in the rest of the system).

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.
Last edited by RedDwarf on Sun Jun 01, 2008 11:54 am, edited 10 times in total.
grinvader
ZSNES Shake Shake Prinny
Posts: 5632
Joined: Wed Jul 28, 2004 4:15 pm
Location: PAL50, dood !

Post by grinvader »

90ms is a whoppy 5.4 frames. Have fun getting completely off-sync sound effects.
皆黙って俺について来い!!

Code: Select all

<jmr> bsnes has the most accurate wiki page but it takes forever to load (or something)
Pantheon: Gideon Zhi | CaitSith2 | Nach | kode54
RedDwarf
Rookie
Posts: 37
Joined: Thu Jan 27, 2005 7:28 pm

Post by RedDwarf »

I just selected it in case 90us was a "us" vs "ms" mistake and 90ms was selected for a good motive. So which one is the "best" value?
100ms in 6 NTSC frames and 5 PAL frames, too much/noticable? 50 ms is 3 NTSC frames and 2,5 PAL frames... 2,5 would be also a problem (frames vs fields, I don't really understand how the sync is done)? Less than 16,666 ms starts to be very CPU intensive... and apparently some soundcards can't have so little buffer (driver problem?).
Nach
ZSNES Developer
ZSNES Developer
Posts: 3904
Joined: Tue Jul 27, 2004 10:54 pm
Location: Solar powered park bench
Contact:

Post by Nach »

I'll look this code over later, and test it out, thank you.
May 9 2007 - NSRT 3.4, now with lots of hashing and even more accurate information! Go download it.
_____________
Insane Coding
byuu

Post by byuu »

Well, I was impatient so I tested it out myself. I have Intel HDA, and this patch helped tremendously. Speed regulation works fine now and latency is very low.

Only problem I saw was that setting "emulation speed" to 75% spits out errors onto the console about invalid format parameters or somesuch. Probably because 24000hz isn't a standard sampling rate. Hopefully there's a way around that besides using 22050hz, as all the other drivers can pull it off.

Still also need to add support for disabling speed regulation entirely (Audio::Synchronize -> false.)

I really appreciate the patches to ALSA and libao, thanks again.
RedDwarf
Rookie
Posts: 37
Joined: Thu Jan 27, 2005 7:28 pm

Post by RedDwarf »

byuu wrote:Only problem I saw was that setting "emulation speed" to 75% spits out errors onto the console about invalid format parameters or somesuch. Probably because 24000hz isn't a standard sampling rate. Hopefully there's a way around that besides using 22050hz, as all the other drivers can pull it off.
It also happens to me with the AD1988 (also hda-intel). It is weird, the '1' from
snd_pcm_set_params(device.handle, device.format, SND_PCM_ACCESS_RW_INTERLEAVED, device.channels, settings.frequency, 1, device.latency);
means
soft_resample 0 = disallow alsa-lib resample stream, 1 = allow resampling
I will look into it, but looks like an alsa-lib problem (that probably would affect *any* card that doesn't has native support for 24KHz). snd_pcm_set_params() is a new wrapper funcion to make the init process easier, if libao/openal hadn't the problem is because they use the old functions.
To fix it with old/actual alsa versions snd_pcm_set_params() would need to be changed for the five or six functions needed previously to set the sound card... it can be copied&pasted from libao or openal. With some look, if I find the exact problem and report it to alsa devs, ALSA 1.0.17 will work with the code like it is right now.


Edit: Problem reported to ALSA devs and alternative init() function that allows 24KHz with hda-intel and actual alsa version:

Code: Select all

  bool init() {
    if(snd_pcm_open(&device.handle, device.name, SND_PCM_STREAM_PLAYBACK, 0) < 0) {
      //failed to initialize
      term();
      return false;
    }

    snd_pcm_hw_params_t *hwparams;
    snd_pcm_sw_params_t *swparams;
    unsigned rate = settings.frequency;
    unsigned buffer_time = device.latency;
    unsigned int period_time = device.latency/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, &settings.buffer_size, &settings.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,
       (settings.buffer_size / settings.period_size) * settings.period_size) < 0) {
      term();
      return false;
    }
    if(snd_pcm_sw_params(device.handle, swparams) < 0) {
      term();
      return false;
    }

    buffer.data = new uint32_t[settings.period_size];

    return true;
  }
Note that snd_pcm_set_params() has a more complex logic than this code... and I suppose that complexity isn't there only to add bugs ;-)
RedDwarf
Rookie
Posts: 37
Joined: Thu Jan 27, 2005 7:28 pm

Post by RedDwarf »

The one that voted for the original code could give some info?
Using the snd_pcm_dump_setup() code from the first post and posting the output could help.
Deathlike2
ZSNES Developer
ZSNES Developer
Posts: 6747
Joined: Tue Dec 28, 2004 6:47 am

Post by Deathlike2 »

Even though this isn't a relatively scientific poll, but if the discrepency between the codes exist, then it makes one wonder how inconsistant the behavior is for ALSA, not necessarily of the hardware being run...
Continuing [url=http://slickproductions.org/forum/index.php?board=13.0]FF4[/url] Research...
RedDwarf
Rookie
Posts: 37
Joined: Thu Jan 27, 2005 7:28 pm

Post by RedDwarf »

I have a problem understanding the "Uncapped" mode. At this mode the play rate is set at 32KHz, the same that at 100%. But if the emulator goes at a speed of 150%/90fps I get 48000 samples each second.
If I receive 48000 samples each second but only play 32000 samples each second... each second I acummulate 16000 samples!!! So what I get at the end is a sound that is behind the rest of the emulator, and at some point the buffer where the 16000samples/second are acummulated is fulled and I get a segmentation fault.

What I'm failing to understand?
byuu

Post by byuu »

I've been meaning to ask others something about this anyway, so I've responded to you and asked that question here:
http://board.zsnes.com/phpBB2/viewtopic ... 641#170641

Oh, and it's kind of pointless to accept votes without posts explaining the results. I'd ignore the two "no" votes until they respond.
RedDwarf
Rookie
Posts: 37
Joined: Thu Jan 27, 2005 7:28 pm

Post by RedDwarf »

Uncapped = 32khz, but without waiting for samples to fully play
I wasn't waiting for samples to fully play, but I was saving them to play them later.
So what I must do is overwrite samples that hadn't time to play? I though making this I would obtain very cracky sound, but seems to work with the same sound quality than the openal code.
Here is the patch over the first one:

Code: Select all

--- src/lib/ruby/audio/alsa.cpp
+++ src/lib/ruby/audio/alsa.cpp
@@ -24,22 +24,29 @@
   } buffer;
 
   struct {
+    bool synchronize;
     unsigned frequency;
     snd_pcm_uframes_t buffer_size;
     snd_pcm_uframes_t period_size;
   } settings;
 
   bool cap(Audio::Setting setting) {
+    if(setting == Audio::Synchronize) return true;
     if(setting == Audio::Frequency) return true;
     return false;
   }
 
   uintptr_t get(Audio::Setting setting) {
+    if(setting == Audio::Synchronize) return settings.synchronize;
     if(setting == Audio::Frequency) return settings.frequency;
     return false;
   }
 
   bool set(Audio::Setting setting, uintptr_t param) {
+    if(setting == Audio::Synchronize) {
+      settings.synchronize = param;
+      return true;
+    }
     if(setting == Audio::Frequency) {
       settings.frequency = param;
       if(device.handle) {
@@ -57,6 +64,17 @@
     buffer.data[buffer.length++] = left + (right << 16);
     if(buffer.length < settings.period_size) return;
 
+    if(!settings.synchronize) {
+      snd_pcm_sframes_t delay;
+      snd_pcm_delay(device.handle, &delay);
+      if(delay < 0)
+        snd_pcm_prepare(device.handle);
+      else if(delay > settings.buffer_size - settings.period_size) {
+        buffer.length = 0;
+        return;
+      }
+    }
+
     uint32_t *buffer_ptr = buffer.data;
     do {
       snd_pcm_sframes_t written = snd_pcm_writei(device.handle, buffer_ptr, buffer.length);
@@ -120,6 +138,7 @@
     buffer.data = 0;
     buffer.length = 0;
 
+    settings.synchronize = true;
     settings.frequency = 32000;
   }
 

Edit: I should made my test in the crappy hda-intel card and not in the SB Live!. Add a "snd_pcm_avail_update(device.handle);" just before "snd_pcm_delay(device.handle, &delay)" if you get no sound with uncapped speed.
RedDwarf
Rookie
Posts: 37
Joined: Thu Jan 27, 2005 7:28 pm

Post by RedDwarf »

In case anyone is interested in getting exactly 100ms latency (that probably isn't so exact anyway, but...).
My hda-intel AD1988 gets those 682 periods instead of the 720 periods because of dmix. Since some time ago dmix is automatically used if the sound card doesn't does hardware mixing. And by default it uses a 1024 period size. Since the card is 48KHz, at 32KHz (bsnes default speed) you get a 32/48*1024=682 period.

At least in openSUSE that value can be changed at /usr/share/alsa/pcm/dmix.conf. At the period_size section change "default 1024" by "default <whatever>".

Note that I'm not saying you should. Probably you shouldn't. It's just FYI.
You should only need to change it if you REALLY NEED very low latencies... and after playing a little with the 100ms latency I don't see the need for less. For sure not less than 43ms.

Also, with the next "glitch-free" PulseAudio dmix will dissapear and you will have latencies so low like you want. So all this will not be relevant at all.
Nach
ZSNES Developer
ZSNES Developer
Posts: 3904
Joined: Tue Jul 27, 2004 10:54 pm
Location: Solar powered park bench
Contact:

Post by Nach »

Hi.

Sorry I wasn't around for a while, been busy.

I'd really like to hear for those two who voted in favor of the original ALSA as to why. RedDwarf's seems to better than what I initially wrote, except I noticed for certain situations where there where these little freezes in game which were very annoying, on some of the machines I tested.

Here's what I have now:

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;
    snd_pcm_uframes_t buffer_size;
    snd_pcm_uframes_t period_size;
    int channels;
    const char *name;
    unsigned latency;
  } device;

  struct {
    uint32_t *data;
    unsigned length;
  } buffer;

  struct {
    bool synchronize;
    unsigned frequency;
  } settings;

  bool cap(Audio::Setting setting) {
    if(setting == Audio::Synchronize) return true;
    if(setting == Audio::Frequency) return true;
    return false;
  }

  uintptr_t get(Audio::Setting setting) {
    if(setting == Audio::Synchronize) return settings.synchronize;
    if(setting == Audio::Frequency) return settings.frequency;
    return false;
  }

  bool set(Audio::Setting setting, uintptr_t param) {
    if(setting == Audio::Synchronize) {
      settings.synchronize = param;
      return true;
    }
    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 < device.period_size) return;

    if(!settings.synchronize) {
      snd_pcm_avail_update(device.handle);
      snd_pcm_sframes_t delay;
      snd_pcm_delay(device.handle, &delay);
      if(delay < 0) {
        snd_pcm_prepare(device.handle);
      } else if(delay > device.buffer_size - device.period_size) {
        buffer.length = 0;
        return;
      }
    }

    uint32_t *buffer_ptr = buffer.data;
    int i = 3;

    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));
    }
  }

  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, &device.buffer_size, &device.period_size) < 0) {
      device.period_size = device.latency * 1e-6 * settings.frequency / 4;
      term();
      return false;
    }

    buffer.data = new uint32_t[device.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 = 60000;

    buffer.data = 0;
    buffer.length = 0;

    settings.synchronize = false;
    settings.frequency = 22050;
  }

  ~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
This I find is in every way better than what was originally in bsnes, thanks RedDwarf.
Improvements here seem to work well on the half dozen machines I tested with various sound cards and CPU power. Good sound, not too much CPU, not too high latency (2/5 less than code above), no freezes. On really fast machines when playing at max speed, there's a bit of audio dropped, but I think that's acceptable.

There remains the question of how to handle ALSA for non Linux UNIX systems. Should we make a different Makefile param, or should I modify the code to dynamically use ALSA (so no need for -lasound), and #ifdef __linux__ the file?

BTW RedDrawf, dmix is not automatically used. I recently installed Linux on a machine here on a card which lacks hardware mixing, and I can only use one sound program at a time, even though they are all ALSA.
Last edited by Nach on Wed Jul 02, 2008 4:51 pm, edited 1 time in total.
May 9 2007 - NSRT 3.4, now with lots of hashing and even more accurate information! Go download it.
_____________
Insane Coding
RedDwarf
Rookie
Posts: 37
Joined: Thu Jan 27, 2005 7:28 pm

Post by RedDwarf »

Nach wrote:BTW RedDrawf, dmix is not automatically used. I recently installed Linux on a machine here on a card which lacks hardware mixing, and I can only use one sound program at a time, even though they are all ALSA.
Documentation says it should ;-) Not sure how this is archieved. I think it has to do with config files, but... I don't read LISP :wink:
They are at 1.0.17rc3. If it's just a config fix with some luck you could have it for 1.0.17 if you report it now.

About the new code. "device.latency = 600000;"? There isn't an extra zero?
Nach
ZSNES Developer
ZSNES Developer
Posts: 3904
Joined: Tue Jul 27, 2004 10:54 pm
Location: Solar powered park bench
Contact:

Post by Nach »

RedDwarf wrote:
Nach wrote:BTW RedDrawf, dmix is not automatically used. I recently installed Linux on a machine here on a card which lacks hardware mixing, and I can only use one sound program at a time, even though they are all ALSA.
Documentation says it should ;-)
What else is new?
RedDwarf wrote: They are at 1.0.17rc3. If it's just a config fix with some luck you could have it for 1.0.17 if you report it now.
If I actually care. Even if I did have dmix, it only works for applications which use the ALSA API which is worthless. Most of the sound card drivers also suck, and the API is a disaster, they should just drop the project altogether.
RedDwarf wrote: About the new code. "device.latency = 600000;"? There isn't an extra zero?
Ah yes, thanks for the catch. I was thinking before posting the code to make it a bit cleaner and remove some zeros and change 1e-6 to 1e-3, but then I changed my mind, and put back one too many zeros. I'll go correct that in my post above.
May 9 2007 - NSRT 3.4, now with lots of hashing and even more accurate information! Go download it.
_____________
Insane Coding
RedDwarf
Rookie
Posts: 37
Joined: Thu Jan 27, 2005 7:28 pm

Stupid patch over 0.039

Post by RedDwarf »

Code: Select all

--- lib/ruby/audio/alsa.cpp
+++ lib/ruby/audio/alsa.cpp
@@ -86,12 +86,8 @@
     if(buffer.length < device.period_size) return;
 
     if(settings.synchronize == false) {
-      snd_pcm_avail_update(device.handle);
-      snd_pcm_sframes_t delay;
-      snd_pcm_delay(device.handle, &delay);
-      if(delay < 0) {
-        snd_pcm_prepare(device.handle);
-      } else if(delay > device.buffer_size - device.period_size) {
+      snd_pcm_sframes_t avail = snd_pcm_avail_update(device.handle);
+      if(avail < device.period_size) {
         buffer.length = 0;
         return;
       }
When I used snd_pcm_delay() the snd_pcm_avail_update() documentation wrongly specified it "Use it only for mmap access".
The thing is snd_pcm_delay() is wrongly used here. snd_pcm_delay() can include extra latencies (network latency when using PulseAudio, an extra buffer with USB audio, etc.). snd_pcm_avail_update() gives the value is really interestig here: how much space is available/empty to write.

Not really important, but...
Locked