--- mythtv.orig/libs/libmyth/audiooutputalsa.cpp
+++ mythtv/libs/libmyth/audiooutputalsa.cpp
@@ -1,31 +1,53 @@
-#include <cstdio>
-#include <cstdlib>
-#include <sys/time.h>
-#include <time.h>
-#include "config.h"
+/*
+ * Copyright (C) <=2008 unattributed author(s)
+ * Copyright (C) 2008  Alan Calvert
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
 
 using namespace std;
 
 #include "mythcontext.h"
 #include "audiooutputalsa.h"
     
-#define LOC QString("ALSA: ")
-#define LOC_WARN QString("ALSA, Warning: ")
-#define LOC_ERR QString("ALSA, Error: ")
-
-// redefine assert as no-op to quiet some compiler warnings
-// about assert always evaluating true in alsa headers.
-#undef assert
-#define assert(x)
+#define LOC QString("AudioOutputALSA: ")
+#define LOC_WARN QString("AudioOutputALSA Warn: ")
+#define LOC_ERR QString("AudioOutputALSA Error: ")
 
 AudioOutputALSA::AudioOutputALSA(const AudioSettings &settings) :
-    AudioOutputBase(settings),
-    pcm_handle(NULL),
-    numbadioctls(0),
-    mixer_handle(NULL),
-    mixer_control(QString::null)
+    AudioOutputBase(settings)
 {
-    // Set everything up
+    pcm.device = (audio_passthru)
+                  ? QByteArray(audio_passthru_device.toAscii())
+                  : QByteArray(audio_main_device.toAscii());
+    if (pcm.device.isEmpty())
+        pcm.device = "default";
+    pcm.handle = NULL;
+    pcm.logtag = "pcm '" + pcm.device + "' ";
+    pcm.sample_rate = settings.samplerate;
+    pcm.use_mmap = false;
+
+    mixer.device = gContext->GetSetting("MixerDevice", "default");
+    if (mixer.device.startsWith("ALSA:"))
+        mixer.device.remove(0, 5);
+    mixer.handle = NULL;
+    mixer.control = gContext->GetSetting("MixerControl", "PCM");
+    mixer.elem = NULL;
+    mixer.logtag = "mixer '" + mixer.device + "' ";
+
     Reconfigure(settings);
 }
 
@@ -36,672 +58,678 @@
 
 bool AudioOutputALSA::OpenDevice()
 {
-    snd_pcm_format_t format;
-    unsigned int buffer_time, period_time;
-    int err;
-
-    if (pcm_handle != NULL)
-        CloseDevice();
-
-    pcm_handle = NULL;
-    numbadioctls = 0;
-
-    QString real_device = (audio_passthru) ?
-        audio_passthru_device : audio_main_device;
-
-    VERBOSE(VB_GENERAL, QString("Opening ALSA audio device '%1'.")
-            .arg(real_device));
-
-    QByteArray dev_ba = real_device.toLocal8Bit();
-    err = snd_pcm_open(&pcm_handle, dev_ba.constData(),
-                       SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
-
-    if (err < 0)
-    { 
-        Error(QString("snd_pcm_open(%1): %2")
-              .arg(real_device).arg(snd_strerror(err)));
-
-        if (pcm_handle)
-            CloseDevice();
+    if (!PrepPCM())
         return false;
-    }
-
-    /* the audio fragment size was computed by using the next lower power of 2
-       of the following:
-
-       const int video_frame_rate = 30;
-       const int bits_per_byte = 8;
-       int fbytes = (audio_bits * audio_channels * audio_samplerate) / 
-                    (bits_per_byte * video_frame_rate);
-                    
-        For telephony apps, a much shorter fragment size is needed to reduce the
-        delay, and fragments should be multiples of the RTP packet size (10ms). 
-        20ms delay should be the max introduced by the driver, which equates
-        to 320 bytes at 8000 samples/sec and mono 16-bit samples
-    */
-    if (source == AUDIOOUTPUT_TELEPHONY)
-    {
-        fragment_size = 320;
-        buffer_time = 80000;  // 80 ms
-        period_time = buffer_time / 4;  // 20ms
-    }
-    else
+    if (!PrepMixer())
     {
-        fragment_size = 1536 * audio_channels * audio_bits / 8;
-        period_time = 25000;  // in usec, interrupt period time
-        // in usec, for driver buffer alloc (64k max)
-        buffer_time = period_time * 16;
-    }
-
-    if (audio_bits == 8)
-        format = SND_PCM_FORMAT_S8;
-    else if (audio_bits == 16)
-        // is the sound data coming in really little-endian or is it
-        // CPU-endian?
-#ifdef WORDS_BIGENDIAN
-        format = SND_PCM_FORMAT_S16;
-#else
-        format = SND_PCM_FORMAT_S16_LE;
-#endif
-    else if (audio_bits == 24)
-#ifdef WORDS_BIGENDIAN
-        format = SND_PCM_FORMAT_S24;
-#else
-        format = SND_PCM_FORMAT_S24_LE;
-#endif
-    else
-    {
-        Error(QString("Unknown sample format: %1 bits.").arg(audio_bits));
+        CloseDevice();
         return false;
     }
-
-    err = SetParameters(pcm_handle,
-                        format, audio_channels, audio_samplerate, buffer_time,
-                        period_time);
-    if (err < 0) 
+    if (AlsaBad(snd_pcm_prepare(pcm.handle), "prepare failed"))
     {
-        Error("Unable to set ALSA parameters");
         CloseDevice();
         return false;
-    }    
-
-    // make us think that soundcard buffer is 4 fragments smaller than
-    // it really is
-    audio_buffer_unused = soundcard_buffer_size - (fragment_size * 4);
-
-    if (internal_vol)
-        OpenMixer(set_initial_vol);
-    
-    // Device opened successfully
+    }
+    pcm.bytes_per_frame = snd_pcm_frames_to_bytes(pcm.handle, 1);
+    fragment_size = pcm.bytes_per_frame * pcm.period_size;
+    soundcard_buffer_size = pcm.bytes_per_frame * pcm.buffer_size;
+    VERBOSE(VB_AUDIO, LOC + pcm.logtag
+            + QString("duly opened: fragment size %1, "
+                      "soundcard buffer size %2 (in bytes)")
+                      .arg(fragment_size).arg(soundcard_buffer_size));
     return true;
 }
 
 void AudioOutputALSA::CloseDevice()
 {
-    CloseMixer();
-    if (pcm_handle != NULL)
-    {
-        snd_pcm_close(pcm_handle);
-        pcm_handle = NULL;
-    }
+    if (mixer.handle != NULL
+        && !AlsaBad(snd_mixer_close(mixer.handle), "close mixer failed"))
+            VERBOSE(VB_AUDIO, LOC + mixer.logtag + "duly closed");
+    mixer.handle = NULL;
+    if (pcm.handle != NULL
+        && !AlsaBad(snd_pcm_close(pcm.handle), "close pcm failed"))
+            VERBOSE(VB_AUDIO, LOC + pcm.logtag + "duly closed");
+    pcm.handle = NULL;
 }
 
