Source to src/sd-alsa/sound.c


Enter a symbol's name here to quickly find it.

 /*
  * UAE - The Un*x Amiga Emulator
  *
  * Support for Linux/ALSA sound
  *
  * Copyright 1997 Bernd Schmidt
  * Copyright 2004 Heikki Orsila
  * Copyright 2006-2007 Richard Drummond
  *
  * BUGS: certainly
  * TODO:
  * - if setup_sound() fails, there may still be hope to get the
  *   sound device, but we totally give up.. see sd-uss.
  */

#include "sysconfig.h"
#include "sysdeps.h"

#include "options.h"
#include "gensound.h"
#include "memory.h"
#include "events.h"
#include "custom.h"
#include "newcpu.h"
#include "sounddep/sound.h"
#include "threaddep/thread.h"

#include <alsa/asoundlib.h>

static smp_comm_pipe to_sound_pipe;
static uae_sem_t sound_comm_sem;

static char alsa_device[256] = "default";
static int alsa_verbose = 0;

static int have_sound = 0, have_thread = 0;
static int dont_block;

static int which_buffer;
static uae_u16 sndbuffer[2][44100];
uae_u16 *sndbufpt, *sndbuf_base;
int sndbufsize;

static snd_pcm_t *alsa_playback_handle = 0;
static int alsa_to_frames_divisor;
static snd_pcm_uframes_t period_frames;

/* alsa_xrun_recovery() function is copied from ALSA manual. why the hell did
 * they make ALSA this hard?! i bet 95% of ALSA programmers would like a
 * simpler way to do error handling.. let the 5% use tricky APIs.  */
static int alsa_xrun_recovery (snd_pcm_t *handle, int err)
{
    if (err == -EPIPE) {
	/* under-run */
	err = snd_pcm_prepare (handle);
	if (err < 0)
	    fprintf (stderr, "uae: no recovery with alsa from underrun, prepare failed: %s\n", snd_strerror (err));
	return 0;
    } else if (err == -ESTRPIPE) {
	while ((err = snd_pcm_resume (handle)) == -EAGAIN) {
	    /* wait until the suspend flag is released */
	    fprintf (stderr, "uae: sleeping for alsa.\n");
	    sleep (1);
	}
	if (err < 0) {
	    err = snd_pcm_prepare (handle);
	    if (err < 0)
		fprintf (stderr, "uae: no recovery with alsa from suspend, prepare failed: %s\n", snd_strerror (err));
	}
	return 0;
    }
    return err;
}

static void write_sound_frames (uae_u16 *bufbase)
{
    char *buf = (char *) bufbase;
    int ret;

    int frames = period_frames;
    while (frames > 0) {
	ret = snd_pcm_writei (alsa_playback_handle, buf, frames);
	if (ret < 0) {
	    if (ret == -EAGAIN || ret == -EINTR)
		continue;
	    if (alsa_xrun_recovery (alsa_playback_handle, ret) < 0) {
		fprintf (stderr, "uae: write error with alsa: %s\n", snd_strerror (ret));
		exit (-1);
	    }
	    continue;
	}
	frames -= ret;
	buf += ret * alsa_to_frames_divisor;
    }
}

void finish_sound_buffers ()
{
    dont_block = currprefs.m68k_speed == -1 && (!regs.stopped || active_fs_packets > 0);
    if (!dont_block) {
	write_sound_frames (sndbuf_base);
    } else {
	write_comm_pipe_int (&to_sound_pipe, 2, 1);
	uae_sem_wait (&sound_comm_sem);
    }

    sndbufpt = sndbuf_base = sndbuffer[which_buffer ^ 1];
}

void close_sound (void)
{
    sync_with_sound = 0;
    if (alsa_playback_handle) {
	snd_pcm_close (alsa_playback_handle);
	alsa_playback_handle = 0;
    }
    if (have_thread) {
	write_comm_pipe_int (&to_sound_pipe, 1, 1);
	uae_sem_wait (&sound_comm_sem);
	uae_sem_destroy (&sound_comm_sem);
	have_thread = 0;
    }
}

static int open_sound_device (void)
{
    return snd_pcm_open (&alsa_playback_handle, alsa_device, SND_PCM_STREAM_PLAYBACK, 0);
}

