diff options
Diffstat (limited to 'akode/plugins/alsa_sink/alsa_sink.cpp')
| -rw-r--r-- | akode/plugins/alsa_sink/alsa_sink.cpp | 311 | 
1 files changed, 311 insertions, 0 deletions
| diff --git a/akode/plugins/alsa_sink/alsa_sink.cpp b/akode/plugins/alsa_sink/alsa_sink.cpp new file mode 100644 index 0000000..2abad2e --- /dev/null +++ b/akode/plugins/alsa_sink/alsa_sink.cpp @@ -0,0 +1,311 @@ +/*  aKode: ALSA Sink + +    Copyright (C) 2004-2005 Allan Sandfeld Jensen <kde@carewolf.com> + +    This library is free software; you can redistribute it and/or +    modify it under the terms of the GNU Library General Public +    License as published by the Free Software Foundation; either +    version 2 of the License, or (at your option) any later version. + +    This library 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 +    Library General Public License for more details. + +    You should have received a copy of the GNU Library General Public License +    along with this library; see the file COPYING.LIB.  If not, write to +    the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, +    Boston, MA 02110-1301, USA. +*/ + +#include <iostream> + +#include <alsa/asoundlib.h> +#include <alsa/pcm.h> + +#include <audioframe.h> +#include "alsa_sink.h" + +/* + * resume from suspend + */ +static int resume(snd_pcm_t *pcm) +{ +  int res; +  while ((res = snd_pcm_resume(pcm)) == -EAGAIN) +    sleep(1); +  if (! res) +    return 0; +  return snd_pcm_prepare(pcm); +} + +namespace aKode { + +extern "C" { ALSASinkPlugin alsa_sink; } + +struct ALSASink::private_data +{ +    private_data() : pcm_playback(0), buffer(0), error(false), can_pause(false) {}; + +    snd_pcm_t *pcm_playback; + +    AudioConfiguration config; +    int scale; +    int filled, fragmentSize; +    int sampleSize; +    char* buffer; +    bool error; +    bool can_pause; +}; + +ALSASink::ALSASink() +{ +    m_data = new private_data; +} + +ALSASink::~ALSASink() +{ +    close(); +    delete m_data; +} + +bool ALSASink::open() +{ +    int err = 0; +    // open is non-blocking to make it possible to fail when occupied +    err = snd_pcm_open(&m_data->pcm_playback, "default", SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); +    if (err < 0) { +        m_data->error = true; +        return false; +    } + +    // Set to blocking +    snd_pcm_nonblock(m_data->pcm_playback, 0); + +    m_data->error = false; +    return true; +} + +void ALSASink::close() +{ +    if (m_data->pcm_playback) { +        snd_pcm_drain(m_data->pcm_playback); +        snd_pcm_close(m_data->pcm_playback); +    } +    m_data->pcm_playback = 0; +    m_data->error = false; +} + +int ALSASink::setAudioConfiguration(const AudioConfiguration* config) +{ +    if (m_data->error) return -1; + +    // Get back to OPEN state (is SETUP state enough with snd_pcm_drop?) +    snd_pcm_state_t state = snd_pcm_state( m_data->pcm_playback ); +    if (state != SND_PCM_STATE_OPEN) { +        close(); +        if (!open()) return -1; +    } + +    int res = 0; +    int wid = 1; +    m_data->config = *config; +    snd_pcm_hw_params_t *hw; +    snd_pcm_hw_params_alloca(&hw); +    snd_pcm_hw_params_any(m_data->pcm_playback, hw); +    snd_pcm_hw_params_set_access(m_data->pcm_playback, hw, SND_PCM_ACCESS_RW_INTERLEAVED); +    // Detect format: +    snd_pcm_format_t format = SND_PCM_FORMAT_UNKNOWN; +    // Test for float, 24 and 32 bit integer. Fall back to 16bit +    if (m_data->config.sample_width<0) { +        if (snd_pcm_hw_params_test_format(m_data->pcm_playback, hw,  SND_PCM_FORMAT_FLOAT ) == 0) { +            format = SND_PCM_FORMAT_FLOAT; +            m_data->scale = 1; +            wid = 4; +            goto found_format; +        } +        // Try 16bit then +        m_data->config.sample_width = 16; +        res = 1; +    } +    if (m_data->config.sample_width > 24 && m_data->config.sample_width <=32) { +        if (snd_pcm_hw_params_test_format(m_data->pcm_playback, hw, SND_PCM_FORMAT_S32) == 0) { +            format = SND_PCM_FORMAT_S32; +            m_data->scale = 1<<(32-config->sample_width); +            wid = 4; +            goto found_format; +        } +        // Try 24bit then +        m_data->config.sample_width = 24; +        res = 1; +    } +    if (m_data->config.sample_width > 16 && m_data->config.sample_width <= 24) { +        if (snd_pcm_hw_params_test_format(m_data->pcm_playback, hw,  SND_PCM_FORMAT_S24 ) == 0) { +            format = SND_PCM_FORMAT_S24; +            m_data->scale = 1<<(24-config->sample_width); +            wid = 4; +            goto found_format; +        } +        // Try 16bit then +        m_data->config.sample_width = 16; +    } +    // If the driver doesnt support 8 or 16 bit, we will fail completly +    if (m_data->config.sample_width<=8) { +        format = SND_PCM_FORMAT_S8; +        m_data->scale = 1<<(8-config->sample_width); +        wid = 1; +        goto found_format; +    } +    else +    if (m_data->config.sample_width<=16) { +        format = SND_PCM_FORMAT_S16; +        m_data->scale = 1<<(16-config->sample_width); +        wid = 2; +        goto found_format; +    } + +found_format: +    if (format != SND_PCM_FORMAT_UNKNOWN) +        snd_pcm_hw_params_set_format(m_data->pcm_playback, hw, format); +    else +        return -1; + +    unsigned int rate = config->sample_rate; +    snd_pcm_hw_params_set_rate_near(m_data->pcm_playback, hw, &rate, 0); +    if (m_data->config.sample_rate != rate) { +        m_data->config.sample_rate = rate; +        res = 1; +    } + +    snd_pcm_hw_params_set_channels(m_data->pcm_playback, hw, config->channels); + + +    m_data->fragmentSize = 1024; +    snd_pcm_uframes_t period_size = m_data->fragmentSize / (wid*config->channels); +    snd_pcm_hw_params_set_period_size_near(m_data->pcm_playback, hw, &period_size, 0); + +    m_data->fragmentSize = period_size * (wid*config->channels); +//     std::cerr << "akode: ALSA fragment-size: " << m_data->fragmentSize << "\n"; + +    delete m_data->buffer; +    m_data->buffer = new char [m_data->fragmentSize]; +    m_data->filled = 0; + +    if (snd_pcm_hw_params(m_data->pcm_playback, hw) < 0) { +        return -1; +    } +    else { +        m_data->can_pause = (snd_pcm_hw_params_can_pause(hw) == 1); +        return res; +    } +} + +const AudioConfiguration* ALSASink::audioConfiguration() const +{ +    return &m_data->config; +} + +template<class T> +bool ALSASink::_writeFrame(AudioFrame* frame) +{ +    int channels = m_data->config.channels; + +    long i = 0; +    T* buffer = (T*)m_data->buffer; +    T** data = (T**)frame->data; +    while(true) { +        if (m_data->filled >= m_data->fragmentSize) +        xrun: +        { +            int frames = snd_pcm_bytes_to_frames(m_data->pcm_playback, m_data->filled); +            int status = snd_pcm_writei(m_data->pcm_playback, m_data->buffer, frames); +            if (status == -EPIPE) { +                snd_pcm_prepare(m_data->pcm_playback); +                //std::cerr << "akode: ALSA xrun\n"; +                goto xrun; +            } +            else if (status < 0) return false; +            int bytes = snd_pcm_frames_to_bytes(m_data->pcm_playback, status); +            if (m_data->filled != bytes) { +                int rest = m_data->filled - bytes; +                //std::cerr << "akode: ALSA write-remainder: " << rest << "\n"; +                memmove(m_data->buffer, m_data->buffer + bytes, rest); +                m_data->filled = rest; +            } else +                m_data->filled = 0; + +        } +        if (i >= frame->length) break; +        for(int j=0; j<channels; j++) { +            buffer[m_data->filled/sizeof(T)] = (data[j][i])*m_data->scale; +            m_data->filled+=sizeof(T); +        } +        i++; +    } + +    if (snd_pcm_state( m_data->pcm_playback ) == SND_PCM_STATE_PREPARED) +        snd_pcm_start(m_data->pcm_playback); + +    return true; +} + +bool ALSASink::writeFrame(AudioFrame* frame) +{ +    if (m_data->error) return false; +    if (!frame) return false; + +    if ( frame->sample_width != m_data->config.sample_width +      || frame->channels != m_data->config.channels +      || frame->sample_rate != m_data->config.sample_rate) +    { +        if (setAudioConfiguration(frame) < 0) +            return false; +    } + +    if ( snd_pcm_state(m_data->pcm_playback) == SND_PCM_STATE_SUSPENDED ) { +      int res = ::resume(m_data->pcm_playback); +      if (res < 0) +        return false; +    } +    else +    if (snd_pcm_state( m_data->pcm_playback ) == SND_PCM_STATE_PAUSED) +        snd_pcm_pause(m_data->pcm_playback, 0); + +    if (snd_pcm_state( m_data->pcm_playback ) == SND_PCM_STATE_SETUP) +        snd_pcm_prepare(m_data->pcm_playback); + +    if (frame->sample_width<0) +        return _writeFrame<float>(frame); +    else +    if (frame->sample_width<=8) +        return _writeFrame<int8_t>(frame); +    else +    if (frame->sample_width<=16) +        return _writeFrame<int16_t>(frame); +    else +    if (frame->sample_width<=32) +        return _writeFrame<int32_t>(frame); + +    return false; +} + +void ALSASink::pause() +{ +    if (m_data->error) return; + +    if (m_data->can_pause) { +        snd_pcm_pause(m_data->pcm_playback, 1); +    } + +} + +// Do not confuse this with snd_pcm_resume which is used to resume from a suspend +void ALSASink::resume() +{ +    if (m_data->error) return; + +    if (snd_pcm_state( m_data->pcm_playback ) == SND_PCM_STATE_PAUSED) +        snd_pcm_pause(m_data->pcm_playback, 0); +} + +} // namespace | 