-
 void AudioOutputALSA::WriteAudio(unsigned char *aubuf, int size)
 {
-    unsigned char *tmpbuf;
-    int lw = 0;
-    int frames = size / audio_bytes_per_sample;
-
-    if (pcm_handle == NULL)
-    {
-        VERBOSE(VB_IMPORTANT, QString("WriteAudio() called with pcm_handle == NULL!"));
+    if (audio_actually_paused || pcm.handle == NULL)
         return;
-    }
-    
-    tmpbuf = aubuf;
-
-    VERBOSE(VB_AUDIO+VB_TIMESTAMP,
-            QString("WriteAudio: Preparing %1 bytes (%2 frames)")
-            .arg(size).arg(frames));
-    
-    while (frames > 0) 
+    snd_pcm_state_t state = snd_pcm_state(pcm.handle);
+    switch (state)
     {
-        lw = pcm_write_func(pcm_handle, tmpbuf, frames);
-        
-        if (lw >= 0)
-        {
-            if (lw < frames)
-                VERBOSE(VB_AUDIO, QString("WriteAudio: short write %1 bytes (ok)")
-                        .arg(lw * audio_bytes_per_sample));
-
-            frames -= lw;
-            tmpbuf += lw * audio_bytes_per_sample; // bytes
-        } 
-        else if (lw == -EAGAIN)
-        {
-            VERBOSE(VB_AUDIO, QString("WriteAudio: device is blocked - waiting"));
-
-            snd_pcm_wait(pcm_handle, 10);
-        }
-        else if (lw == -EPIPE &&
-                 snd_pcm_state(pcm_handle) == SND_PCM_STATE_XRUN)
-        {
-            VERBOSE(VB_IMPORTANT, "WriteAudio: buffer underrun");
-
-            if ((lw = snd_pcm_prepare(pcm_handle)) < 0)
-            {
-                Error(QString("WriteAudio: unable to recover from xrun: %1")
-                      .arg(snd_strerror(lw)));
-                return;
-            }
-        }
-        else if (lw == -ESTRPIPE)
-        {
-            VERBOSE(VB_IMPORTANT, "WriteAudio: device is suspended");
-
-            while ((lw = snd_pcm_resume(pcm_handle)) == -EAGAIN)
-                usleep(200);
-
-            if (lw < 0)
-            {
-                VERBOSE(VB_IMPORTANT, "WriteAudio: resume failed");
-
-                if ((lw = snd_pcm_prepare(pcm_handle)) < 0)
-                {
-                    Error(QString("WriteAudio: unable to recover from suspend: %1")
-                          .arg(snd_strerror(lw)));
-                    return;
-                }
-            }
-        }
-        else if (lw == -EBADFD)
-        {
-            VERBOSE(VB_IMPORTANT,
-                    QString("WriteAudio: device is in a bad state (state = %1)")
-                    .arg(snd_pcm_state(pcm_handle)));
-            return;
-        }
-        else
-        {
-            VERBOSE(VB_IMPORTANT, QString("pcm_write_func: %1 (%2)")
-                    .arg(snd_strerror(lw)).arg(lw));
-            VERBOSE(VB_IMPORTANT, QString("WriteAudio: snd_pcm_state == %1")
-                    .arg(snd_pcm_state(pcm_handle)));
-
-            // CloseDevice();
-            return;
-        }
+        case SND_PCM_STATE_XRUN:
+        case SND_PCM_STATE_SUSPENDED:
+            if (!XrunRecovery())
+                break;
+        case SND_PCM_STATE_PREPARED:
+            if (AlsaBad(snd_pcm_start(pcm.handle), "pcm start failed"))
+                break;
+        case SND_PCM_STATE_RUNNING:
+            if (pcm.use_mmap)
+                WriteMmap(aubuf, size / pcm.bytes_per_frame);
+            else
+                WriteRw(aubuf, size / pcm.bytes_per_frame);
+            break;
+
+        default:
+            VERBOSE(VB_IMPORTANT, LOC_ERR + pcm.logtag
+                    + QString("alarming SND_PCM_STATE %1 through WriteAudio()")
+                              .arg(state));
+            break;
     }
 }
 
 int AudioOutputALSA::GetBufferedOnSoundcard(void) const