/* Try to determine whether sound is available.  This is only for GUI purposes.  */
int setup_sound (void)
{
    int err;
    sound_available = 0;
    if ((err = open_sound_device ()) < 0) {
	/* TODO: if the pcm was busy, we should the same as sd-uss does.
	   tell the caller that sound is available. in any other
	   condition we should just return 0. */
	write_log ("Cannot open audio device: %s.\n", snd_strerror (err));
	return 0;
    }
    snd_pcm_close (alsa_playback_handle);
    alsa_playback_handle = 0;
    sound_available = 1;
    return 1;
}

static int set_hw_params (snd_pcm_t *pcm, snd_pcm_hw_params_t *hw_params,
			  unsigned int *rate, unsigned int channels,
			  snd_pcm_format_t format, unsigned int *buffer_time,
			  snd_pcm_uframes_t *buffer_frames,
			  snd_pcm_uframes_t *per_frames)
{
    int err;
    unsigned int periods = 2;

    err = snd_pcm_hw_params_any (pcm, hw_params);
    if (err < 0)
	return err;
    err = snd_pcm_hw_params_set_access (pcm, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
    if (err < 0)
	return err;
    err = snd_pcm_hw_params_set_format (pcm, hw_params, format);
    if (err < 0)
	return err;
    err = snd_pcm_hw_params_set_channels (pcm, hw_params, channels);
    if (err < 0)
	return err;
    err = snd_pcm_hw_params_set_rate_near (pcm, hw_params, rate, 0);
    if (err < 0)
	return err;
    err = snd_pcm_hw_params_set_buffer_time_near (pcm, hw_params, buffer_time, NULL);
    if (err < 0)
	return err;
    snd_pcm_hw_params_get_buffer_size (hw_params, buffer_frames);
    err = snd_pcm_hw_params_set_periods_near (pcm, hw_params, &periods, NULL);
    if (err < 0)
	return err;
    if (periods == 1)
	return -EINVAL;
    err = snd_pcm_hw_params (pcm, hw_params);

    snd_pcm_hw_params_get_period_size (hw_params, per_frames, NULL);
    return 0;
}

static int set_sw_params (snd_pcm_t * pcm,
			  snd_pcm_sw_params_t * sw_params, snd_pcm_uframes_t buffer_frames, snd_pcm_uframes_t period_frames)
{
    int err;

    err = snd_pcm_sw_params_current (pcm, sw_params);
    if (err < 0)
	return err;
    err = snd_pcm_sw_params_set_start_threshold (pcm, sw_params, (buffer_frames / period_frames) * period_frames);
    if (err < 0)
	return err;
    err = snd_pcm_sw_params_set_avail_min (pcm, sw_params, period_frames);
    if (err < 0)
	return err;
    err = snd_pcm_sw_params_set_stop_threshold (pcm, sw_params, buffer_frames);
    if (err < 0)
	return err;
    err = snd_pcm_sw_params_set_xfer_align (pcm, sw_params, 1);
    if (err < 0)
	return err;
    err = snd_pcm_sw_params (pcm, sw_params);
    if (err < 0)
	return err;
    return 0;
}

static void open_sound (void)
{
    unsigned int rate;
    snd_pcm_format_t format;
    unsigned int channels;

    snd_pcm_hw_params_t *hw_params = 0;
    snd_pcm_sw_params_t *sw_params = 0;
    snd_pcm_uframes_t buffer_frames;
    unsigned int buffer_time;

    snd_output_t *alsa_out;

    int err;

    sync_with_sound = 0;

    snd_output_stdio_attach (&alsa_out, stderr, 0);

    channels = 2;
    rate = currprefs.sound_freq;

    have_sound = 0;
    alsa_playback_handle = 0;
    if ((err = open_sound_device ()) < 0) {
	write_log ("Cannot open audio device: %s\n", snd_strerror (err));
	goto nosound;
    }

    buffer_time = currprefs.sound_maxbsiz * 1000;

    while (buffer_time / (rate * 2 * channels) < 6)
	buffer_time *= 2;
    if (buffer_time != currprefs.sound_maxbsiz * 1000) {
	fprintf (stderr, "Increasing sound buffer size to sane minimum of %d bytes.\n",
		 buffer_time / 1000);
    }
    if (buffer_time < 1000 || buffer_time > 500000)
	buffer_time = 100000;


    if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0) {
	write_log ("Cannot allocate hardware parameter structure: %s.\n", snd_strerror (err));
	goto nosound;
    }
    if ((err = snd_pcm_sw_params_malloc (&sw_params)) < 0) {
	write_log ("Cannot allocate software parameter structure: %s.\n", snd_strerror (err));
	goto nosound;
    }

    format = SND_PCM_FORMAT_S16;

    alsa_to_frames_divisor = 2 * channels;

    if ((err =
	 set_hw_params (alsa_playback_handle, hw_params, &rate, channels, format, &buffer_time, &buffer_frames,
			&period_frames)) < 0) {
	write_log ("Cannot set hw parameters: %s.\n", snd_strerror (err));
	goto nosound;
    }

    if ((err = set_sw_params (alsa_playback_handle, sw_params, buffer_frames, period_frames)) < 0) {
	write_log ("Cannot set sw parameters: %s.\n", snd_strerror (err));
	goto nosound;
    }

    sndbufsize = period_frames * alsa_to_frames_divisor;
    snd_pcm_hw_params_free (hw_params);
    snd_pcm_sw_params_free (sw_params);

    if ((err = snd_pcm_prepare (alsa_playback_handle)) < 0) {
	write_log ("Cannot prepare audio interface for use: %s.\n", snd_strerror (err));
	goto nosound;
    }

    obtainedfreq = rate;

    init_sound_table16 ();
    sample_handler = sample16s_handler;

    have_sound = 1;
    sound_available = 1;

    write_log ("ALSA: Using device '%s'.\n", alsa_device);
    write_log ("ALSA: Sound configured for %d Hz. Buffer length is %u us, period size %d bytes.\n",
	       rate, buffer_time, period_frames * alsa_to_frames_divisor);

    if (alsa_verbose)
	snd_pcm_dump (alsa_playback_handle, alsa_out);

    sndbufpt = sndbuf_base = sndbuffer[which_buffer = 0];

    sync_with_sound = 1;
    return;

  nosound:
    have_sound = 0;
    if (hw_params)
	snd_pcm_hw_params_free (hw_params);
    if (sw_params)
	snd_pcm_sw_params_free (sw_params);

    close_sound ();
}

