You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdemultimedia/kscd/libwm/audio/audio_alsa.c

335 lines
9.3 KiB

/*
* 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 */