-{ 
-    if (pcm_handle == NULL)
-    {
-        VERBOSE(VB_IMPORTANT, QString("getBufferedOnSoundcard() called with pcm_handle == NULL!"));
+{
+    if (pcm.handle == NULL)
         return 0;
-    }
-
-    // this should be more like what you want, previously this function
-    // was returning the soundcard buffer size -dag
-
-    snd_pcm_sframes_t delay = 0;
-
-    snd_pcm_state_t state = snd_pcm_state(pcm_handle);
-    if (state == SND_PCM_STATE_RUNNING || 
-        state == SND_PCM_STATE_DRAINING)
-    {
-        snd_pcm_delay(pcm_handle, &delay);
-    }
-
-    if (delay < 0)
-        delay = 0;
-
-    int buffered = delay * audio_bytes_per_sample;
 
+    int buffered = 0;
+    snd_pcm_sframes_t frames = snd_pcm_avail_update(pcm.handle);
+    if (frames < 0)
+        VERBOSE(VB_IMPORTANT, LOC_ERR + pcm.logtag
+                + QString("GetBufferedOnSoundcard query buffer status "
+                          "failed: %1").arg(snd_strerror(frames)));
+    else
+        buffered = (pcm.buffer_size - frames) * pcm.bytes_per_frame;
     return buffered;
 }
 
-
 int AudioOutputALSA::GetSpaceOnSoundcard(void) const
 {
-    if (pcm_handle == NULL)
-    {
-        VERBOSE(VB_IMPORTANT, QString("GetSpaceOnSoundcard() ") +
-                "called with pcm_handle == NULL!");
-
+    if (pcm.handle == NULL)
         return 0;
-    }
-
-    snd_pcm_sframes_t avail, delay;
-
-    snd_pcm_state_t state = snd_pcm_state(pcm_handle);
-    if (state == SND_PCM_STATE_RUNNING || 
-        state == SND_PCM_STATE_DRAINING)
-    {
-        snd_pcm_delay(pcm_handle, &delay);
-    }
-
-    avail = snd_pcm_avail_update(pcm_handle);
-    if (avail < 0 ||
-        (snd_pcm_uframes_t)avail > (snd_pcm_uframes_t)soundcard_buffer_size)
-        avail = soundcard_buffer_size;
-
-    int space = (avail * audio_bytes_per_sample) - audio_buffer_unused;
-
-    if (space < 0)
-        space = 0;
 
+    int space = 0;
+    snd_pcm_sframes_t frames;
+    if ((frames = snd_pcm_avail_update(pcm.handle)) < 0)
+        VERBOSE(VB_IMPORTANT, LOC_ERR + pcm.logtag
+                + QString("GetSpaceOnSoundcard query buffer status failed: %1")
+                          .arg(snd_strerror(frames)));
+    else
+        space = frames * pcm.bytes_per_frame;
     return space;
 }
 
-
-int AudioOutputALSA::SetParameters(snd_pcm_t *handle,
-                                   snd_pcm_format_t format, unsigned int channels,
-                                   unsigned int rate, unsigned int buffer_time,
-                                   unsigned int period_time)
-{
-    int err, dir;
-    snd_pcm_hw_params_t *params;
-    snd_pcm_sw_params_t *swparams;
-    snd_pcm_uframes_t buffer_size;
-    snd_pcm_uframes_t period_size;
-
-    VERBOSE(VB_AUDIO, QString("in SetParameters(format=%1, channels=%2, "
-                              "rate=%3, buffer_time=%4, period_time=%5)")
-            .arg(format).arg(channels).arg(rate).arg(buffer_time).arg(period_time));
-
-    if (handle == NULL)
-    {
-        VERBOSE(VB_IMPORTANT, QString("SetParameters() called with handle == NULL!"));
+int AudioOutputALSA::GetVolumeChannel(int channel) const
+{
+    if (mixer.elem == NULL)
         return 0;
-    }
-        
-    snd_pcm_hw_params_alloca(&params);
-    snd_pcm_sw_params_alloca(&swparams);
-    
-    /* choose all parameters */
-    if ((err = snd_pcm_hw_params_any(handle, params)) < 0)
-    {
-        Error(QString("Broken configuration for playback; no configurations"
-              " available: %1").arg(snd_strerror(err)));
-        return err;
-    }
 
-    /* set the interleaved read/write format, use mmap if available */
-    pcm_write_func = &snd_pcm_mmap_writei;
-    err = snd_pcm_hw_params_set_access(
-        handle, params, SND_PCM_ACCESS_MMAP_INTERLEAVED);
-    if (err < 0)
+    int retvol = 0;
+    if (channel > SND_MIXER_SCHN_UNKNOWN && channel < audio_channels)
     {
-        VERBOSE(VB_GENERAL, LOC_WARN +
-                "mmap not available, attempting to fall back to slow writes.");
-        QString old_err = snd_strerror(err);
-        pcm_write_func = &snd_pcm_writei;
-        err = snd_pcm_hw_params_set_access(
-            handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
-        if (err < 0)
-        {
-            Error("Interleaved sound types MMAP & RW are not available");
-            VERBOSE(VB_IMPORTANT,
-                    QString("MMAP Error: %1\n\t\t\tRW Error: %2")
-                    .arg(old_err).arg(snd_strerror(err)));
-            return err;
+        int chk;
+        snd_mixer_selem_channel_id_t chx = (snd_mixer_selem_channel_id_t)channel;
+        long mixvol;
+        chk = snd_mixer_selem_get_playback_volume(mixer.elem, chx, &mixvol);
+        if (chk < 0)
+           VERBOSE(VB_IMPORTANT, LOC_ERR + mixer.logtag
+                    + QString("failed to get channel %2 volume: %3")
+                              .arg(channel).arg(snd_strerror(chk)));
+        else if (mixer.volrange > 0L)
+        {
+            retvol = (mixvol - mixer.volmin) * 100.0f / mixer.volrange + .5f;
+            retvol = max(retvol, 0);
+            retvol = min(retvol, 100);
+            VERBOSE(VB_AUDIO+VB_EXTRA, LOC + mixer.logtag
+                    + QString("get volume channel %1: %2 (mixer volume: %3")
+                              .arg(channel).arg(retvol).arg(mixvol));
         }
     }
+    else
+        VERBOSE(VB_IMPORTANT, LOC_ERR + mixer.logtag
+                + QString("get volume, invalid channel: %1").arg(channel));
+    return retvol;
+}
 
-    /* set the sample format */
-    if ((err = snd_pcm_hw_params_set_format(handle, params, format)) < 0)
-    {
-        Error(QString("Sample format not available: %1")
-              .arg(snd_strerror(err)));
-        return err;
-    }
+void AudioOutputALSA::SetVolumeChannel(int channel, int volume)
+{
+    if (!internal_vol || mixer.elem == NULL)
+        return;
 
-    /* set the count of channels */
-    if ((err = snd_pcm_hw_params_set_channels(handle, params, channels)) < 0)
+    if (channel > SND_MIXER_SCHN_UNKNOWN && channel < audio_channels)
     {
-        Error(QString("Channels count (%1) not available: %2")
-              .arg(channels).arg(snd_strerror(err)));
-        return err;
+        snd_mixer_selem_channel_id_t chx =
+            (snd_mixer_selem_channel_id_t)channel;
+        long mixvol = volume * mixer.volrange / 100.0f - mixer.volmin + 0.5f;
+        mixvol = max(mixvol, mixer.volmin);
+        mixvol = min(mixvol, mixer.volmax);
+        if (!AlsaBad(snd_mixer_selem_set_playback_volume(mixer.elem, chx, mixvol),
+                     QString("failed to set channel %1 volume -> %2")
+                             .arg(channel).arg(volume)))
+            VERBOSE(VB_AUDIO, LOC + mixer.logtag
+                    + QString("set channel %1 volume -> %2 (mixer volume: %3)")
+                              .arg(channel).arg(volume).arg(mixvol));
     }
+    else
+        VERBOSE(VB_IMPORTANT, LOC_ERR + mixer.logtag
+                + QString("set volume, invalid channel: %1").arg(channel));
+}
 
-    /* set the stream rate */
-    unsigned int rrate = rate;
-    if ((err = snd_pcm_hw_params_set_rate_near(handle, params, &rrate, 0)) < 0)
+void AudioOutputALSA::Reset(void)
+{
+    if (pcm.handle != NULL)
     {
-        Error(QString("Samplerate (%1Hz) not available: %2")
-              .arg(rate).arg(snd_strerror(err)));
-        return err;
+        AlsaBad(snd_pcm_drop(pcm.handle), "Reset drop failed");
+        AlsaBad(snd_pcm_prepare(pcm.handle), "Reset prepare failed");
+        AudioOutputBase::Reset();
+        VERBOSE(VB_AUDIO+VB_EXTRA, LOC + pcm.logtag + "duly reset");
     }
+}
 
-    if (rrate != rate)
-    {
-        Error(QString("Rate doesn't match (requested %1Hz, got %2Hz)")
-              .arg(rate).arg(rrate));
-        return -EINVAL;
-    }
+void AudioOutputALSA::Pause(bool paused)
+{
+    if (pcm.handle == NULL)
+        return;
 
-    /* set the buffer time */
-    if ((err = snd_pcm_hw_params_set_buffer_time_near(handle, params,
-                                                     &buffer_time, &dir)) < 0)
-    {
-        Error(QString("Unable to set buffer time %1 for playback: %2")
-              .arg(buffer_time).arg(snd_strerror(err)));
-        return err;
+    if (paused && !audio_actually_paused)
+     {
+        AlsaBad(snd_pcm_drop(pcm.handle), "pause drop failed");
+        pauseaudio = paused;
+        audio_actually_paused = true;
+        VERBOSE(VB_AUDIO+VB_EXTRA, LOC + pcm.logtag + "actually paused");
+    }
+    else if (!paused && audio_actually_paused)
+     {
+        QString tag = "(un)Pause ";
+        AlsaBad(snd_pcm_drop(pcm.handle), tag + "unpause drop failed");
+        AlsaBad(snd_pcm_prepare(pcm.handle), tag + "unpause prepare failed");
+        pauseaudio = paused;
+        audio_actually_paused = false;
+        VERBOSE(VB_AUDIO+VB_EXTRA, LOC + pcm.logtag + "actually unpaused");
     }
+}
 
-    if ((err = snd_pcm_hw_params_get_buffer_size(params, &buffer_size)) < 0)
-    {
-        Error(QString("Unable to get buffer size for playback: %1")
-              .arg(snd_strerror(err)));
-        return err;
-    } else {
-        VERBOSE(VB_AUDIO, QString("get_buffer_size returned %1").arg(buffer_size));
+void AudioOutputALSA::Drain(void)
+{
+    AudioOutputBase::Drain();
+    if (pcm.handle != NULL)
+        AlsaBad(snd_pcm_drain(pcm.handle), "pcm drain failed");
+}
+
+bool AudioOutputALSA::PrepPCM()
+{
+    if (pcm.handle != NULL)
+        snd_pcm_close(pcm.handle);
+    AlsaBad(snd_config_update_free_global(), "failed to update snd config");
+    QRegExp built_in_regx("^(front|surround\\d{2}|hdmi|rear|center_lfe|iec958)"
+                         ":(CARD=\\w+,DEV=\\d)|(DEV=\\d,CARD=\\w+)");
+    bool is_built_in = QString(pcm.device).contains(built_in_regx);
+    QRegExp plug_regx("^(plug|plughw):\\w+");
+    bool is_plug = QString(pcm.device).contains(plug_regx);
+
+    while (true)
+    {
+        pcm.logtag = "pcm '" + pcm.device + "' ";
+        if (AlsaBad(snd_pcm_open(&pcm.handle, pcm.device.constData(),
+                                 SND_PCM_STREAM_PLAYBACK,
+                                 SND_PCM_NO_AUTO_CHANNELS),
+                    "failed to open device"))
+        {
+            pcm.handle = NULL;
+            return false;
+        }
+        if (AlsaBad(snd_pcm_nonblock(pcm.handle, 1), "set nonblock failed"))
+        {
+            snd_pcm_close(pcm.handle);
+            pcm.handle = NULL;
+            return false;
+        }
+        if (PrepHwparams())
+            break;
+        else if (!is_plug && !is_built_in) // try again with 'plug:'
+        {
+            AlsaBad(snd_pcm_close(pcm.handle), "plug retry, close failed");
+            pcm.handle = NULL;
+            pcm.device.prepend("plug:");
+            is_plug = true;
+            continue;
+        }
+        else
+        {
+            snd_pcm_close(pcm.handle);
+            pcm.handle = NULL;
+            return false;
+        }
     }
-    soundcard_buffer_size = buffer_size * audio_bytes_per_sample;
-
-    /* set the period time */
-    if ((err = snd_pcm_hw_params_set_period_time_near(
-                    handle, params, &period_time, &dir)) < 0)
+    if (!PrepSwparams())
     {
-        Error(QString("Unable to set period time %1 for playback: %2")
-              .arg(period_time).arg(snd_strerror(err)));
-        return err;
-    } else {
-        VERBOSE(VB_AUDIO, QString("set_period_time_near returned %1").arg(period_time));
-    }
-
-    if ((err = snd_pcm_hw_params_get_period_size(params, &period_size,
-                                                &dir)) < 0) {
-        Error(QString("Unable to get period size for playback: %1")
-              .arg(snd_strerror(err)));
-        return err;
-    } else {
-        VERBOSE(VB_AUDIO, QString("get_period_size returned %1").arg(period_size));
+        snd_pcm_close(pcm.handle);
+        return false;
     }
+    return true;
+}
 
-    /* write the parameters to device */
-    if ((err = snd_pcm_hw_params(handle, params)) < 0) {
-        Error(QString("Unable to set hw params for playback: %1")
-              .arg(snd_strerror(err)));
-        return err;
-    }
-    
-    /* get the current swparams */
-    if ((err = snd_pcm_sw_params_current(handle, swparams)) < 0)
-    {
-        Error(QString("Unable to determine current swparams for playback:"
-                      " %1").arg(snd_strerror(err)));
-        return err;
-    }
-    /* start the transfer after period_size */
-    if ((err = snd_pcm_sw_params_set_start_threshold(handle, swparams, 
-                                                    period_size)) < 0)
-    {
-        Error(QString("Unable to set start threshold mode for playback: %1")
-              .arg(snd_strerror(err)));
-        return err;
-    }
+bool AudioOutputALSA::PrepHwparams(void)
+{
+    bool result = false;
+    snd_pcm_hw_params_t*  hwparams;
+    snd_pcm_hw_params_alloca(&hwparams);
+    snd_pcm_access_t axs;
+    snd_pcm_format_t format;
+    unsigned int rate, latency, period_time, minchan, maxchan, periods;
 
-    /* allow the transfer when at least period_size samples can be processed */
-    if ((err = snd_pcm_sw_params_set_avail_min(handle, swparams,
-                                              period_size)) < 0)
+    if (AlsaBad(snd_pcm_hw_params_any(pcm.handle, hwparams),
+                "no playback configurations available"))
+        goto fini;
+    if (AlsaBad(snd_pcm_hw_params_set_periods_integer(pcm.handle, hwparams),
+                "cannot restrict period size to integral value"))
+        goto fini;
+    pcm.use_mmap = false;
+    axs = SND_PCM_ACCESS_MMAP_INTERLEAVED;
+    if (!AlsaBad(snd_pcm_hw_params_set_access(pcm.handle, hwparams, axs),
+                 "mmap io access not possible"))
+        pcm.use_mmap = true;
+    else
     {
-        Error(QString("Unable to set avail min for playback: %1")
-              .arg(snd_strerror(err)));
-        return err;
+        axs = SND_PCM_ACCESS_RW_INTERLEAVED;
+        if (AlsaBad(snd_pcm_hw_params_set_access(pcm.handle, hwparams, axs),
+                     "failed to set mmap or rw access, both failed"))
+            goto fini;
+    }
+    switch (audio_bits) // ALSA to append _LE/_BE accordingly
+    {
+        case 8:
+            format = SND_PCM_FORMAT_U8;
+            break;
+        case 16:
+        default:
+            format = SND_PCM_FORMAT_S16;
+            break;
+    }
+    if (AlsaBad(snd_pcm_hw_params_set_format(pcm.handle, hwparams, format),
+                "failed to set sample format"))
+        goto fini;
+
+    rate = pcm.sample_rate;
+    if (AlsaBad(snd_pcm_hw_params_set_rate_near(pcm.handle, hwparams, &rate, NULL),
+                QString("failed setting sample rate near %1").arg(rate)))
+        goto fini;
+    VERBOSE(VB_AUDIO+VB_EXTRA, LOC + pcm.logtag
+            + QString("requested sample rate %1, set sample rate %2")
+                      .arg(pcm.sample_rate).arg(rate));
+    if (rate < pcm.sample_rate * 0.95f || rate > pcm.sample_rate * 1.05f)
+    {
+        AlsaBad(-EINVAL, QString("requested sample rate %1, set sample rate %2 "
+                                 "- more than 5% off")
+                                 .arg(pcm.sample_rate).arg(rate));
+        goto fini;
+    }
+    if (AlsaBad(snd_pcm_hw_params_set_channels(pcm.handle, hwparams,
+                                               audio_channels),
+                QString("failed to set channels to %1").arg(audio_channels)))
+        goto fini;
+
+    // latency, period, buffer prep based on Alsa fn() snd_pcm_set_params()
+    latency = 50000; // 50msec
+    if (!AlsaBad(snd_pcm_hw_params_set_buffer_time_near(pcm.handle, hwparams,
+                                                        &latency, NULL),
+                "initial buffer time/latency setting failed"))
+    {
+        if (AlsaBad(snd_pcm_hw_params_get_buffer_size(hwparams, &pcm.buffer_size),
+                    "failed to get buffer size"))
+            goto fini;
+        if (AlsaBad(snd_pcm_hw_params_get_buffer_time(hwparams, &latency, NULL),
+                    "failed to get latency setting"))
+            goto fini;
+        period_time = latency / 4;
+        if (AlsaBad(snd_pcm_hw_params_set_period_time_near(pcm.handle, hwparams,
+                                                           &period_time, NULL),
+                    "failed to set period time"))
+            goto fini;
+        if (AlsaBad(snd_pcm_hw_params_get_period_size(hwparams, &pcm.period_size,
+                                                      NULL),
+                    "failed to get period size"))
+            goto fini;
     }
-
-    /* align all transfers to 1 sample */
-    if ((err = snd_pcm_sw_params_set_xfer_align(handle, swparams, 1)) < 0)
+    else
     {
-        Error(QString("Unable to set transfer align for playback: %1")
-              .arg(snd_strerror(err)));
-        return err;
-    }
+        VERBOSE(VB_AUDIO, LOC + "hwparams settings, Plan B");
+        period_time = latency / 4;
+        if (AlsaBad(snd_pcm_hw_params_set_period_time_near(pcm.handle, hwparams,
+                                                           &period_time, NULL),
+                    "failed to set period time"))
+            goto fini;
+        if (AlsaBad(snd_pcm_hw_params_get_period_size(hwparams, &pcm.period_size,
+                                                      NULL),
+                    "failed to get period size"))
+            goto fini;
+        pcm.buffer_size = pcm.period_size * 4;
+        if (AlsaBad(snd_pcm_hw_params_set_buffer_size_near(pcm.handle, hwparams,
+                                                           &pcm.buffer_size),
+                    "failed to set buffer size"))
+            goto fini;
+        if (AlsaBad(snd_pcm_hw_params_get_buffer_size(hwparams,
+                                                      &pcm.buffer_size),
+                    "failed to get buffer size"))
+            goto fini;
+    }
+    if (AlsaBad(snd_pcm_hw_params (pcm.handle, hwparams),
+                "failed to set hardware parameters"))
+		goto fini;
+
+    result = true;
+    periods = 0;
+    AlsaBad(snd_pcm_hw_params_get_periods(hwparams, &periods, NULL),
+            "failed to get periods");
+    VERBOSE(VB_AUDIO, LOC + pcm.logtag
+            + QString("latency %1msec, period size %2, periods %3, "
+                      "buffer size %4 (in frames)")
+                      .arg(latency /1000.0, 0, 'f', 1).arg(pcm.period_size)
+                      .arg(periods).arg(pcm.buffer_size));
+    VERBOSE(VB_AUDIO, LOC + pcm.logtag
+            + QString("channels %1, using %2").arg(audio_channels)
+                      .arg((pcm.use_mmap) ? "mmap write" : "snd_pcm_writei"));
+    fini:
+    return result;
+}
 
-    /* write the parameters to the playback device */
-    if ((err = snd_pcm_sw_params(handle, swparams)) < 0)
-    {
-        Error(QString("Unable to set sw params for playback: %1")
-              .arg(snd_strerror(err)));
-        return err;
-    }
+bool AudioOutputALSA::PrepSwparams(void)
+{
+    snd_pcm_sw_params_t* swparams;
+    snd_pcm_sw_params_alloca(&swparams);
+	snd_pcm_uframes_t boundary;
+    bool result = false;
 
-    if ((err = snd_pcm_prepare(handle)) < 0)
-        Error(QString("Initial pcm prepare err %1 %2")
-              .arg(err).arg(snd_strerror(err)));
+    if (AlsaBad(snd_pcm_sw_params_current(pcm.handle, swparams),
+               "failed to get swparams"))
+        goto fini;
+
+    // using explicit start, not auto start
+    if (AlsaBad(snd_pcm_sw_params_set_start_threshold(pcm.handle, swparams,
+                                                      INT_MAX),
+                "failed to set start threshold"))
+		goto fini;
+   if (AlsaBad(snd_pcm_sw_params_get_boundary(swparams, &boundary),
+               "failed to get boundary"))
+        goto fini;
+    if (AlsaBad(snd_pcm_sw_params_set_stop_threshold(pcm.handle, swparams,
+                                                     boundary),
+                "failed to set stop threshold"))
+		goto fini;
+    if (AlsaBad(snd_pcm_sw_params(pcm.handle, swparams),
+                "failed to set software parameters"))
+        goto fini;
+
+    result = true;
 
-    return 0;
+    fini:
+    return result;
 }
 
-
-int AudioOutputALSA::GetVolumeChannel(int channel) const
+bool AudioOutputALSA::PrepMixer(void)
 {
-    long actual_volume;
-
-    if (mixer_handle == NULL)
-        return 100;
-
-    QByteArray mix_ctl = mixer_control.toAscii();
-    snd_mixer_selem_id_t *sid;
-    snd_mixer_selem_id_alloca(&sid);
-    snd_mixer_selem_id_set_index(sid, 0);
-    snd_mixer_selem_id_set_name(sid, mix_ctl.constData());
+    if (pcm.handle == NULL)
+        return false;
 
-    snd_mixer_elem_t *elem = snd_mixer_find_selem(mixer_handle, sid);
-    if (!elem)
+    int chk;
+    if ((chk = snd_mixer_open(&mixer.handle, 0)) < 0)
     {
-        VERBOSE(VB_IMPORTANT, QString("Mixer unable to find control %1")
-                .arg(mixer_control));
-        return 100;
+        VERBOSE(VB_IMPORTANT, LOC_ERR + mixer.logtag
+                + QString("failed to open: %2").arg(snd_strerror(chk)));
+        return false;
     }
-
-    snd_mixer_selem_channel_id_t chan = (snd_mixer_selem_channel_id_t) channel;
-    if (!snd_mixer_selem_has_playback_channel(elem, chan))
+    struct snd_mixer_selem_regopt regopts =
+        {1, SND_MIXER_SABSTRACT_NONE, mixer.device.constData(), NULL, NULL};
+    if ((chk = snd_mixer_selem_register(mixer.handle, &regopts, NULL)) < 0)
+    {
+        VERBOSE(VB_IMPORTANT, LOC_ERR + mixer.logtag
+                + QString("failed to register: %2").arg(snd_strerror(chk)));
+        snd_mixer_close(mixer.handle);
+        mixer.handle = NULL;
+        return false;
+    }
+    if ((chk = snd_mixer_load(mixer.handle)) < 0)
     {
-        snd_mixer_selem_id_set_index(sid, channel);
-        if ((elem = snd_mixer_find_selem(mixer_handle, sid)) == NULL)
-        {
-            VERBOSE(VB_IMPORTANT, QString("Mixer unable to find control %1 %2")
-                    .arg(mixer_control).arg(channel));
-            return 100;
-        }
+        VERBOSE(VB_IMPORTANT, LOC_ERR + mixer.logtag
+                + QString("failed to load: %2").arg(snd_strerror(chk)));
+        snd_mixer_close(mixer.handle);
+        mixer.handle = NULL;
+        return false;
     }
-
-    ALSAVolumeInfo vinfo = GetVolumeRange(elem);
-
-    snd_mixer_selem_get_playback_volume(
-        elem, (snd_mixer_selem_channel_id_t)channel, &actual_volume);
-
-    return vinfo.ToMythRange(actual_volume);
-}
-
-void AudioOutputALSA::SetVolumeChannel(int channel, int volume)
-{
-    SetCurrentVolume(mixer_control, channel, volume);
-}
-
-void AudioOutputALSA::SetCurrentVolume(QString control, int channel, int volume)
-{
-    VERBOSE(VB_AUDIO, QString("Setting %1 volume to %2")
-            .arg(control).arg(volume));
-
-    if (!mixer_handle)
-        return; // no mixer, nothing to do
-
-    QByteArray ctl = control.toAscii();
-    snd_mixer_selem_id_t *sid;
-    snd_mixer_selem_id_alloca(&sid);
-    snd_mixer_selem_id_set_index(sid, 0);
-    snd_mixer_selem_id_set_name(sid, ctl.constData());
-
-    snd_mixer_elem_t *elem = snd_mixer_find_selem(mixer_handle, sid);
-    if (!elem)
+    unsigned int elcount = snd_mixer_get_count(mixer.handle);
+    snd_mixer_elem_t* elx = snd_mixer_first_elem(mixer.handle);
+    mixer.elem = NULL;
+    for (unsigned int ctr = 0; elx != NULL && ctr < elcount; ++ctr)
+    {
+        if (!strcmp(mixer.control.constData(), snd_mixer_selem_get_name(elx))
+            && !snd_mixer_selem_is_enumerated(elx)
+            && snd_mixer_selem_has_playback_volume(elx)
+            && snd_mixer_selem_is_active(elx))
+        {
+            mixer.elem = elx;
+            mixer.logtag += "control '" + mixer.control + "', ";
+            VERBOSE(VB_AUDIO+VB_EXTRA, LOC + mixer.logtag
+                    + QString("playback control '%1' selected")
+                              .arg(mixer.control.constData()));
+            break;
+        }
+        elx = snd_mixer_elem_next(elx);
+    }
+    if (mixer.elem == NULL)
+    {
+        VERBOSE(VB_IMPORTANT, LOC_ERR + mixer.logtag
+                + QString("playback control '%1' not found")
+                          .arg(mixer.control.constData()));
+        snd_mixer_close(mixer.handle);
+        mixer.handle = NULL;
+        return false;
+    }
+    if (AlsaBad(snd_mixer_selem_get_playback_volume_range(mixer.elem,
+                                                          &mixer.volmin,
+                                                          &mixer.volmax),
+                "failed to get volume range"))
     {
-        VERBOSE(VB_IMPORTANT, QString("Mixer unable to find control %1")
-                .arg(control));
-        return;
+        snd_mixer_close(mixer.handle);
+        mixer.handle = NULL;
+        return false;
     }
+    mixer.volrange = mixer.volmax - mixer.volmin;
+    VERBOSE(VB_AUDIO+VB_EXTRA, LOC + mixer.logtag
+            + QString("volume range - min %1, max %2, range %3")
+                      .arg(mixer.volmin).arg(mixer.volmax).arg(mixer.volrange));
+    if (set_initial_vol)
+    {
+        int initial_vol;
+        if ( mixer.control == "PCM")
+            initial_vol = gContext->GetNumSetting("PCMMixerVolume", 80);
+        else
+            initial_vol = gContext->GetNumSetting("MasterMixerVolume", 80);
+        for (int ch = 0; ch < audio_channels; ++ch)
+            SetVolumeChannel(ch, initial_vol);
+    }
+    VERBOSE(VB_AUDIO, LOC + mixer.logtag + "set up ok");
+    return true;
+}
 
-    snd_mixer_selem_channel_id_t chan = (snd_mixer_selem_channel_id_t) channel;
-    if (!snd_mixer_selem_has_playback_channel(elem, chan))
-    {
-        snd_mixer_selem_id_set_index(sid, channel);
-        if ((elem = snd_mixer_find_selem(mixer_handle, sid)) == NULL)
+void AudioOutputALSA::WriteMmap(unsigned char *data, snd_pcm_uframes_t nframes)
+{
+    unsigned char* ptr8 = data;
+    int16_t* ptr16 = (int16_t*)data;
+    int retries = 0;
+    int chk;
+    while ( nframes > 0 && retries < 3)
+    {
+        snd_pcm_hwsync(pcm.handle);
+        snd_pcm_sframes_t avail = snd_pcm_avail_update(pcm.handle);
+        if (avail < 0)
+        {
+            VERBOSE(VB_IMPORTANT, LOC_ERR + pcm.logtag
+                    + "WriteMmap failed to get avail");
+            if (Recovery(avail))
+            {
+                ++retries;
+                continue;
+            }
+            else
+                return;
+        }
+        snd_pcm_uframes_t towrite =
+            (nframes > (snd_pcm_uframes_t)avail) ? avail : nframes;
+        const snd_pcm_channel_area_t* areas;
+        snd_pcm_uframes_t offset;
+        if ((chk = snd_pcm_mmap_begin(pcm.handle, &areas, &offset, &towrite))
+            < 0)
         {
-            VERBOSE(VB_IMPORTANT,
-                    QString("mixer unable to find control %1 %2")
-                    .arg(control).arg(channel));
+            VERBOSE(VB_IMPORTANT, LOC_ERR + pcm.logtag
+                    + "mmap begin failed");
             return;
         }
+        // get channel bases
+        unsigned char* samples[audio_channels];
+        int steps[audio_channels];
+        int ch;
+        for (ch = 0; ch < audio_channels; ++ch)
+        {
+            samples[ch] = (unsigned char*)areas[ch].addr
+                          + (areas[ch].first + areas[ch].step * offset ) / 8;
+            steps[ch] = areas[ch].step / 8;
+        }
+        // wrrrrite ...
+        unsigned int frm;
+        switch( audio_bits)
+        {
+            case 16:
+                for (frm = towrite; frm; --frm)
+                    for(ch = 0; ch < audio_channels; ++ch)
+                    {
+                        *(int16_t*)(samples[ch]) = *ptr16++;
+                        samples[ch] += steps[ch];
+                    }
+                break;
+
+            case 8:
+                for (frm = towrite; frm; --frm)
+                    for(ch = 0; ch < audio_channels; ++ch)
+                    {
+                        *samples[ch] = *ptr8++;
+                        samples[ch] += steps[ch];
+                    }
+                break;
+            default:
+                VERBOSE(VB_IMPORTANT, LOC_ERR + pcm.logtag
+                        + "invalid sample format, DirectWrite");
+                        return;
+        }
+        chk = snd_pcm_mmap_commit(pcm.handle, offset, towrite);
+        if (chk < 0)
+        {
+            VERBOSE(VB_IMPORTANT, LOC_ERR + pcm.logtag
+                    + "mmap commit failed");
+            return;
+        }
+        if (chk < (int)towrite)
+            VERBOSE(VB_IMPORTANT, LOC_ERR + pcm.logtag
+                    + QString("short write, %1 / %2, DirectWrite")
+                              .arg(chk).arg(towrite));
+        nframes -= towrite;
+        ++retries;
     }
+}
 
-    ALSAVolumeInfo vinfo = GetVolumeRange(elem);
-
-    long set_vol = vinfo.ToALSARange(volume);
-
-    int err = snd_mixer_selem_set_playback_volume(elem, chan, set_vol);
-    if (err < 0)
-    {
-        VERBOSE(VB_IMPORTANT, QString("mixer set channel %1 err %2: %3")
-                .arg(channel).arg(err).arg(snd_strerror(err)));
-    }
-    else
-    {
-        VERBOSE(VB_AUDIO, QString("channel %1 vol set to %2")
-                .arg(channel).arg(set_vol));
-    }
-
-    if (snd_mixer_selem_has_playback_switch(elem))
+void AudioOutputALSA::WriteRw(unsigned char *data, snd_pcm_uframes_t nframes)
+{
+    snd_pcm_uframes_t towrite;
+    snd_pcm_sframes_t wrote, avail;
+    int retries = 0;
+    for (wrote = 0; nframes > 0 && retries < 3; data += wrote * pcm.bytes_per_frame)
     {
-        int unmute = (0 != set_vol);
-        if (snd_mixer_selem_has_playback_switch_joined(elem))
+        snd_pcm_hwsync(pcm.handle);
+        if ((avail = snd_pcm_avail_update(pcm.handle)) < 0)
         {
-            // Only mute if all the channels should be muted.
-            for (int i = 0; i < audio_channels; i++)
+            VERBOSE(VB_IMPORTANT, LOC_ERR + pcm.logtag
+                    + "WriteRw failed to get avail");
+            if (Recovery(avail))
             {
-                if (0 != GetVolumeChannel(i))
-                    unmute = 1;
+                ++retries;
+                continue;
             }
+            else
+                return;
         }
-
-        err = snd_mixer_selem_set_playback_switch(elem, chan, unmute);
-        if (err < 0)
+        towrite = (nframes < (snd_pcm_uframes_t)avail) ? nframes : avail;
+        wrote = snd_pcm_writei(pcm.handle, (void*)data, towrite);
+        if (wrote < 0)
         {
-            VERBOSE(VB_IMPORTANT, LOC_ERR +
-                    QString("Mixer set playback switch %1 err %2: %3")
-                    .arg(channel).arg(err).arg(snd_strerror(err)));
+            switch (wrote) // ==> frames written, -EBADFD, -EPIPE, -ESTRPIPE
+            {
+                case -EBADFD:
+                    AlsaBad(-EBADFD, "unfit for writing");
+                    break;
+                case -EPIPE:
+                case -ESTRPIPE:
+                    Recovery(wrote);
+                    break;
+                default:
+                    AlsaBad(wrote, "snd_pcm_writei ==> weird state");
+                    nframes = 0;
+                    break;
+            }
+            wrote = 0;
         }
         else
         {
-            VERBOSE(VB_AUDIO, LOC +
-                    QString("channel %1 playback switch set to %2")
-                    .arg(channel).arg(unmute));
+            if ((snd_pcm_uframes_t)wrote < towrite)
+                VERBOSE(VB_AUDIO, LOC_ERR + pcm.logtag
+                        + QString("short write, %1 / %2 frames")
+                                  .arg(wrote).arg(towrite));
+            nframes -= wrote;
         }
+        ++retries;
     }
 }
 
-void AudioOutputALSA::OpenMixer(bool setstartingvolume)
+bool AudioOutputALSA::Recovery(int err)
 {
-    int volume;
-
-    mixer_control = gContext->GetSetting("MixerControl", "PCM");
-
-    SetupMixer();
-
-    if (mixer_handle != NULL && setstartingvolume)
+    if (err > 0)
+        err = -err;
+    bool isgood = false;
+    bool suspense = false;
+    switch (err)
     {
-        volume = gContext->GetNumSetting("MasterMixerVolume", 80);
-        SetCurrentVolume("Master", 0, volume);
-        SetCurrentVolume("Master", 1, volume);
-
-        volume = gContext->GetNumSetting("PCMMixerVolume", 80);
-        SetCurrentVolume("PCM", 0, volume);
-        SetCurrentVolume("PCM", 1, volume);
+        case -EINTR:
+            isgood = true; // nuthin to see here
+            break;
+        case -ESTRPIPE:
+            suspense = true;
+        case -EPIPE:
+            if (!AlsaBad(snd_pcm_prepare(pcm.handle),
+                         QString("failed to recover from %1")
+                                 .arg((suspense) ? "suspend" : "underrun")))
+                isgood = true;
+            break;
+        default:
+            break;
     }
+    return isgood;
 }
 
-void AudioOutputALSA::CloseMixer(void)
-{
-    if (mixer_handle != NULL)
-        snd_mixer_close(mixer_handle);
-    mixer_handle = NULL;
-}
-
-void AudioOutputALSA::SetupMixer(void)
+bool AudioOutputALSA::XrunRecovery(void)
 {
-    int err;
-
-    QString alsadevice = gContext->GetSetting("MixerDevice", "default");
-    QString device = alsadevice.remove(QString("ALSA:"));
-
-    if (mixer_handle != NULL)
-        CloseMixer();
-
-    VERBOSE(VB_AUDIO, QString("Opening mixer %1").arg(device));
-
-    // TODO: This is opening card 0. Fix for case of multiple soundcards
-    if ((err = snd_mixer_open(&mixer_handle, 0)) < 0)
-    {
-        Warn(QString("Mixer device open error %1: %2")
-             .arg(err).arg(snd_strerror(err)));
-        mixer_handle = NULL;
-        return;
-    }
-
-    QByteArray dev = device.toAscii();
-    if ((err = snd_mixer_attach(mixer_handle, dev.constData())) < 0)
-    {
-        Warn(QString("Mixer attach error %1: %2"
-                     "\n\t\t\tCheck Mixer Name in Setup: '%3'")
-             .arg(err).arg(snd_strerror(err)).arg(device));
-        CloseMixer();
-        return;
-    }
-
-    if ((err = snd_mixer_selem_register(mixer_handle, NULL, NULL)) < 0)
+    bool isgood = false;
+    if (pcm.handle != NULL)
     {
-        Warn(QString("Mixer register error %1: %2")
-             .arg(err).arg(snd_strerror(err)));
-        CloseMixer();
-        return;
-    }
-
-    if ((err = snd_mixer_load(mixer_handle)) < 0)
-    {
-        Warn(QString("Mixer load error %1: %2")
-             .arg(err).arg(snd_strerror(err)));
-        CloseMixer();
-        return;
+        if (!AlsaBad(snd_pcm_drop(pcm.handle), "pcm drop failed"))
+            if (!AlsaBad(snd_pcm_prepare(pcm.handle), "pcm prepare failed"))
+                isgood = true;
+        VERBOSE(VB_AUDIO, LOC + pcm.logtag
+                + "xrun recovery " + ((isgood) ? "good" : "not good"));
     }
+    return isgood;
 }
 
-ALSAVolumeInfo AudioOutputALSA::GetVolumeRange(snd_mixer_elem_t *elem) const
+bool AudioOutputALSA::AlsaBad(int op_result, QString err_msg, bool warn_only)
 {
-    long volume_min, volume_max;
-
-    int err = snd_mixer_selem_get_playback_volume_range(
-        elem, &volume_min, &volume_max);
-
-    if (err < 0)
+    bool isbad = (op_result < 0); // (op_result < 0) => true return
+    if (isbad)
     {
-        static bool first_time = true;
-        if (first_time)
-        {
-            VERBOSE(VB_IMPORTANT,
-                    "snd_mixer_selem_get_playback_volume_range()" + ENO);
-            first_time = false;
-        }
+        QString loc_tag = (warn_only) ? LOC_WARN : LOC_ERR;
+        VERBOSE(VB_IMPORTANT, loc_tag + pcm.logtag + err_msg
+                + QString(": %1").arg(snd_strerror(op_result)));
     }
-
-    ALSAVolumeInfo vinfo(volume_min, volume_max);
-
-    VERBOSE(VB_AUDIO, QString("Volume range is %1 to %2, mult=%3")
-            .arg(vinfo.volume_min).arg(vinfo.volume_max)
-            .arg(vinfo.range_multiplier));
-
-    return vinfo;
+    return isbad;
 }
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
--- mythtv.orig/libs/libmyth/audiooutputalsa.h
+++ mythtv/libs/libmyth/audiooutputalsa.h
@@ -1,94 +1,82 @@
+/*
+ * Copyright (C) <=2008 unattributed author(s)
+ * Copyright (C) 2008  Alan Calvert
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
 #ifndef AUDIOOUTPUTALSA
 #define AUDIOOUTPUTALSA
 
-#define ALSA_PCM_NEW_HW_PARAMS_API
-#define ALSA_PCM_NEW_SW_PARAMS_API
-#include <alsa/asoundlib.h>
-
-#include "audiooutputbase.h"
-
 using namespace std;
 
-class ALSAVolumeInfo
-{
-  public:
-    ALSAVolumeInfo(long  playback_vol_min,
-                   long  playback_vol_max) :
-        range_multiplier(1.0f),
-        volume_min(playback_vol_min), volume_max(playback_vol_max)
-    {
-        float range = (float) (volume_max - volume_min);
-        if (range > 0.0f)
-            range_multiplier = 100.0f / range;
-        range_multiplier_inv = 1.0f / range_multiplier;
-    }
-
-    int ToMythRange(long alsa_volume)
-    {
-        long toz = alsa_volume - volume_min;
-        int val = (int) (toz * range_multiplier);
-        val = (val < 0)   ? 0   : val;
-        val = (val > 100) ? 100 : val;
-        return val;
-    }
-
-    long ToALSARange(int myth_volume)
-    {
-        float tos = myth_volume * range_multiplier_inv;
-        long val = (long) (tos + volume_min + 0.5);
-        val = (val < volume_min) ? volume_min : val;
-        val = (val > volume_max) ? volume_max : val;
-        return val;
-    }
-
-    float range_multiplier;
-    float range_multiplier_inv;
-    long  volume_min;
-    long  volume_max;
-};
+#include <alsa/asoundlib.h>
+#include "audiooutputbase.h"
 
 class AudioOutputALSA : public AudioOutputBase
 {
   public:
     AudioOutputALSA(const AudioSettings &settings);
-    virtual ~AudioOutputALSA();
+    ~AudioOutputALSA();
 
-    // Volume control
-    virtual int GetVolumeChannel(int channel) const; // Returns 0-100
-    virtual void SetVolumeChannel(int channel, int volume); // range 0-100 for vol
+    void Reset(void);
+    void Pause(bool paused);
+    void Drain(void);
+    int GetVolumeChannel(int channel) const;        //  volume range 0-100
+    void SetVolumeChannel(int channel, int volume); //
 
-    
   protected:
-    // You need to implement the following functions
-    virtual bool OpenDevice(void);
-    virtual void CloseDevice(void);
-    virtual void WriteAudio(unsigned char *aubuf, int size);
-    virtual int  GetSpaceOnSoundcard(void) const;
-    virtual int  GetBufferedOnSoundcard(void) const;
-
-  private:
-    inline int SetParameters(snd_pcm_t *handle,
-                             snd_pcm_format_t format, unsigned int channels,
-                             unsigned int rate, unsigned int buffer_time,
-                             unsigned int period_time);
-
-
-    // Volume related
-    void SetCurrentVolume(QString control, int channel, int volume);
-    void OpenMixer(bool setstartingvolume);
-    void CloseMixer(void);
-    void SetupMixer(void);
-    ALSAVolumeInfo GetVolumeRange(snd_mixer_elem_t *elem) const;
+    bool OpenDevice(void);
+    void CloseDevice(void);
+    void WriteAudio(unsigned char *aubuf, int size);
+    int  GetSpaceOnSoundcard(void) const;
+    int  GetBufferedOnSoundcard(void) const;
 
   private:
-    snd_pcm_t   *pcm_handle;
-    int          numbadioctls;
-    QMutex       killAudioLock;
-    snd_mixer_t *mixer_handle;
-    QString      mixer_control; // e.g. "PCM"
-    snd_pcm_sframes_t (*pcm_write_func)(snd_pcm_t*, const void*, 
-                                        snd_pcm_uframes_t);
+    bool PrepPCM(void);
+    bool PrepHwparams(void);
+    bool PrepSwparams(void);
+    bool PrepMixer(void);
+    void WriteMmap(unsigned char *data, snd_pcm_uframes_t nframes);
+    void WriteRw(unsigned char *data, snd_pcm_uframes_t nframes);
+    bool Recovery(int err);
+    bool XrunRecovery(void);
+    bool AlsaBad(int op_result, QString err_msg, bool warn_only = false);
+
+    struct {
+        QByteArray            device;
+        snd_pcm_t*            handle;
+        unsigned int          sample_rate;
+        snd_pcm_uframes_t     period_size;
+        snd_pcm_uframes_t     buffer_size;
+        int                   bytes_per_frame; // ! bytes_per_sample, grrr!
+        bool                  use_mmap;
+        QString               logtag;
+    } pcm;
+
+    struct {
+        QByteArray            device;
+        QByteArray            control;
+        snd_mixer_t*          handle;
+        snd_mixer_elem_t*     elem;
+        long                  volmin;
+        long                  volmax;
+        long                  volrange;
+        QString               logtag;
+    } mixer;
 };
-
 #endif
-
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
--- mythtv.orig/programs/mythfrontend/globalsettings.cpp
+++ mythtv/programs/mythfrontend/globalsettings.cpp
@@ -19,6 +19,10 @@
 #include <qdir.h>
 #include <qimage.h>
 
+#ifdef USING_ALSA
+#include <alsa/asoundlib.h>
+#endif
+
 // MythTV headers
 #include "mythconfig.h"
 #include "mythcontext.h"
@@ -39,11 +43,14 @@
 #include "mythdirs.h"
 #include "mythuihelper.h"
 
+#ifdef USING_ALSA
+static AlsaPlaybackPcms alsa_pcms;
+#endif
+
 static HostComboBox *AudioOutputDevice()
 {
     HostComboBox *gc = new HostComboBox("AudioOutputDevice", true);
     gc->setLabel(QObject::tr("Audio output device"));
-
 #ifdef USING_ALSA
     gc->addSelection("ALSA:default",       "ALSA:default");
     gc->addSelection("ALSA:spdif",         "ALSA:spdif");
@@ -52,6 +59,12 @@
     gc->addSelection("ALSA:digital",       "ALSA:digital");
     gc->addSelection("ALSA:mixed-analog",  "ALSA:mixed-analog");
     gc->addSelection("ALSA:mixed-digital", "ALSA:mixed-digital");
+    for (int p = 0; p < alsa_pcms.pcm_count; ++p)
+        if (strncasecmp(alsa_pcms.pcm_tags[p], "iec958:", 7) != 0)
+        {
+            QByteArray sel = "ALSA:" + QByteArray(alsa_pcms.pcm_tags[p]);
+            gc->addSelection(sel.constData(), sel.constData());
+        }
 #endif
 #ifdef USING_OSS
     QDir dev("/dev", "dsp*", QDir::Name, QDir::System);
@@ -127,7 +140,14 @@
 #ifndef USING_MINGW
     gc->addSelection("ALSA:iec958:{ AES0 0x02 }", "ALSA:iec958:{ AES0 0x02 }");
 #endif
-
+#ifdef USING_ALSA
+    for (int p = 0; p < alsa_pcms.pcm_count; ++p)
+        if (!strncasecmp(alsa_pcms.pcm_tags[p], "iec958:", 7))
+        {
+            QByteArray sel = "ALSA:" + QByteArray(alsa_pcms.pcm_tags[p]);
+            gc->addSelection(sel.constData(), sel.constData());
+        }
+#endif
     gc->setHelpText(QObject::tr("Audio output device to use for AC3 and "
                     "DTS passthrough. Default is the same as Audio output "
                     "device. This value is currently only used with ALSA "
@@ -5048,4 +5068,45 @@
     addChild(xboxset);
 }
 
+#ifdef USING_ALSA
+AlsaPlaybackPcms::AlsaPlaybackPcms()
+{
+    for (int p = 0; p < ALSA_MAX_PCMS; ++p)
+        pcm_tags[p] = NULL;
+    pcm_count = 0;
+    snd_ctl_card_info_t *ctl_info = NULL;
+    if (snd_ctl_card_info_malloc(&ctl_info) == 0)
+    {
+        char* io;
+        void** hints = NULL;
+        void** n = NULL;
+        if (!(snd_device_name_hint(-1, "pcm", &hints) < 0))
+        {
+            n = hints;
+            while (*n != NULL && pcm_count < ALSA_MAX_PCMS)
+            {
+                io = snd_device_name_get_hint(*n, "IOID");
+                if (io == NULL || (io != NULL && strcmp(io, "Output")))
+                    pcm_tags[pcm_count++] = snd_device_name_get_hint(*n, "NAME");
+                                            // memory hereby allocated is
+                                            // resolved in the destructor
+                if (io != NULL)
+                    free(io);
+                n++;
+            }
+            snd_device_name_free_hint(hints);
+        }
+    }
+    if (ctl_info != NULL)
+        snd_ctl_card_info_free(ctl_info);
+}
+
+AlsaPlaybackPcms::~AlsaPlaybackPcms()
+{
+    for( int p = 0; p < ALSA_MAX_PCMS; ++p)
+        if (pcm_tags[p] != NULL)
+            free(pcm_tags[p]);
+}
+
+#endif
 // vim:set sw=4 ts=4 expandtab:
--- mythtv.orig/programs/mythfrontend/globalsettings.h
+++ mythtv/programs/mythfrontend/globalsettings.h
@@ -165,4 +165,16 @@
 };
 #endif // USING_IVTV
 
+#ifdef USING_ALSA
+#define ALSA_MAX_PCMS 24
+
+class AlsaPlaybackPcms
+{
+  public:
+    AlsaPlaybackPcms();
+    ~AlsaPlaybackPcms();
+    char* pcm_tags[ALSA_MAX_PCMS];
+    int pcm_count;
+};
+#endif // USING_ALSA
 #endif
