Ticket #5729: alt_audiooutalsa.v1.patch

File alt_audiooutalsa.v1.patch, 54.6 KB (added by Dibblah, 16 years ago)

Same as above, uncompressed, for ease of review

  • libs/libmyth/audiooutputalsa.cpp

    old new  
    1 #include <cstdio>
    2 #include <cstdlib>
    3 #include <sys/time.h>
    4 #include <time.h>
    5 #include "config.h"
     1/*
     2 * Copyright (C) <=2008 unattributed author(s)
     3 * Copyright (C) 2008  Alan Calvert
     4 *
     5 * This program is free software; you can redistribute it and/or
     6 * modify it under the terms of the GNU General Public License
     7 * as published by the Free Software Foundation; either version 2
     8 * of the License, or (at your option) any later version.
     9 *
     10 * This program is distributed in the hope that it will be useful,
     11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13 * GNU General Public License for more details.
     14 *
     15 * You should have received a copy of the GNU General Public License
     16 * along with this program; if not, write to the Free Software
     17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
     18 * 02110-1301, USA.
     19 */
    620
    721using namespace std;
    822
    923#include "mythcontext.h"
    1024#include "audiooutputalsa.h"
    1125   
    12 #define LOC QString("ALSA: ")
    13 #define LOC_WARN QString("ALSA, Warning: ")
    14 #define LOC_ERR QString("ALSA, Error: ")
    15 
    16 // redefine assert as no-op to quiet some compiler warnings
    17 // about assert always evaluating true in alsa headers.
    18 #undef assert
    19 #define assert(x)
     26#define LOC QString("AudioOutputALSA: ")
     27#define LOC_WARN QString("AudioOutputALSA Warn: ")
     28#define LOC_ERR QString("AudioOutputALSA Error: ")
    2029
    2130AudioOutputALSA::AudioOutputALSA(const AudioSettings &settings) :
    22     AudioOutputBase(settings),
    23     pcm_handle(NULL),
    24     numbadioctls(0),
    25     mixer_handle(NULL),
    26     mixer_control(QString::null)
     31    AudioOutputBase(settings)
    2732{
    28     // Set everything up
     33    pcm.device = (audio_passthru)
     34                  ? QByteArray(audio_passthru_device.toAscii())
     35                  : QByteArray(audio_main_device.toAscii());
     36    if (pcm.device.isEmpty())
     37        pcm.device = "default";
     38    pcm.handle = NULL;
     39    pcm.logtag = "pcm '" + pcm.device + "' ";
     40    pcm.sample_rate = settings.samplerate;
     41    pcm.use_mmap = false;
     42
     43    mixer.device = gContext->GetSetting("MixerDevice", "default");
     44    if (mixer.device.startsWith("ALSA:"))
     45        mixer.device.remove(0, 5);
     46    mixer.handle = NULL;
     47    mixer.control = gContext->GetSetting("MixerControl", "PCM");
     48    mixer.elem = NULL;
     49    mixer.logtag = "mixer '" + mixer.device + "' ";
     50
    2951    Reconfigure(settings);
    3052}
    3153
     
    3658
    3759bool AudioOutputALSA::OpenDevice()
    3860{
    39     snd_pcm_format_t format;
    40     unsigned int buffer_time, period_time;
    41     int err;
    42 
    43     if (pcm_handle != NULL)
    44         CloseDevice();
    45 
    46     pcm_handle = NULL;
    47     numbadioctls = 0;
    48 
    49     QString real_device = (audio_passthru) ?
    50         audio_passthru_device : audio_main_device;
    51 
    52     VERBOSE(VB_GENERAL, QString("Opening ALSA audio device '%1'.")
    53             .arg(real_device));
    54 
    55     QByteArray dev_ba = real_device.toLocal8Bit();
    56     err = snd_pcm_open(&pcm_handle, dev_ba.constData(),
    57                        SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
    58 
    59     if (err < 0)
    60     {
    61         Error(QString("snd_pcm_open(%1): %2")
    62               .arg(real_device).arg(snd_strerror(err)));
    63 
    64         if (pcm_handle)
    65             CloseDevice();
     61    if (!PrepPCM())
    6662        return false;
    67     }
    68 
    69     /* the audio fragment size was computed by using the next lower power of 2
    70        of the following:
    71 
    72        const int video_frame_rate = 30;
    73        const int bits_per_byte = 8;
    74        int fbytes = (audio_bits * audio_channels * audio_samplerate) /
    75                     (bits_per_byte * video_frame_rate);
    76                    
    77         For telephony apps, a much shorter fragment size is needed to reduce the
    78         delay, and fragments should be multiples of the RTP packet size (10ms).
    79         20ms delay should be the max introduced by the driver, which equates
    80         to 320 bytes at 8000 samples/sec and mono 16-bit samples
    81     */
    82     if (source == AUDIOOUTPUT_TELEPHONY)
    83     {
    84         fragment_size = 320;
    85         buffer_time = 80000;  // 80 ms
    86         period_time = buffer_time / 4;  // 20ms
    87     }
    88     else
     63    if (!PrepMixer())
    8964    {
    90         fragment_size = 1536 * audio_channels * audio_bits / 8;
    91         period_time = 25000;  // in usec, interrupt period time
    92         // in usec, for driver buffer alloc (64k max)
    93         buffer_time = period_time * 16;
    94     }
    95 
    96     if (audio_bits == 8)
    97         format = SND_PCM_FORMAT_S8;
    98     else if (audio_bits == 16)
    99         // is the sound data coming in really little-endian or is it
    100         // CPU-endian?
    101 #ifdef WORDS_BIGENDIAN
    102         format = SND_PCM_FORMAT_S16;
    103 #else
    104         format = SND_PCM_FORMAT_S16_LE;
    105 #endif
    106     else if (audio_bits == 24)
    107 #ifdef WORDS_BIGENDIAN
    108         format = SND_PCM_FORMAT_S24;
    109 #else
    110         format = SND_PCM_FORMAT_S24_LE;
    111 #endif
    112     else
    113     {
    114         Error(QString("Unknown sample format: %1 bits.").arg(audio_bits));
     65        CloseDevice();
    11566        return false;
    11667    }
    117 
    118     err = SetParameters(pcm_handle,
    119                         format, audio_channels, audio_samplerate, buffer_time,
    120                         period_time);
    121     if (err < 0)
     68    if (AlsaBad(snd_pcm_prepare(pcm.handle), "prepare failed"))
    12269    {
    123         Error("Unable to set ALSA parameters");
    12470        CloseDevice();
    12571        return false;
    126     }   
    127 
    128     // make us think that soundcard buffer is 4 fragments smaller than
    129     // it really is
    130     audio_buffer_unused = soundcard_buffer_size - (fragment_size * 4);
    131 
    132     if (internal_vol)
    133         OpenMixer(set_initial_vol);
    134    
    135     // Device opened successfully
     72    }
     73    pcm.bytes_per_frame = snd_pcm_frames_to_bytes(pcm.handle, 1);
     74    fragment_size = pcm.bytes_per_frame * pcm.period_size;
     75    soundcard_buffer_size = pcm.bytes_per_frame * pcm.buffer_size;
     76    VERBOSE(VB_AUDIO, LOC + pcm.logtag
     77            + QString("duly opened: fragment size %1, "
     78                      "soundcard buffer size %2 (in bytes)")
     79                      .arg(fragment_size).arg(soundcard_buffer_size));
    13680    return true;
    13781}
    13882
    13983void AudioOutputALSA::CloseDevice()
    14084{
    141     CloseMixer();
    142     if (pcm_handle != NULL)
    143     {
    144         snd_pcm_close(pcm_handle);
    145         pcm_handle = NULL;
    146     }
     85    if (mixer.handle != NULL
     86        && !AlsaBad(snd_mixer_close(mixer.handle), "close mixer failed"))
     87            VERBOSE(VB_AUDIO, LOC + mixer.logtag + "duly closed");
     88    mixer.handle = NULL;
     89    if (pcm.handle != NULL
     90        && !AlsaBad(snd_pcm_close(pcm.handle), "close pcm failed"))
     91            VERBOSE(VB_AUDIO, LOC + pcm.logtag + "duly closed");
     92    pcm.handle = NULL;
    14793}
    14894
    149 
    15095void AudioOutputALSA::WriteAudio(unsigned char *aubuf, int size)
    15196{
    152     unsigned char *tmpbuf;
    153     int lw = 0;
    154     int frames = size / audio_bytes_per_sample;
    155 
    156     if (pcm_handle == NULL)
    157     {
    158         VERBOSE(VB_IMPORTANT, QString("WriteAudio() called with pcm_handle == NULL!"));
     97    if (audio_actually_paused || pcm.handle == NULL)
    15998        return;
    160     }
    161    
    162     tmpbuf = aubuf;
    163 
    164     VERBOSE(VB_AUDIO+VB_TIMESTAMP,
    165             QString("WriteAudio: Preparing %1 bytes (%2 frames)")
    166             .arg(size).arg(frames));
    167    
    168     while (frames > 0)
     99    snd_pcm_state_t state = snd_pcm_state(pcm.handle);
     100    switch (state)
    169101    {
    170         lw = pcm_write_func(pcm_handle, tmpbuf, frames);
    171        
    172         if (lw >= 0)
    173         {
    174             if (lw < frames)
    175                 VERBOSE(VB_AUDIO, QString("WriteAudio: short write %1 bytes (ok)")
    176                         .arg(lw * audio_bytes_per_sample));
    177 
    178             frames -= lw;
    179             tmpbuf += lw * audio_bytes_per_sample; // bytes
    180         }
    181         else if (lw == -EAGAIN)
    182         {
    183             VERBOSE(VB_AUDIO, QString("WriteAudio: device is blocked - waiting"));
    184 
    185             snd_pcm_wait(pcm_handle, 10);
    186         }
    187         else if (lw == -EPIPE &&
    188                  snd_pcm_state(pcm_handle) == SND_PCM_STATE_XRUN)
    189         {
    190             VERBOSE(VB_IMPORTANT, "WriteAudio: buffer underrun");
    191 
    192             if ((lw = snd_pcm_prepare(pcm_handle)) < 0)
    193             {
    194                 Error(QString("WriteAudio: unable to recover from xrun: %1")
    195                       .arg(snd_strerror(lw)));
    196                 return;
    197             }
    198         }
    199         else if (lw == -ESTRPIPE)
    200         {
    201             VERBOSE(VB_IMPORTANT, "WriteAudio: device is suspended");
    202 
    203             while ((lw = snd_pcm_resume(pcm_handle)) == -EAGAIN)
    204                 usleep(200);
    205 
    206             if (lw < 0)
    207             {
    208                 VERBOSE(VB_IMPORTANT, "WriteAudio: resume failed");
    209 
    210                 if ((lw = snd_pcm_prepare(pcm_handle)) < 0)
    211                 {
    212                     Error(QString("WriteAudio: unable to recover from suspend: %1")
    213                           .arg(snd_strerror(lw)));
    214                     return;
    215                 }
    216             }
    217         }
    218         else if (lw == -EBADFD)
    219         {
    220             VERBOSE(VB_IMPORTANT,
    221                     QString("WriteAudio: device is in a bad state (state = %1)")
    222                     .arg(snd_pcm_state(pcm_handle)));
    223             return;
    224         }
    225         else
    226         {
    227             VERBOSE(VB_IMPORTANT, QString("pcm_write_func: %1 (%2)")
    228                     .arg(snd_strerror(lw)).arg(lw));
    229             VERBOSE(VB_IMPORTANT, QString("WriteAudio: snd_pcm_state == %1")
    230                     .arg(snd_pcm_state(pcm_handle)));
    231 
    232             // CloseDevice();
    233             return;
    234         }
     102        case SND_PCM_STATE_XRUN:
     103        case SND_PCM_STATE_SUSPENDED:
     104            if (!XrunRecovery())
     105                break;
     106        case SND_PCM_STATE_PREPARED:
     107            if (AlsaBad(snd_pcm_start(pcm.handle), "pcm start failed"))
     108                break;
     109        case SND_PCM_STATE_RUNNING:
     110            if (pcm.use_mmap)
     111                WriteMmap(aubuf, size / pcm.bytes_per_frame);
     112            else
     113                WriteRw(aubuf, size / pcm.bytes_per_frame);
     114            break;
     115
     116        default:
     117            VERBOSE(VB_IMPORTANT, LOC_ERR + pcm.logtag
     118                    + QString("alarming SND_PCM_STATE %1 through WriteAudio()")
     119                              .arg(state));
     120            break;
    235121    }
    236122}
    237123
    238124int AudioOutputALSA::GetBufferedOnSoundcard(void) const
    239 {
    240     if (pcm_handle == NULL)
    241     {
    242         VERBOSE(VB_IMPORTANT, QString("getBufferedOnSoundcard() called with pcm_handle == NULL!"));
     125{
     126    if (pcm.handle == NULL)
    243127        return 0;
    244     }
    245 
    246     // this should be more like what you want, previously this function
    247     // was returning the soundcard buffer size -dag
    248 
    249     snd_pcm_sframes_t delay = 0;
    250 
    251     snd_pcm_state_t state = snd_pcm_state(pcm_handle);
    252     if (state == SND_PCM_STATE_RUNNING ||
    253         state == SND_PCM_STATE_DRAINING)
    254     {
    255         snd_pcm_delay(pcm_handle, &delay);
    256     }
    257 
    258     if (delay < 0)
    259         delay = 0;
    260 
    261     int buffered = delay * audio_bytes_per_sample;
    262128
     129    int buffered = 0;
     130    snd_pcm_sframes_t frames = snd_pcm_avail_update(pcm.handle);
     131    if (frames < 0)
     132        VERBOSE(VB_IMPORTANT, LOC_ERR + pcm.logtag
     133                + QString("GetBufferedOnSoundcard query buffer status "
     134                          "failed: %1").arg(snd_strerror(frames)));
     135    else
     136        buffered = (pcm.buffer_size - frames) * pcm.bytes_per_frame;
    263137    return buffered;
    264138}
    265139
    266 
    267140int AudioOutputALSA::GetSpaceOnSoundcard(void) const
    268141{
    269     if (pcm_handle == NULL)
    270     {
    271         VERBOSE(VB_IMPORTANT, QString("GetSpaceOnSoundcard() ") +
    272                 "called with pcm_handle == NULL!");
    273 
     142    if (pcm.handle == NULL)
    274143        return 0;
    275     }
    276 
    277     snd_pcm_sframes_t avail, delay;
    278 
    279     snd_pcm_state_t state = snd_pcm_state(pcm_handle);
    280     if (state == SND_PCM_STATE_RUNNING ||
    281         state == SND_PCM_STATE_DRAINING)
    282     {
    283         snd_pcm_delay(pcm_handle, &delay);
    284     }
    285 
    286     avail = snd_pcm_avail_update(pcm_handle);
    287     if (avail < 0 ||
    288         (snd_pcm_uframes_t)avail > (snd_pcm_uframes_t)soundcard_buffer_size)
    289         avail = soundcard_buffer_size;
    290 
    291     int space = (avail * audio_bytes_per_sample) - audio_buffer_unused;
    292 
    293     if (space < 0)
    294         space = 0;
    295144
     145    int space = 0;
     146    snd_pcm_sframes_t frames;
     147    if ((frames = snd_pcm_avail_update(pcm.handle)) < 0)
     148        VERBOSE(VB_IMPORTANT, LOC_ERR + pcm.logtag
     149                + QString("GetSpaceOnSoundcard query buffer status failed: %1")
     150                          .arg(snd_strerror(frames)));
     151    else
     152        space = frames * pcm.bytes_per_frame;
    296153    return space;
    297154}
    298155
    299 
    300 int AudioOutputALSA::SetParameters(snd_pcm_t *handle,
    301                                    snd_pcm_format_t format, unsigned int channels,
    302                                    unsigned int rate, unsigned int buffer_time,
    303                                    unsigned int period_time)
    304 {
    305     int err, dir;
    306     snd_pcm_hw_params_t *params;
    307     snd_pcm_sw_params_t *swparams;
    308     snd_pcm_uframes_t buffer_size;
    309     snd_pcm_uframes_t period_size;
    310 
    311     VERBOSE(VB_AUDIO, QString("in SetParameters(format=%1, channels=%2, "
    312                               "rate=%3, buffer_time=%4, period_time=%5)")
    313             .arg(format).arg(channels).arg(rate).arg(buffer_time).arg(period_time));
    314 
    315     if (handle == NULL)
    316     {
    317         VERBOSE(VB_IMPORTANT, QString("SetParameters() called with handle == NULL!"));
     156int AudioOutputALSA::GetVolumeChannel(int channel) const
     157{
     158    if (mixer.elem == NULL)
    318159        return 0;
    319     }
    320        
    321     snd_pcm_hw_params_alloca(&params);
    322     snd_pcm_sw_params_alloca(&swparams);
    323    
    324     /* choose all parameters */
    325     if ((err = snd_pcm_hw_params_any(handle, params)) < 0)
    326     {
    327         Error(QString("Broken configuration for playback; no configurations"
    328               " available: %1").arg(snd_strerror(err)));
    329         return err;
    330     }
    331160
    332     /* set the interleaved read/write format, use mmap if available */
    333     pcm_write_func = &snd_pcm_mmap_writei;
    334     err = snd_pcm_hw_params_set_access(
    335         handle, params, SND_PCM_ACCESS_MMAP_INTERLEAVED);
    336     if (err < 0)
     161    int retvol = 0;
     162    if (channel > SND_MIXER_SCHN_UNKNOWN && channel < audio_channels)
    337163    {
    338         VERBOSE(VB_GENERAL, LOC_WARN +
    339                 "mmap not available, attempting to fall back to slow writes.");
    340         QString old_err = snd_strerror(err);
    341         pcm_write_func = &snd_pcm_writei;
    342         err = snd_pcm_hw_params_set_access(
    343             handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
    344         if (err < 0)
    345         {
    346             Error("Interleaved sound types MMAP & RW are not available");
    347             VERBOSE(VB_IMPORTANT,
    348                     QString("MMAP Error: %1\n\t\t\tRW Error: %2")
    349                     .arg(old_err).arg(snd_strerror(err)));
    350             return err;
     164        int chk;
     165        snd_mixer_selem_channel_id_t chx = (snd_mixer_selem_channel_id_t)channel;
     166        long mixvol;
     167        chk = snd_mixer_selem_get_playback_volume(mixer.elem, chx, &mixvol);
     168        if (chk < 0)
     169           VERBOSE(VB_IMPORTANT, LOC_ERR + mixer.logtag
     170                    + QString("failed to get channel %2 volume: %3")
     171                              .arg(channel).arg(snd_strerror(chk)));
     172        else if (mixer.volrange > 0L)
     173        {
     174            retvol = (mixvol - mixer.volmin) * 100.0f / mixer.volrange + .5f;
     175            retvol = max(retvol, 0);
     176            retvol = min(retvol, 100);
     177            VERBOSE(VB_AUDIO+VB_EXTRA, LOC + mixer.logtag
     178                    + QString("get volume channel %1: %2 (mixer volume: %3")
     179                              .arg(channel).arg(retvol).arg(mixvol));
    351180        }
    352181    }
     182    else
     183        VERBOSE(VB_IMPORTANT, LOC_ERR + mixer.logtag
     184                + QString("get volume, invalid channel: %1").arg(channel));
     185    return retvol;
     186}
    353187
    354     /* set the sample format */
    355     if ((err = snd_pcm_hw_params_set_format(handle, params, format)) < 0)
    356     {
    357         Error(QString("Sample format not available: %1")
    358               .arg(snd_strerror(err)));
    359         return err;
    360     }
     188void AudioOutputALSA::SetVolumeChannel(int channel, int volume)
     189{
     190    if (!internal_vol || mixer.elem == NULL)
     191        return;
    361192
    362     /* set the count of channels */
    363     if ((err = snd_pcm_hw_params_set_channels(handle, params, channels)) < 0)
     193    if (channel > SND_MIXER_SCHN_UNKNOWN && channel < audio_channels)
    364194    {
    365         Error(QString("Channels count (%1) not available: %2")
    366               .arg(channels).arg(snd_strerror(err)));
    367         return err;
     195        snd_mixer_selem_channel_id_t chx =
     196            (snd_mixer_selem_channel_id_t)channel;
     197        long mixvol = volume * mixer.volrange / 100.0f - mixer.volmin + 0.5f;
     198        mixvol = max(mixvol, mixer.volmin);
     199        mixvol = min(mixvol, mixer.volmax);
     200        if (!AlsaBad(snd_mixer_selem_set_playback_volume(mixer.elem, chx, mixvol),
     201                     QString("failed to set channel %1 volume -> %2")
     202                             .arg(channel).arg(volume)))
     203            VERBOSE(VB_AUDIO, LOC + mixer.logtag
     204                    + QString("set channel %1 volume -> %2 (mixer volume: %3)")
     205                              .arg(channel).arg(volume).arg(mixvol));
    368206    }
     207    else
     208        VERBOSE(VB_IMPORTANT, LOC_ERR + mixer.logtag
     209                + QString("set volume, invalid channel: %1").arg(channel));
     210}
    369211
    370     /* set the stream rate */
    371     unsigned int rrate = rate;
    372     if ((err = snd_pcm_hw_params_set_rate_near(handle, params, &rrate, 0)) < 0)
     212void AudioOutputALSA::Reset(void)
     213{
     214    if (pcm.handle != NULL)
    373215    {
    374         Error(QString("Samplerate (%1Hz) not available: %2")
    375               .arg(rate).arg(snd_strerror(err)));
    376         return err;
     216        AlsaBad(snd_pcm_drop(pcm.handle), "Reset drop failed");
     217        AlsaBad(snd_pcm_prepare(pcm.handle), "Reset prepare failed");
     218        AudioOutputBase::Reset();
     219        VERBOSE(VB_AUDIO+VB_EXTRA, LOC + pcm.logtag + "duly reset");
    377220    }
     221}
    378222
    379     if (rrate != rate)
    380     {
    381         Error(QString("Rate doesn't match (requested %1Hz, got %2Hz)")
    382               .arg(rate).arg(rrate));
    383         return -EINVAL;
    384     }
     223void AudioOutputALSA::Pause(bool paused)
     224{
     225    if (pcm.handle == NULL)
     226        return;
    385227
    386     /* set the buffer time */
    387     if ((err = snd_pcm_hw_params_set_buffer_time_near(handle, params,
    388                                                      &buffer_time, &dir)) < 0)
    389     {
    390         Error(QString("Unable to set buffer time %1 for playback: %2")
    391               .arg(buffer_time).arg(snd_strerror(err)));
    392         return err;
     228    if (paused && !audio_actually_paused)
     229     {
     230        AlsaBad(snd_pcm_drop(pcm.handle), "pause drop failed");
     231        pauseaudio = paused;
     232        audio_actually_paused = true;
     233        VERBOSE(VB_AUDIO+VB_EXTRA, LOC + pcm.logtag + "actually paused");
     234    }
     235    else if (!paused && audio_actually_paused)
     236     {
     237        QString tag = "(un)Pause ";
     238        AlsaBad(snd_pcm_drop(pcm.handle), tag + "unpause drop failed");
     239        AlsaBad(snd_pcm_prepare(pcm.handle), tag + "unpause prepare failed");
     240        pauseaudio = paused;
     241        audio_actually_paused = false;
     242        VERBOSE(VB_AUDIO+VB_EXTRA, LOC + pcm.logtag + "actually unpaused");
    393243    }
     244}
    394245
    395     if ((err = snd_pcm_hw_params_get_buffer_size(params, &buffer_size)) < 0)
    396     {
    397         Error(QString("Unable to get buffer size for playback: %1")
    398               .arg(snd_strerror(err)));
    399         return err;
    400     } else {
    401         VERBOSE(VB_AUDIO, QString("get_buffer_size returned %1").arg(buffer_size));
     246void AudioOutputALSA::Drain(void)
     247{
     248    AudioOutputBase::Drain();
     249    if (pcm.handle != NULL)
     250        AlsaBad(snd_pcm_drain(pcm.handle), "pcm drain failed");
     251}
     252
     253bool AudioOutputALSA::PrepPCM()
     254{
     255    if (pcm.handle != NULL)
     256        snd_pcm_close(pcm.handle);
     257    AlsaBad(snd_config_update_free_global(), "failed to update snd config");
     258    QRegExp built_in_regx("^(front|surround\\d{2}|hdmi|rear|center_lfe|iec958)"
     259                         ":(CARD=\\w+,DEV=\\d)|(DEV=\\d,CARD=\\w+)");
     260    bool is_built_in = QString(pcm.device).contains(built_in_regx);
     261    QRegExp plug_regx("^(plug|plughw):\\w+");
     262    bool is_plug = QString(pcm.device).contains(plug_regx);
     263
     264    while (true)
     265    {
     266        pcm.logtag = "pcm '" + pcm.device + "' ";
     267        if (AlsaBad(snd_pcm_open(&pcm.handle, pcm.device.constData(),
     268                                 SND_PCM_STREAM_PLAYBACK,
     269                                 SND_PCM_NO_AUTO_CHANNELS),
     270                    "failed to open device"))
     271        {
     272            pcm.handle = NULL;
     273            return false;
     274        }
     275        if (AlsaBad(snd_pcm_nonblock(pcm.handle, 1), "set nonblock failed"))
     276        {
     277            snd_pcm_close(pcm.handle);
     278            pcm.handle = NULL;
     279            return false;
     280        }
     281        if (PrepHwparams())
     282            break;
     283        else if (!is_plug && !is_built_in) // try again with 'plug:'
     284        {
     285            AlsaBad(snd_pcm_close(pcm.handle), "plug retry, close failed");
     286            pcm.handle = NULL;
     287            pcm.device.prepend("plug:");
     288            is_plug = true;
     289            continue;
     290        }
     291        else
     292        {
     293            snd_pcm_close(pcm.handle);
     294            pcm.handle = NULL;
     295            return false;
     296        }
    402297    }
    403     soundcard_buffer_size = buffer_size * audio_bytes_per_sample;
    404 
    405     /* set the period time */
    406     if ((err = snd_pcm_hw_params_set_period_time_near(
    407                     handle, params, &period_time, &dir)) < 0)
     298    if (!PrepSwparams())
    408299    {
    409         Error(QString("Unable to set period time %1 for playback: %2")
    410               .arg(period_time).arg(snd_strerror(err)));
    411         return err;
    412     } else {
    413         VERBOSE(VB_AUDIO, QString("set_period_time_near returned %1").arg(period_time));
    414     }
    415 
    416     if ((err = snd_pcm_hw_params_get_period_size(params, &period_size,
    417                                                 &dir)) < 0) {
    418         Error(QString("Unable to get period size for playback: %1")
    419               .arg(snd_strerror(err)));
    420         return err;
    421     } else {
    422         VERBOSE(VB_AUDIO, QString("get_period_size returned %1").arg(period_size));
     300        snd_pcm_close(pcm.handle);
     301        return false;
    423302    }
     303    return true;
     304}
    424305
    425     /* write the parameters to device */
    426     if ((err = snd_pcm_hw_params(handle, params)) < 0) {
    427         Error(QString("Unable to set hw params for playback: %1")
    428               .arg(snd_strerror(err)));
    429         return err;
    430     }
    431    
    432     /* get the current swparams */
    433     if ((err = snd_pcm_sw_params_current(handle, swparams)) < 0)
    434     {
    435         Error(QString("Unable to determine current swparams for playback:"
    436                       " %1").arg(snd_strerror(err)));
    437         return err;
    438     }
    439     /* start the transfer after period_size */
    440     if ((err = snd_pcm_sw_params_set_start_threshold(handle, swparams,
    441                                                     period_size)) < 0)
    442     {
    443         Error(QString("Unable to set start threshold mode for playback: %1")
    444               .arg(snd_strerror(err)));
    445         return err;
    446     }
     306bool AudioOutputALSA::PrepHwparams(void)
     307{
     308    bool result = false;
     309    snd_pcm_hw_params_t*  hwparams;
     310    snd_pcm_hw_params_alloca(&hwparams);
     311    snd_pcm_access_t axs;
     312    snd_pcm_format_t format;
     313    unsigned int rate, latency, period_time, minchan, maxchan, periods;
    447314
    448     /* allow the transfer when at least period_size samples can be processed */
    449     if ((err = snd_pcm_sw_params_set_avail_min(handle, swparams,
    450                                               period_size)) < 0)
     315    if (AlsaBad(snd_pcm_hw_params_any(pcm.handle, hwparams),
     316                "no playback configurations available"))
     317        goto fini;
     318    if (AlsaBad(snd_pcm_hw_params_set_periods_integer(pcm.handle, hwparams),
     319                "cannot restrict period size to integral value"))
     320        goto fini;
     321    pcm.use_mmap = false;
     322    axs = SND_PCM_ACCESS_MMAP_INTERLEAVED;
     323    if (!AlsaBad(snd_pcm_hw_params_set_access(pcm.handle, hwparams, axs),
     324                 "mmap io access not possible"))
     325        pcm.use_mmap = true;
     326    else
    451327    {
    452         Error(QString("Unable to set avail min for playback: %1")
    453               .arg(snd_strerror(err)));
    454         return err;
     328        axs = SND_PCM_ACCESS_RW_INTERLEAVED;
     329        if (AlsaBad(snd_pcm_hw_params_set_access(pcm.handle, hwparams, axs),
     330                     "failed to set mmap or rw access, both failed"))
     331            goto fini;
     332    }
     333    switch (audio_bits) // ALSA to append _LE/_BE accordingly
     334    {
     335        case 8:
     336            format = SND_PCM_FORMAT_U8;
     337            break;
     338        case 16:
     339        default:
     340            format = SND_PCM_FORMAT_S16;
     341            break;
     342    }
     343    if (AlsaBad(snd_pcm_hw_params_set_format(pcm.handle, hwparams, format),
     344                "failed to set sample format"))
     345        goto fini;
     346
     347    rate = pcm.sample_rate;
     348    if (AlsaBad(snd_pcm_hw_params_set_rate_near(pcm.handle, hwparams, &rate, NULL),
     349                QString("failed setting sample rate near %1").arg(rate)))
     350        goto fini;
     351    VERBOSE(VB_AUDIO+VB_EXTRA, LOC + pcm.logtag
     352            + QString("requested sample rate %1, set sample rate %2")
     353                      .arg(pcm.sample_rate).arg(rate));
     354    if (rate < pcm.sample_rate * 0.95f || rate > pcm.sample_rate * 1.05f)
     355    {
     356        AlsaBad(-EINVAL, QString("requested sample rate %1, set sample rate %2 "
     357                                 "- more than 5% off")
     358                                 .arg(pcm.sample_rate).arg(rate));
     359        goto fini;
     360    }
     361    if (AlsaBad(snd_pcm_hw_params_set_channels(pcm.handle, hwparams,
     362                                               audio_channels),
     363                QString("failed to set channels to %1").arg(audio_channels)))
     364        goto fini;
     365
     366    // latency, period, buffer prep based on Alsa fn() snd_pcm_set_params()
     367    latency = 50000; // 50msec
     368    if (!AlsaBad(snd_pcm_hw_params_set_buffer_time_near(pcm.handle, hwparams,
     369                                                        &latency, NULL),
     370                "initial buffer time/latency setting failed"))
     371    {
     372        if (AlsaBad(snd_pcm_hw_params_get_buffer_size(hwparams, &pcm.buffer_size),
     373                    "failed to get buffer size"))
     374            goto fini;
     375        if (AlsaBad(snd_pcm_hw_params_get_buffer_time(hwparams, &latency, NULL),
     376                    "failed to get latency setting"))
     377            goto fini;
     378        period_time = latency / 4;
     379        if (AlsaBad(snd_pcm_hw_params_set_period_time_near(pcm.handle, hwparams,
     380                                                           &period_time, NULL),
     381                    "failed to set period time"))
     382            goto fini;
     383        if (AlsaBad(snd_pcm_hw_params_get_period_size(hwparams, &pcm.period_size,
     384                                                      NULL),
     385                    "failed to get period size"))
     386            goto fini;
    455387    }
    456 
    457     /* align all transfers to 1 sample */
    458     if ((err = snd_pcm_sw_params_set_xfer_align(handle, swparams, 1)) < 0)
     388    else
    459389    {
    460         Error(QString("Unable to set transfer align for playback: %1")
    461               .arg(snd_strerror(err)));
    462         return err;
    463     }
     390        VERBOSE(VB_AUDIO, LOC + "hwparams settings, Plan B");
     391        period_time = latency / 4;
     392        if (AlsaBad(snd_pcm_hw_params_set_period_time_near(pcm.handle, hwparams,
     393                                                           &period_time, NULL),
     394                    "failed to set period time"))
     395            goto fini;
     396        if (AlsaBad(snd_pcm_hw_params_get_period_size(hwparams, &pcm.period_size,
     397                                                      NULL),
     398                    "failed to get period size"))
     399            goto fini;
     400        pcm.buffer_size = pcm.period_size * 4;
     401        if (AlsaBad(snd_pcm_hw_params_set_buffer_size_near(pcm.handle, hwparams,
     402                                                           &pcm.buffer_size),
     403                    "failed to set buffer size"))
     404            goto fini;
     405        if (AlsaBad(snd_pcm_hw_params_get_buffer_size(hwparams,
     406                                                      &pcm.buffer_size),
     407                    "failed to get buffer size"))
     408            goto fini;
     409    }
     410    if (AlsaBad(snd_pcm_hw_params (pcm.handle, hwparams),
     411                "failed to set hardware parameters"))
     412                goto fini;
     413
     414    result = true;
     415    periods = 0;
     416    AlsaBad(snd_pcm_hw_params_get_periods(hwparams, &periods, NULL),
     417            "failed to get periods");
     418    VERBOSE(VB_AUDIO, LOC + pcm.logtag
     419            + QString("latency %1msec, period size %2, periods %3, "
     420                      "buffer size %4 (in frames)")
     421                      .arg(latency /1000.0, 0, 'f', 1).arg(pcm.period_size)
     422                      .arg(periods).arg(pcm.buffer_size));
     423    VERBOSE(VB_AUDIO, LOC + pcm.logtag
     424            + QString("channels %1, using %2").arg(audio_channels)
     425                      .arg((pcm.use_mmap) ? "mmap write" : "snd_pcm_writei"));
     426    fini:
     427    return result;
     428}
    464429
    465     /* write the parameters to the playback device */
    466     if ((err = snd_pcm_sw_params(handle, swparams)) < 0)
    467     {
    468         Error(QString("Unable to set sw params for playback: %1")
    469               .arg(snd_strerror(err)));
    470         return err;
    471     }
     430bool AudioOutputALSA::PrepSwparams(void)
     431{
     432    snd_pcm_sw_params_t* swparams;
     433    snd_pcm_sw_params_alloca(&swparams);
     434        snd_pcm_uframes_t boundary;
     435    bool result = false;
    472436
    473     if ((err = snd_pcm_prepare(handle)) < 0)
    474         Error(QString("Initial pcm prepare err %1 %2")
    475               .arg(err).arg(snd_strerror(err)));
     437    if (AlsaBad(snd_pcm_sw_params_current(pcm.handle, swparams),
     438               "failed to get swparams"))
     439        goto fini;
     440
     441    // using explicit start, not auto start
     442    if (AlsaBad(snd_pcm_sw_params_set_start_threshold(pcm.handle, swparams,
     443                                                      INT_MAX),
     444                "failed to set start threshold"))
     445                goto fini;
     446   if (AlsaBad(snd_pcm_sw_params_get_boundary(swparams, &boundary),
     447               "failed to get boundary"))
     448        goto fini;
     449    if (AlsaBad(snd_pcm_sw_params_set_stop_threshold(pcm.handle, swparams,
     450                                                     boundary),
     451                "failed to set stop threshold"))
     452                goto fini;
     453    if (AlsaBad(snd_pcm_sw_params(pcm.handle, swparams),
     454                "failed to set software parameters"))
     455        goto fini;
     456
     457    result = true;
    476458
    477     return 0;
     459    fini:
     460    return result;
    478461}
    479462
    480 
    481 int AudioOutputALSA::GetVolumeChannel(int channel) const
     463bool AudioOutputALSA::PrepMixer(void)
    482464{
    483     long actual_volume;
    484 
    485     if (mixer_handle == NULL)
    486         return 100;
    487 
    488     QByteArray mix_ctl = mixer_control.toAscii();
    489     snd_mixer_selem_id_t *sid;
    490     snd_mixer_selem_id_alloca(&sid);
    491     snd_mixer_selem_id_set_index(sid, 0);
    492     snd_mixer_selem_id_set_name(sid, mix_ctl.constData());
     465    if (pcm.handle == NULL)
     466        return false;
    493467
    494     snd_mixer_elem_t *elem = snd_mixer_find_selem(mixer_handle, sid);
    495     if (!elem)
     468    int chk;
     469    if ((chk = snd_mixer_open(&mixer.handle, 0)) < 0)
    496470    {
    497         VERBOSE(VB_IMPORTANT, QString("Mixer unable to find control %1")
    498                 .arg(mixer_control));
    499         return 100;
     471        VERBOSE(VB_IMPORTANT, LOC_ERR + mixer.logtag
     472                + QString("failed to open: %2").arg(snd_strerror(chk)));
     473        return false;
    500474    }
    501 
    502     snd_mixer_selem_channel_id_t chan = (snd_mixer_selem_channel_id_t) channel;
    503     if (!snd_mixer_selem_has_playback_channel(elem, chan))
     475    struct snd_mixer_selem_regopt regopts =
     476        {1, SND_MIXER_SABSTRACT_NONE, mixer.device.constData(), NULL, NULL};
     477    if ((chk = snd_mixer_selem_register(mixer.handle, &regopts, NULL)) < 0)
     478    {
     479        VERBOSE(VB_IMPORTANT, LOC_ERR + mixer.logtag
     480                + QString("failed to register: %2").arg(snd_strerror(chk)));
     481        snd_mixer_close(mixer.handle);
     482        mixer.handle = NULL;
     483        return false;
     484    }
     485    if ((chk = snd_mixer_load(mixer.handle)) < 0)
    504486    {
    505         snd_mixer_selem_id_set_index(sid, channel);
    506         if ((elem = snd_mixer_find_selem(mixer_handle, sid)) == NULL)
    507         {
    508             VERBOSE(VB_IMPORTANT, QString("Mixer unable to find control %1 %2")
    509                     .arg(mixer_control).arg(channel));
    510             return 100;
    511         }
     487        VERBOSE(VB_IMPORTANT, LOC_ERR + mixer.logtag
     488                + QString("failed to load: %2").arg(snd_strerror(chk)));
     489        snd_mixer_close(mixer.handle);
     490        mixer.handle = NULL;
     491        return false;
    512492    }
    513 
    514     ALSAVolumeInfo vinfo = GetVolumeRange(elem);
    515 
    516     snd_mixer_selem_get_playback_volume(
    517         elem, (snd_mixer_selem_channel_id_t)channel, &actual_volume);
    518 
    519     return vinfo.ToMythRange(actual_volume);
    520 }
    521 
    522 void AudioOutputALSA::SetVolumeChannel(int channel, int volume)
    523 {
    524     SetCurrentVolume(mixer_control, channel, volume);
    525 }
    526 
    527 void AudioOutputALSA::SetCurrentVolume(QString control, int channel, int volume)
    528 {
    529     VERBOSE(VB_AUDIO, QString("Setting %1 volume to %2")
    530             .arg(control).arg(volume));
    531 
    532     if (!mixer_handle)
    533         return; // no mixer, nothing to do
    534 
    535     QByteArray ctl = control.toAscii();
    536     snd_mixer_selem_id_t *sid;
    537     snd_mixer_selem_id_alloca(&sid);
    538     snd_mixer_selem_id_set_index(sid, 0);
    539     snd_mixer_selem_id_set_name(sid, ctl.constData());
    540 
    541     snd_mixer_elem_t *elem = snd_mixer_find_selem(mixer_handle, sid);
    542     if (!elem)
     493    unsigned int elcount = snd_mixer_get_count(mixer.handle);
     494    snd_mixer_elem_t* elx = snd_mixer_first_elem(mixer.handle);
     495    mixer.elem = NULL;
     496    for (unsigned int ctr = 0; elx != NULL && ctr < elcount; ++ctr)
     497    {
     498        if (!strcmp(mixer.control.constData(), snd_mixer_selem_get_name(elx))
     499            && !snd_mixer_selem_is_enumerated(elx)
     500            && snd_mixer_selem_has_playback_volume(elx)
     501            && snd_mixer_selem_is_active(elx))
     502        {
     503            mixer.elem = elx;
     504            mixer.logtag += "control '" + mixer.control + "', ";
     505            VERBOSE(VB_AUDIO+VB_EXTRA, LOC + mixer.logtag
     506                    + QString("playback control '%1' selected")
     507                              .arg(mixer.control.constData()));
     508            break;
     509        }
     510        elx = snd_mixer_elem_next(elx);
     511    }
     512    if (mixer.elem == NULL)
     513    {
     514        VERBOSE(VB_IMPORTANT, LOC_ERR + mixer.logtag
     515                + QString("playback control '%1' not found")
     516                          .arg(mixer.control.constData()));
     517        snd_mixer_close(mixer.handle);
     518        mixer.handle = NULL;
     519        return false;
     520    }
     521    if (AlsaBad(snd_mixer_selem_get_playback_volume_range(mixer.elem,
     522                                                          &mixer.volmin,
     523                                                          &mixer.volmax),
     524                "failed to get volume range"))
    543525    {
    544         VERBOSE(VB_IMPORTANT, QString("Mixer unable to find control %1")
    545                 .arg(control));
    546         return;
     526        snd_mixer_close(mixer.handle);
     527        mixer.handle = NULL;
     528        return false;
    547529    }
     530    mixer.volrange = mixer.volmax - mixer.volmin;
     531    VERBOSE(VB_AUDIO+VB_EXTRA, LOC + mixer.logtag
     532            + QString("volume range - min %1, max %2, range %3")
     533                      .arg(mixer.volmin).arg(mixer.volmax).arg(mixer.volrange));
     534    if (set_initial_vol)
     535    {
     536        int initial_vol;
     537        if ( mixer.control == "PCM")
     538            initial_vol = gContext->GetNumSetting("PCMMixerVolume", 80);
     539        else
     540            initial_vol = gContext->GetNumSetting("MasterMixerVolume", 80);
     541        for (int ch = 0; ch < audio_channels; ++ch)
     542            SetVolumeChannel(ch, initial_vol);
     543    }
     544    VERBOSE(VB_AUDIO, LOC + mixer.logtag + "set up ok");
     545    return true;
     546}
    548547
    549     snd_mixer_selem_channel_id_t chan = (snd_mixer_selem_channel_id_t) channel;
    550     if (!snd_mixer_selem_has_playback_channel(elem, chan))
    551     {
    552         snd_mixer_selem_id_set_index(sid, channel);
    553         if ((elem = snd_mixer_find_selem(mixer_handle, sid)) == NULL)
     548void AudioOutputALSA::WriteMmap(unsigned char *data, snd_pcm_uframes_t nframes)
     549{
     550    unsigned char* ptr8 = data;
     551    int16_t* ptr16 = (int16_t*)data;
     552    int retries = 0;
     553    int chk;
     554    while ( nframes > 0 && retries < 3)
     555    {
     556        snd_pcm_hwsync(pcm.handle);
     557        snd_pcm_sframes_t avail = snd_pcm_avail_update(pcm.handle);
     558        if (avail < 0)
     559        {
     560            VERBOSE(VB_IMPORTANT, LOC_ERR + pcm.logtag
     561                    + "WriteMmap failed to get avail");
     562            if (Recovery(avail))
     563            {
     564                ++retries;
     565                continue;
     566            }
     567            else
     568                return;
     569        }
     570        snd_pcm_uframes_t towrite =
     571            (nframes > (snd_pcm_uframes_t)avail) ? avail : nframes;
     572        const snd_pcm_channel_area_t* areas;
     573        snd_pcm_uframes_t offset;
     574        if ((chk = snd_pcm_mmap_begin(pcm.handle, &areas, &offset, &towrite))
     575            < 0)
    554576        {
    555             VERBOSE(VB_IMPORTANT,
    556                     QString("mixer unable to find control %1 %2")
    557                     .arg(control).arg(channel));
     577            VERBOSE(VB_IMPORTANT, LOC_ERR + pcm.logtag
     578                    + "mmap begin failed");
    558579            return;
    559580        }
     581        // get channel bases
     582        unsigned char* samples[audio_channels];
     583        int steps[audio_channels];
     584        int ch;
     585        for (ch = 0; ch < audio_channels; ++ch)
     586        {
     587            samples[ch] = (unsigned char*)areas[ch].addr
     588                          + (areas[ch].first + areas[ch].step * offset ) / 8;
     589            steps[ch] = areas[ch].step / 8;
     590        }
     591        // wrrrrite ...
     592        unsigned int frm;
     593        switch( audio_bits)
     594        {
     595            case 16:
     596                for (frm = towrite; frm; --frm)
     597                    for(ch = 0; ch < audio_channels; ++ch)
     598                    {
     599                        *(int16_t*)(samples[ch]) = *ptr16++;
     600                        samples[ch] += steps[ch];
     601                    }
     602                break;
     603
     604            case 8:
     605                for (frm = towrite; frm; --frm)
     606                    for(ch = 0; ch < audio_channels; ++ch)
     607                    {
     608                        *samples[ch] = *ptr8++;
     609                        samples[ch] += steps[ch];
     610                    }
     611                break;
     612            default:
     613                VERBOSE(VB_IMPORTANT, LOC_ERR + pcm.logtag
     614                        + "invalid sample format, DirectWrite");
     615                        return;
     616        }
     617        chk = snd_pcm_mmap_commit(pcm.handle, offset, towrite);
     618        if (chk < 0)
     619        {
     620            VERBOSE(VB_IMPORTANT, LOC_ERR + pcm.logtag
     621                    + "mmap commit failed");
     622            return;
     623        }
     624        if (chk < (int)towrite)
     625            VERBOSE(VB_IMPORTANT, LOC_ERR + pcm.logtag
     626                    + QString("short write, %1 / %2, DirectWrite")
     627                              .arg(chk).arg(towrite));
     628        nframes -= towrite;
     629        ++retries;
    560630    }
     631}
    561632
    562     ALSAVolumeInfo vinfo = GetVolumeRange(elem);
    563 
    564     long set_vol = vinfo.ToALSARange(volume);
    565 
    566     int err = snd_mixer_selem_set_playback_volume(elem, chan, set_vol);
    567     if (err < 0)
    568     {
    569         VERBOSE(VB_IMPORTANT, QString("mixer set channel %1 err %2: %3")
    570                 .arg(channel).arg(err).arg(snd_strerror(err)));
    571     }
    572     else
    573     {
    574         VERBOSE(VB_AUDIO, QString("channel %1 vol set to %2")
    575                 .arg(channel).arg(set_vol));
    576     }
    577 
    578     if (snd_mixer_selem_has_playback_switch(elem))
     633void AudioOutputALSA::WriteRw(unsigned char *data, snd_pcm_uframes_t nframes)
     634{
     635    snd_pcm_uframes_t towrite;
     636    snd_pcm_sframes_t wrote, avail;
     637    int retries = 0;
     638    for (wrote = 0; nframes > 0 && retries < 3; data += wrote * pcm.bytes_per_frame)
    579639    {
    580         int unmute = (0 != set_vol);
    581         if (snd_mixer_selem_has_playback_switch_joined(elem))
     640        snd_pcm_hwsync(pcm.handle);
     641        if ((avail = snd_pcm_avail_update(pcm.handle)) < 0)
    582642        {
    583             // Only mute if all the channels should be muted.
    584             for (int i = 0; i < audio_channels; i++)
     643            VERBOSE(VB_IMPORTANT, LOC_ERR + pcm.logtag
     644                    + "WriteRw failed to get avail");
     645            if (Recovery(avail))
    585646            {
    586                 if (0 != GetVolumeChannel(i))
    587                     unmute = 1;
     647                ++retries;
     648                continue;
    588649            }
     650            else
     651                return;
    589652        }
    590 
    591         err = snd_mixer_selem_set_playback_switch(elem, chan, unmute);
    592         if (err < 0)
     653        towrite = (nframes < (snd_pcm_uframes_t)avail) ? nframes : avail;
     654        wrote = snd_pcm_writei(pcm.handle, (void*)data, towrite);
     655        if (wrote < 0)
    593656        {
    594             VERBOSE(VB_IMPORTANT, LOC_ERR +
    595                     QString("Mixer set playback switch %1 err %2: %3")
    596                     .arg(channel).arg(err).arg(snd_strerror(err)));
     657            switch (wrote) // ==> frames written, -EBADFD, -EPIPE, -ESTRPIPE
     658            {
     659                case -EBADFD:
     660                    AlsaBad(-EBADFD, "unfit for writing");
     661                    break;
     662                case -EPIPE:
     663                case -ESTRPIPE:
     664                    Recovery(wrote);
     665                    break;
     666                default:
     667                    AlsaBad(wrote, "snd_pcm_writei ==> weird state");
     668                    nframes = 0;
     669                    break;
     670            }
     671            wrote = 0;
    597672        }
    598673        else
    599674        {
    600             VERBOSE(VB_AUDIO, LOC +
    601                     QString("channel %1 playback switch set to %2")
    602                     .arg(channel).arg(unmute));
     675            if ((snd_pcm_uframes_t)wrote < towrite)
     676                VERBOSE(VB_AUDIO, LOC_ERR + pcm.logtag
     677                        + QString("short write, %1 / %2 frames")
     678                                  .arg(wrote).arg(towrite));
     679            nframes -= wrote;
    603680        }
     681        ++retries;
    604682    }
    605683}
    606684
    607 void AudioOutputALSA::OpenMixer(bool setstartingvolume)
     685bool AudioOutputALSA::Recovery(int err)
    608686{
    609     int volume;
    610 
    611     mixer_control = gContext->GetSetting("MixerControl", "PCM");
    612 
    613     SetupMixer();
    614 
    615     if (mixer_handle != NULL && setstartingvolume)
     687    if (err > 0)
     688        err = -err;
     689    bool isgood = false;
     690    bool suspense = false;
     691    switch (err)
    616692    {
    617         volume = gContext->GetNumSetting("MasterMixerVolume", 80);
    618         SetCurrentVolume("Master", 0, volume);
    619         SetCurrentVolume("Master", 1, volume);
    620 
    621         volume = gContext->GetNumSetting("PCMMixerVolume", 80);
    622         SetCurrentVolume("PCM", 0, volume);
    623         SetCurrentVolume("PCM", 1, volume);
     693        case -EINTR:
     694            isgood = true; // nuthin to see here
     695            break;
     696        case -ESTRPIPE:
     697            suspense = true;
     698        case -EPIPE:
     699            if (!AlsaBad(snd_pcm_prepare(pcm.handle),
     700                         QString("failed to recover from %1")
     701                                 .arg((suspense) ? "suspend" : "underrun")))
     702                isgood = true;
     703            break;
     704        default:
     705            break;
    624706    }
     707    return isgood;
    625708}
    626709
    627 void AudioOutputALSA::CloseMixer(void)
    628 {
    629     if (mixer_handle != NULL)
    630         snd_mixer_close(mixer_handle);
    631     mixer_handle = NULL;
    632 }
    633 
    634 void AudioOutputALSA::SetupMixer(void)
     710bool AudioOutputALSA::XrunRecovery(void)
    635711{
    636     int err;
    637 
    638     QString alsadevice = gContext->GetSetting("MixerDevice", "default");
    639     QString device = alsadevice.remove(QString("ALSA:"));
    640 
    641     if (mixer_handle != NULL)
    642         CloseMixer();
    643 
    644     VERBOSE(VB_AUDIO, QString("Opening mixer %1").arg(device));
    645 
    646     // TODO: This is opening card 0. Fix for case of multiple soundcards
    647     if ((err = snd_mixer_open(&mixer_handle, 0)) < 0)
    648     {
    649         Warn(QString("Mixer device open error %1: %2")
    650              .arg(err).arg(snd_strerror(err)));
    651         mixer_handle = NULL;
    652         return;
    653     }
    654 
    655     QByteArray dev = device.toAscii();
    656     if ((err = snd_mixer_attach(mixer_handle, dev.constData())) < 0)
    657     {
    658         Warn(QString("Mixer attach error %1: %2"
    659                      "\n\t\t\tCheck Mixer Name in Setup: '%3'")
    660              .arg(err).arg(snd_strerror(err)).arg(device));
    661         CloseMixer();
    662         return;
    663     }
    664 
    665     if ((err = snd_mixer_selem_register(mixer_handle, NULL, NULL)) < 0)
     712    bool isgood = false;
     713    if (pcm.handle != NULL)
    666714    {
    667         Warn(QString("Mixer register error %1: %2")
    668              .arg(err).arg(snd_strerror(err)));
    669         CloseMixer();
    670         return;
    671     }
    672 
    673     if ((err = snd_mixer_load(mixer_handle)) < 0)
    674     {
    675         Warn(QString("Mixer load error %1: %2")
    676              .arg(err).arg(snd_strerror(err)));
    677         CloseMixer();
    678         return;
     715        if (!AlsaBad(snd_pcm_drop(pcm.handle), "pcm drop failed"))
     716            if (!AlsaBad(snd_pcm_prepare(pcm.handle), "pcm prepare failed"))
     717                isgood = true;
     718        VERBOSE(VB_AUDIO, LOC + pcm.logtag
     719                + "xrun recovery " + ((isgood) ? "good" : "not good"));
    679720    }
     721    return isgood;
    680722}
    681723
    682 ALSAVolumeInfo AudioOutputALSA::GetVolumeRange(snd_mixer_elem_t *elem) const
     724bool AudioOutputALSA::AlsaBad(int op_result, QString err_msg, bool warn_only)
    683725{
    684     long volume_min, volume_max;
    685 
    686     int err = snd_mixer_selem_get_playback_volume_range(
    687         elem, &volume_min, &volume_max);
    688 
    689     if (err < 0)
     726    bool isbad = (op_result < 0); // (op_result < 0) => true return
     727    if (isbad)
    690728    {
    691         static bool first_time = true;
    692         if (first_time)
    693         {
    694             VERBOSE(VB_IMPORTANT,
    695                     "snd_mixer_selem_get_playback_volume_range()" + ENO);
    696             first_time = false;
    697         }
     729        QString loc_tag = (warn_only) ? LOC_WARN : LOC_ERR;
     730        VERBOSE(VB_IMPORTANT, loc_tag + pcm.logtag + err_msg
     731                + QString(": %1").arg(snd_strerror(op_result)));
    698732    }
    699 
    700     ALSAVolumeInfo vinfo(volume_min, volume_max);
    701 
    702     VERBOSE(VB_AUDIO, QString("Volume range is %1 to %2, mult=%3")
    703             .arg(vinfo.volume_min).arg(vinfo.volume_max)
    704             .arg(vinfo.range_multiplier));
    705 
    706     return vinfo;
     733    return isbad;
    707734}
     735/* vim: set expandtab tabstop=4 shiftwidth=4: */
  • libs/libmyth/audiooutputalsa.h

    old new  
     1/*
     2 * Copyright (C) <=2008 unattributed author(s)
     3 * Copyright (C) 2008  Alan Calvert
     4 *
     5 * This program is free software; you can redistribute it and/or
     6 * modify it under the terms of the GNU General Public License
     7 * as published by the Free Software Foundation; either version 2
     8 * of the License, or (at your option) any later version.
     9 *
     10 * This program is distributed in the hope that it will be useful,
     11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13 * GNU General Public License for more details.
     14 *
     15 * You should have received a copy of the GNU General Public License
     16 * along with this program; if not, write to the Free Software
     17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
     18 * 02110-1301, USA.
     19 */
     20
    121#ifndef AUDIOOUTPUTALSA
    222#define AUDIOOUTPUTALSA
    323
    4 #define ALSA_PCM_NEW_HW_PARAMS_API
    5 #define ALSA_PCM_NEW_SW_PARAMS_API
    6 #include <alsa/asoundlib.h>
    7 
    8 #include "audiooutputbase.h"
    9 
    1024using namespace std;
    1125
    12 class ALSAVolumeInfo
    13 {
    14   public:
    15     ALSAVolumeInfo(long  playback_vol_min,
    16                    long  playback_vol_max) :
    17         range_multiplier(1.0f),
    18         volume_min(playback_vol_min), volume_max(playback_vol_max)
    19     {
    20         float range = (float) (volume_max - volume_min);
    21         if (range > 0.0f)
    22             range_multiplier = 100.0f / range;
    23         range_multiplier_inv = 1.0f / range_multiplier;
    24     }
    25 
    26     int ToMythRange(long alsa_volume)
    27     {
    28         long toz = alsa_volume - volume_min;
    29         int val = (int) (toz * range_multiplier);
    30         val = (val < 0)   ? 0   : val;
    31         val = (val > 100) ? 100 : val;
    32         return val;
    33     }
    34 
    35     long ToALSARange(int myth_volume)
    36     {
    37         float tos = myth_volume * range_multiplier_inv;
    38         long val = (long) (tos + volume_min + 0.5);
    39         val = (val < volume_min) ? volume_min : val;
    40         val = (val > volume_max) ? volume_max : val;
    41         return val;
    42     }
    43 
    44     float range_multiplier;
    45     float range_multiplier_inv;
    46     long  volume_min;
    47     long  volume_max;
    48 };
     26#include <alsa/asoundlib.h>
     27#include "audiooutputbase.h"
    4928
    5029class AudioOutputALSA : public AudioOutputBase
    5130{
    5231  public:
    5332    AudioOutputALSA(const AudioSettings &settings);
    54     virtual ~AudioOutputALSA();
     33    ~AudioOutputALSA();
    5534
    56     // Volume control
    57     virtual int GetVolumeChannel(int channel) const; // Returns 0-100
    58     virtual void SetVolumeChannel(int channel, int volume); // range 0-100 for vol
     35    void Reset(void);
     36    void Pause(bool paused);
     37    void Drain(void);
     38    int GetVolumeChannel(int channel) const;        //  volume range 0-100
     39    void SetVolumeChannel(int channel, int volume); //
    5940
    60    
    6141  protected:
    62     // You need to implement the following functions
    63     virtual bool OpenDevice(void);
    64     virtual void CloseDevice(void);
    65     virtual void WriteAudio(unsigned char *aubuf, int size);
    66     virtual int  GetSpaceOnSoundcard(void) const;
    67     virtual int  GetBufferedOnSoundcard(void) const;
    68 
    69   private:
    70     inline int SetParameters(snd_pcm_t *handle,
    71                              snd_pcm_format_t format, unsigned int channels,
    72                              unsigned int rate, unsigned int buffer_time,
    73                              unsigned int period_time);
    74 
    75 
    76     // Volume related
    77     void SetCurrentVolume(QString control, int channel, int volume);
    78     void OpenMixer(bool setstartingvolume);
    79     void CloseMixer(void);
    80     void SetupMixer(void);
    81     ALSAVolumeInfo GetVolumeRange(snd_mixer_elem_t *elem) const;
     42    bool OpenDevice(void);
     43    void CloseDevice(void);
     44    void WriteAudio(unsigned char *aubuf, int size);
     45    int  GetSpaceOnSoundcard(void) const;
     46    int  GetBufferedOnSoundcard(void) const;
    8247
    8348  private:
    84     snd_pcm_t   *pcm_handle;
    85     int          numbadioctls;
    86     QMutex       killAudioLock;
    87     snd_mixer_t *mixer_handle;
    88     QString      mixer_control; // e.g. "PCM"
    89     snd_pcm_sframes_t (*pcm_write_func)(snd_pcm_t*, const void*,
    90                                         snd_pcm_uframes_t);
     49    bool PrepPCM(void);
     50    bool PrepHwparams(void);
     51    bool PrepSwparams(void);
     52    bool PrepMixer(void);
     53    void WriteMmap(unsigned char *data, snd_pcm_uframes_t nframes);
     54    void WriteRw(unsigned char *data, snd_pcm_uframes_t nframes);
     55    bool Recovery(int err);
     56    bool XrunRecovery(void);
     57    bool AlsaBad(int op_result, QString err_msg, bool warn_only = false);
     58
     59    struct {
     60        QByteArray            device;
     61        snd_pcm_t*            handle;
     62        unsigned int          sample_rate;
     63        snd_pcm_uframes_t     period_size;
     64        snd_pcm_uframes_t     buffer_size;
     65        int                   bytes_per_frame; // ! bytes_per_sample, grrr!
     66        bool                  use_mmap;
     67        QString               logtag;
     68    } pcm;
     69
     70    struct {
     71        QByteArray            device;
     72        QByteArray            control;
     73        snd_mixer_t*          handle;
     74        snd_mixer_elem_t*     elem;
     75        long                  volmin;
     76        long                  volmax;
     77        long                  volrange;
     78        QString               logtag;
     79    } mixer;
    9180};
    92 
    9381#endif
    94 
     82/* vim: set expandtab tabstop=4 shiftwidth=4: */
  • programs/mythfrontend/globalsettings.cpp

    old new  
    1919#include <qdir.h>
    2020#include <qimage.h>
    2121
     22#ifdef USING_ALSA
     23#include <alsa/asoundlib.h>
     24#endif
     25
    2226// MythTV headers
    2327#include "mythconfig.h"
    2428#include "mythcontext.h"
     
    3943#include "mythdirs.h"
    4044#include "mythuihelper.h"
    4145
     46#ifdef USING_ALSA
     47static AlsaPlaybackPcms alsa_pcms;
     48#endif
     49
    4250static HostComboBox *AudioOutputDevice()
    4351{
    4452    HostComboBox *gc = new HostComboBox("AudioOutputDevice", true);
    4553    gc->setLabel(QObject::tr("Audio output device"));
    46 
    4754#ifdef USING_ALSA
    4855    gc->addSelection("ALSA:default",       "ALSA:default");
    4956    gc->addSelection("ALSA:spdif",         "ALSA:spdif");
     
    5259    gc->addSelection("ALSA:digital",       "ALSA:digital");
    5360    gc->addSelection("ALSA:mixed-analog",  "ALSA:mixed-analog");
    5461    gc->addSelection("ALSA:mixed-digital", "ALSA:mixed-digital");
     62    for (int p = 0; p < alsa_pcms.pcm_count; ++p)
     63        if (strncasecmp(alsa_pcms.pcm_tags[p], "iec958:", 7) != 0)
     64        {
     65            QByteArray sel = "ALSA:" + QByteArray(alsa_pcms.pcm_tags[p]);
     66            gc->addSelection(sel.constData(), sel.constData());
     67        }
    5568#endif
    5669#ifdef USING_OSS
    5770    QDir dev("/dev", "dsp*", QDir::Name, QDir::System);
     
    127140#ifndef USING_MINGW
    128141    gc->addSelection("ALSA:iec958:{ AES0 0x02 }", "ALSA:iec958:{ AES0 0x02 }");
    129142#endif
    130 
     143#ifdef USING_ALSA
     144    for (int p = 0; p < alsa_pcms.pcm_count; ++p)
     145        if (!strncasecmp(alsa_pcms.pcm_tags[p], "iec958:", 7))
     146        {
     147            QByteArray sel = "ALSA:" + QByteArray(alsa_pcms.pcm_tags[p]);
     148            gc->addSelection(sel.constData(), sel.constData());
     149        }
     150#endif
    131151    gc->setHelpText(QObject::tr("Audio output device to use for AC3 and "
    132152                    "DTS passthrough. Default is the same as Audio output "
    133153                    "device. This value is currently only used with ALSA "
     
    50485068    addChild(xboxset);
    50495069}
    50505070
     5071#ifdef USING_ALSA
     5072AlsaPlaybackPcms::AlsaPlaybackPcms()
     5073{
     5074    for (int p = 0; p < ALSA_MAX_PCMS; ++p)
     5075        pcm_tags[p] = NULL;
     5076    pcm_count = 0;
     5077    snd_ctl_card_info_t *ctl_info = NULL;
     5078    if (snd_ctl_card_info_malloc(&ctl_info) == 0)
     5079    {
     5080        char* io;
     5081        void** hints = NULL;
     5082        void** n = NULL;
     5083        if (!(snd_device_name_hint(-1, "pcm", &hints) < 0))
     5084        {
     5085            n = hints;
     5086            while (*n != NULL && pcm_count < ALSA_MAX_PCMS)
     5087            {
     5088                io = snd_device_name_get_hint(*n, "IOID");
     5089                if (io == NULL || (io != NULL && strcmp(io, "Output")))
     5090                    pcm_tags[pcm_count++] = snd_device_name_get_hint(*n, "NAME");
     5091                                            // memory hereby allocated is
     5092                                            // resolved in the destructor
     5093                if (io != NULL)
     5094                    free(io);
     5095                n++;
     5096            }
     5097            snd_device_name_free_hint(hints);
     5098        }
     5099    }
     5100    if (ctl_info != NULL)
     5101        snd_ctl_card_info_free(ctl_info);
     5102}
     5103
     5104AlsaPlaybackPcms::~AlsaPlaybackPcms()
     5105{
     5106    for( int p = 0; p < ALSA_MAX_PCMS; ++p)
     5107        if (pcm_tags[p] != NULL)
     5108            free(pcm_tags[p]);
     5109}
     5110
     5111#endif
    50515112// vim:set sw=4 ts=4 expandtab:
  • programs/mythfrontend/globalsettings.h

    old new  
    165165};
    166166#endif // USING_IVTV
    167167
     168#ifdef USING_ALSA
     169#define ALSA_MAX_PCMS 24
     170
     171class AlsaPlaybackPcms
     172{
     173  public:
     174    AlsaPlaybackPcms();
     175    ~AlsaPlaybackPcms();
     176    char* pcm_tags[ALSA_MAX_PCMS];
     177    int pcm_count;
     178};
     179#endif // USING_ALSA
    168180#endif