summaryrefslogtreecommitdiffstats
path: root/kscd/libwm/audio/audio_alsa.c
diff options
context:
space:
mode:
Diffstat (limited to 'kscd/libwm/audio/audio_alsa.c')
-rw-r--r--kscd/libwm/audio/audio_alsa.c334
1 files changed, 334 insertions, 0 deletions
diff --git a/kscd/libwm/audio/audio_alsa.c b/kscd/libwm/audio/audio_alsa.c
new file mode 100644
index 00000000..b1d4e938
--- /dev/null
+++ b/kscd/libwm/audio/audio_alsa.c
@@ -0,0 +1,334 @@
+/*
+ * Driver for Advanced Linux Sound Architecture, http://alsa.jcu.cz
+ *
+ * mpg123 comments:
+ * Code by Anders Semb Hermansen <ahermans@vf.telia.no>
+ * Cleanups by Jaroslav Kysela <perex@jcu.cz>
+ * Ville Syrjala <syrjala@sci.fi>
+ *
+ * adopted for libworkman cdda audio backend from Alexander Kern alex.kern@gmx.de
+ *
+ * Adapted to support both ALSA V0.x and V1.x APIs for PCM calls
+ * (Philip Nelson <teamdba@scotdb.com> 2004-03-15)
+ *
+ * This file comes under GPL license.
+ */
+
+
+
+#include <config.h>
+
+#if defined(HAVE_ARTS_LIBASOUND2)
+
+#include <alsa/asoundlib.h>
+#include "audio.h"
+
+char* device = NULL;
+snd_pcm_t *handle;
+
+snd_pcm_format_t format = SND_PCM_FORMAT_S16; /* sample format */
+int rate = 44100; /* stream rate */
+int channels = 2; /* count of channels */
+int buffer_time = 2000000; /* ring buffer length in us */
+int period_time = 100000; /* period time in us */
+
+snd_pcm_sframes_t buffer_size;
+snd_pcm_sframes_t period_size;
+
+int alsa_open(void);
+int alsa_close(void);
+int alsa_stop(void);
+int alsa_play(struct cdda_block *blk);
+int alsa_state(struct cdda_block *blk);
+struct audio_oops* setup_alsa(const char *dev, const char *ctl);
+
+static int set_hwparams(snd_pcm_hw_params_t *params,
+ snd_pcm_access_t accesspar)
+{
+ int err, dir, new_rate;
+
+ /* choose all parameters */
+ err = snd_pcm_hw_params_any(handle, params);
+ if (err < 0) {
+ ERRORLOG("Broken configuration for playback: no configurations available: %s\n", snd_strerror(err));
+ return err;
+ }
+ /* set the interleaved read/write format */
+ err = snd_pcm_hw_params_set_access(handle, params, accesspar);
+ if (err < 0) {
+ ERRORLOG("Access type not available for playback: %s\n", snd_strerror(err));
+ return err;
+ }
+ /* set the sample format */
+ err = snd_pcm_hw_params_set_format(handle, params, format);
+ if (err < 0) {
+ ERRORLOG("Sample format not available for playback: %s\n", snd_strerror(err));
+ return err;
+ }
+ /* set the count of channels */
+ err = snd_pcm_hw_params_set_channels(handle, params, channels);
+ if (err < 0) {
+ ERRORLOG("Channels count (%i) not available for playbacks: %s\n", channels, snd_strerror(err));
+ return err;
+ }
+ /* set the stream rate */
+#if (SND_LIB_MAJOR < 1)
+ err = new_rate = snd_pcm_hw_params_set_rate_near(handle, params, rate, 0);
+#else
+ new_rate = rate;
+ err = snd_pcm_hw_params_set_rate_near(handle, params, &new_rate, 0);
+#endif
+ if (err < 0) {
+ ERRORLOG("Rate %iHz not available for playback: %s\n", rate, snd_strerror(err));
+ return err;
+ }
+ if (new_rate != rate) {
+ ERRORLOG("Rate doesn't match (requested %iHz, get %iHz)\n", rate, new_rate);
+ return -EINVAL;
+ }
+ /* set the buffer time */
+#if (SND_LIB_MAJOR < 1)
+ err = snd_pcm_hw_params_set_buffer_time_near(handle, params, buffer_time, &dir);
+#else
+ err = snd_pcm_hw_params_set_buffer_time_near(handle, params, &buffer_time, &dir);
+#endif
+ if (err < 0) {
+ ERRORLOG("Unable to set buffer time %i for playback: %s\n", buffer_time, snd_strerror(err));
+ return err;
+ }
+#if (SND_LIB_MAJOR < 1)
+ buffer_size = snd_pcm_hw_params_get_buffer_size(params);
+#else
+ err = snd_pcm_hw_params_get_buffer_size(params, &buffer_size);
+ if (err < 0) {
+ ERRORLOG("Unable to get buffer size : %s\n", snd_strerror(err));
+ return err;
+ }
+#endif
+ DEBUGLOG("buffersize %i\n", buffer_size);
+
+ /* set the period time */
+#if (SND_LIB_MAJOR < 1)
+ err = snd_pcm_hw_params_set_period_time_near(handle, params, period_time, &dir);
+#else
+ err = snd_pcm_hw_params_set_period_time_near(handle, params, &period_time, &dir);
+#endif
+ if (err < 0) {
+ ERRORLOG("Unable to set period time %i for playback: %s\n", period_time, snd_strerror(err));
+ return err;
+ }
+
+#if (SND_LIB_MAJOR < 1)
+ period_size = snd_pcm_hw_params_get_period_size(params, &dir);
+#else
+ err = snd_pcm_hw_params_get_period_size(params, &period_size, &dir);
+ if (err < 0) {
+ ERRORLOG("Unable to get hw period size: %s\n", snd_strerror(err));
+ }
+#endif
+
+ DEBUGLOG("period_size %i\n", period_size);
+
+ /* write the parameters to device */
+ err = snd_pcm_hw_params(handle, params);
+ if (err < 0) {
+ ERRORLOG("Unable to set hw params for playback: %s\n", snd_strerror(err));
+ return err;
+ }
+ return 0;
+}
+
+static int set_swparams(snd_pcm_sw_params_t *swparams)
+{
+ int err;
+
+ /* get the current swparams */
+ err = snd_pcm_sw_params_current(handle, swparams);
+ if (err < 0) {
+ ERRORLOG("Unable to determine current swparams for playback: %s\n", snd_strerror(err));
+ return err;
+ }
+ /* start the transfer when the buffer is full */
+ err = snd_pcm_sw_params_set_start_threshold(handle, swparams, buffer_size);
+ if (err < 0) {
+ ERRORLOG("Unable to set start threshold mode for playback: %s\n", snd_strerror(err));
+ return err;
+ }
+ /* allow the transfer when at least period_size samples can be processed */
+ err = snd_pcm_sw_params_set_avail_min(handle, swparams, period_size);
+ if (err < 0) {
+ ERRORLOG("Unable to set avail min for playback: %s\n", snd_strerror(err));
+ return err;
+ }
+ /* align all transfers to 1 sample */
+ err = snd_pcm_sw_params_set_xfer_align(handle, swparams, 1);
+ if (err < 0) {
+ ERRORLOG("Unable to set transfer align for playback: %s\n", snd_strerror(err));
+ return err;
+ }
+ /* write the parameters to the playback device */
+ err = snd_pcm_sw_params(handle, swparams);
+ if (err < 0) {
+ ERRORLOG("Unable to set sw params for playback: %s\n", snd_strerror(err));
+ return err;
+ }
+ return 0;
+}
+
+int alsa_open( void )
+{
+ int err;
+
+ snd_pcm_hw_params_t *hwparams;
+ snd_pcm_sw_params_t *swparams;
+
+ DEBUGLOG("alsa_open\n");
+
+ snd_pcm_hw_params_alloca(&hwparams);
+ snd_pcm_sw_params_alloca(&swparams);
+
+ if((err = snd_pcm_open(&handle, device, SND_PCM_STREAM_PLAYBACK, 0/*SND_PCM_NONBLOCK*/)) < 0 ) {
+ ERRORLOG("open failed: %s\n", snd_strerror(err));
+ return -1;
+ }
+
+ if((err = set_hwparams(hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
+ ERRORLOG("Setting of hwparams failed: %s\n", snd_strerror(err));
+ return -1;
+ }
+ if((err = set_swparams(swparams)) < 0) {
+ ERRORLOG("Setting of swparams failed: %s\n", snd_strerror(err));
+ return -1;
+ }
+
+ return 0;
+}
+
+int alsa_close( void )
+{
+ int err;
+
+ DEBUGLOG("alsa_close\n");
+
+ err = alsa_stop();
+
+#if (SND_LIB_MAJOR < 1)
+ err = snd_pcm_close(handle);
+#else
+ err = snd_pcm_close(handle);
+#endif
+
+ free(device);
+
+ return err;
+}
+
+/*
+ * Play some audio and pass a status message upstream, if applicable.
+ * Returns 0 on success.
+ */
+int
+alsa_play(struct cdda_block *blk)
+{
+ signed short *ptr;
+ int err = 0, frames;
+
+ ptr = (signed short *)blk->buf;
+ frames = blk->buflen / (channels * 2);
+ DEBUGLOG("play %i frames, %i bytes\n", frames, blk->buflen);
+ while (frames > 0) {
+ err = snd_pcm_writei(handle, ptr, frames);
+
+ if (err == -EAGAIN)
+ continue;
+ if(err == -EPIPE) {
+ err = snd_pcm_prepare(handle);
+ continue;
+ } else if (err < 0)
+ break;
+
+ ptr += err * channels;
+ frames -= err;
+ DEBUGLOG("played %i, rest %i\n", err / channels, frames);
+ }
+
+ if (err < 0) {
+ ERRORLOG("alsa_write failed: %s\n", snd_strerror(err));
+ err = snd_pcm_prepare(handle);
+
+ if (err < 0) {
+ ERRORLOG("Unable to snd_pcm_prepare pcm stream: %s\n", snd_strerror(err));
+ }
+ blk->status = WM_CDM_CDDAERROR;
+ return err;
+ }
+
+ return 0;
+}
+
+/*
+ * Stop the audio immediately.
+ */
+int
+alsa_stop( void )
+{
+ int err;
+
+ DEBUGLOG("alsa_stop\n");
+
+ err = snd_pcm_drop(handle);
+ if (err < 0) {
+ ERRORLOG("Unable to drop pcm stream: %s\n", snd_strerror(err));
+ }
+
+ err = snd_pcm_prepare(handle);
+ if (err < 0) {
+ ERRORLOG("Unable to snd_pcm_prepare pcm stream: %s\n", snd_strerror(err));
+ }
+
+ return err;
+}
+
+/*
+ * Get the current audio state.
+ */
+int
+alsa_state(struct cdda_block *blk)
+{
+ return -1; /* not implemented yet for ALSA */
+}
+
+static struct audio_oops alsa_oops = {
+ .wmaudio_open = alsa_open,
+ .wmaudio_close = alsa_close,
+ .wmaudio_play = alsa_play,
+ .wmaudio_stop = alsa_stop,
+ .wmaudio_state = alsa_state,
+ .wmaudio_balance = NULL,
+ .wmaudio_volume = NULL
+};
+
+struct audio_oops*
+setup_alsa(const char *dev, const char *ctl)
+{
+ static int init_complete = 0;
+
+ if(dev && strlen(dev) > 0) {
+ device = strdup(dev);
+ } else {
+ device = strdup("plughw:0,0"); /* playback device */
+ }
+
+ if(init_complete) {
+ ERRORLOG("already initialized\n");
+ return NULL;
+ }
+ if(!alsa_open())
+ init_complete = 1;
+ else
+ return NULL;
+
+ return &alsa_oops;
+}
+
+#endif /* ALSA */