--- uae/src/sd-alsa/sound.c 2018/04/24 17:17:36 1.1.1.3 +++ uae/src/sd-alsa/sound.c 2018/04/24 17:20:53 1.1.1.5 @@ -5,6 +5,7 @@ * * Copyright 1997 Bernd Schmidt * Copyright 2004 Heikki Orsila + * Copyright 2006-2007 Richard Drummond * * BUGS: certainly * TODO: @@ -16,157 +17,377 @@ #include "sysdeps.h" #include "options.h" +#include "gensound.h" #include "memory.h" #include "events.h" #include "custom.h" -#include "gensound.h" +#include "newcpu.h" #include "sounddep/sound.h" +#include "threaddep/thread.h" #include -int sound_fd; -static int have_sound = 0; -static unsigned long formats; +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; -uae_u16 sndbuffer[44100]; -uae_u16 *sndbufpt; +static int which_buffer; +static uae_u16 sndbuffer[2][44100]; +uae_u16 *sndbufpt, *sndbuf_base; int sndbufsize; -snd_pcm_t *alsa_playback_handle = 0; -int alsa_to_frames_divisor = 4; +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) { - if (alsa_playback_handle) { - snd_pcm_close (alsa_playback_handle); - alsa_playback_handle = 0; - } + 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(void) +static int open_sound_device (void) { - return snd_pcm_open (&alsa_playback_handle, "default", SND_PCM_STREAM_PLAYBACK, 0); + 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()) < 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. */ - fprintf (stderr, "cannot open audio device (%s)\n", snd_strerror (err)); + 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; - } - snd_pcm_close (alsa_playback_handle); - alsa_playback_handle = 0; - sound_available = 1; - return 1; +} + +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) { - int tmp; - int rate; - int dspbits; - int alsamode; - int channels; - int err; - snd_pcm_hw_params_t *hw_params; - snd_pcm_uframes_t buffer_frames; - - dspbits = 16; - channels = currprefs.sound_stereo ? 2 : 1; - rate = currprefs.sound_freq; - - have_sound = 0; - alsa_playback_handle = 0; - if ((err = open_sound()) < 0) { - fprintf (stderr, "cannot open audio device (%s)\n", snd_strerror (err)); - goto nosound; - } - - if (currprefs.sound_maxbsiz < 128 || currprefs.sound_maxbsiz > 16384) { - fprintf (stderr, "Sound buffer size %d out of range.\n", currprefs.sound_maxbsiz); - currprefs.sound_maxbsiz = 8192; - } - sndbufsize = currprefs.sound_maxbsiz; - - if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0) { - fprintf (stderr, "cannot allocate hardware parameter structure (%s)\n", - snd_strerror (err)); - goto nosound; - } - - if ((err = snd_pcm_hw_params_any (alsa_playback_handle, hw_params)) < 0) { - fprintf (stderr, "cannot initialize hardware parameter structure (%s)\n", - snd_strerror (err)); - goto nosound; - } - - if ((err = snd_pcm_hw_params_set_access (alsa_playback_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { - fprintf (stderr, "cannot set access type (%s)\n", - snd_strerror (err)); - goto nosound; - } - - alsamode = SND_PCM_FORMAT_S16; - - if ((err = snd_pcm_hw_params_set_format (alsa_playback_handle, hw_params, alsamode)) < 0) { - fprintf (stderr, "cannot set sample format (%s)\n", - snd_strerror (err)); - goto nosound; - } - - if ((err = snd_pcm_hw_params_set_channels (alsa_playback_handle, hw_params, channels)) < 0) { - fprintf (stderr, "cannot set channel count (%s)\n", - snd_strerror (err)); - goto nosound; - } - - if ((err = snd_pcm_hw_params_set_rate_near (alsa_playback_handle, hw_params, &rate, 0)) < 0) { - fprintf (stderr, "cannot set sample rate (%s)\n", - snd_strerror (err)); - goto nosound; - } - - alsa_to_frames_divisor = channels * 2; - buffer_frames = sndbufsize / alsa_to_frames_divisor; - if ((err = snd_pcm_hw_params_set_period_size_near(alsa_playback_handle, hw_params, &buffer_frames, 0)) < 0) { - fprintf (stderr, "cannot set period size near (%s)\n", snd_strerror (err)); - goto nosound; - } - - if ((err = snd_pcm_hw_params (alsa_playback_handle, hw_params)) < 0) { - fprintf (stderr, "cannot set parameters (%s)\n", - snd_strerror (err)); - goto nosound; - } - - snd_pcm_hw_params_free (hw_params); - - if ((err = snd_pcm_prepare (alsa_playback_handle)) < 0) { - fprintf (stderr, "cannot prepare audio interface for use (%s)\n", - snd_strerror (err)); - goto nosound; - } - - obtainedfreq = rate; - - init_sound_table16 (); - sample_handler = currprefs.sound_stereo ? sample16s_handler : sample16_handler; - - have_sound = 1; - sound_available = 1; - printf ("Sound driver found and configured at %d Hz, buffer is %d bytes\n", rate, sndbufsize); - - sndbufpt = sndbuffer; - return 1; - - nosound: - have_sound = 0; - close_sound(); - return 0; + init_sound_thread (); + write_comm_pipe_int (&to_sound_pipe, 0, 1); + uae_sem_wait (&sound_comm_sem); + + return have_sound; }