#if 0
/*
 * Handle audio specific cfgfile options
 */
void audio_default_options (struct uae_prefs *p)
{
    strncpy (alsa_device, "default", 256);
    alsa_verbose = 1;
}

void audio_save_options (FILE * f, const struct uae_prefs *p)
{
    cfgfile_write (f, "alsa.device=%s\n", alsa_device);
    cfgfile_write (f, "alsa.verbose=%s\n", alsa_verbose ? "true" : "false");
}

int audio_parse_option (struct uae_prefs *p, const char *option, const char *value)
{
    return (cfgfile_string (option, value, "device", alsa_device, 256)
	    || cfgfile_yesno (option, value, "verbose", &alsa_verbose));
}
#endif

static void *sound_thread (void *dummy)
{
    for (;;) {
	int cmd = read_comm_pipe_int_blocking (&to_sound_pipe);
	int n;

	switch (cmd) {
	case 0:
	    open_sound ();
	    uae_sem_post (&sound_comm_sem);
	    break;
	case 1:
	    uae_sem_post (&sound_comm_sem);
	    return 0;
	case 2:
	    /* If trying for maximum CPU speed, don't block the main
	       thread, instead set the delaying_for_sound variable.  If
	       not trying for maximum CPU speed, synchronize here by
	       delaying the sem_post until after the write.  */
	    delaying_for_sound = dont_block;
	    if (dont_block)
		uae_sem_post (&sound_comm_sem);

	    write_sound_frames (sndbuf_base);
	    if (!dont_block)
		uae_sem_post (&sound_comm_sem);

	    delaying_for_sound = 0;
	    break;
	}
    }
}

/* We use a thread so that we can use the time spent waiting for the sound
   driver for executing m68k instructions, rather than just blocking.  */
static void init_sound_thread (void)
{
    uae_thread_id tid;

    init_comm_pipe (&to_sound_pipe, 20, 1);
    uae_sem_init (&sound_comm_sem, 0, 0);
    uae_start_thread (sound_thread, NULL, &tid);
    have_thread = 1;
}

int init_sound (void)
{
    init_sound_thread ();
    write_comm_pipe_int (&to_sound_pipe, 0, 1);
    uae_sem_wait (&sound_comm_sem);

    return have_sound;
}