summaryrefslogtreecommitdiffstats
path: root/akode/plugins/alsa_sink/alsa_sink.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'akode/plugins/alsa_sink/alsa_sink.cpp')
-rw-r--r--akode/plugins/alsa_sink/alsa_sink.cpp311
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