/*
* Copyright 2008-2013 Various Authors
* Copyright 2008 Jonathan Kleinehellefort
* Copyright 2004-2005 Timo Hirvonen
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
*/
/*
* snd_pcm_state_t:
*
* Open
* SND_PCM_STATE_OPEN = 0,
*
* Setup installed
* SND_PCM_STATE_SETUP = 1,
*
* Ready to start
* SND_PCM_STATE_PREPARED = 2,
*
* Running
* SND_PCM_STATE_RUNNING = 3,
*
* Stopped: underrun (playback) or overrun (capture) detected
* SND_PCM_STATE_XRUN = 4,
*
* Draining: running (playback) or stopped (capture)
* SND_PCM_STATE_DRAINING = 5,
*
* Paused
* SND_PCM_STATE_PAUSED = 6,
*
* Hardware is suspended
* SND_PCM_STATE_SUSPENDED = 7,
*
* Hardware is disconnected
* SND_PCM_STATE_DISCONNECTED = 8,
*/
#include "../op.h"
#include "../utils.h"
#include "../xmalloc.h"
#include "../sf.h"
#include "../debug.h"
#define ALSA_PCM_NEW_HW_PARAMS_API
#define ALSA_PCM_NEW_SW_PARAMS_API
#include
static sample_format_t alsa_sf;
static snd_pcm_t *alsa_handle;
static snd_pcm_format_t alsa_fmt;
static int alsa_can_pause;
static snd_pcm_status_t *status;
/* bytes (bits * channels / 8) */
static int alsa_frame_size;
/* configuration */
static char *alsa_dsp_device = NULL;
#if 0
#define debug_ret(func, ret) \
d_print("%s returned %d %s\n", func, ret, ret < 0 ? snd_strerror(ret) : "")
#else
#define debug_ret(func, ret) do { } while (0)
#endif
static int alsa_error_to_op_error(int err)
{
if (!err)
return OP_ERROR_SUCCESS;
err = -err;
if (err < SND_ERROR_BEGIN) {
errno = err;
return -OP_ERROR_ERRNO;
}
return -OP_ERROR_INTERNAL;
}
/* we don't want error messages to stderr */
static void error_handler(const char *file, int line, const char *function, int err, const char *fmt, ...)
{
}
static int op_alsa_init(void)
{
int rc;
snd_lib_error_set_handler(error_handler);
if (alsa_dsp_device == NULL)
alsa_dsp_device = xstrdup("default");
rc = snd_pcm_status_malloc(&status);
if (rc < 0) {
free(alsa_dsp_device);
alsa_dsp_device = NULL;
errno = ENOMEM;
return -OP_ERROR_ERRNO;
}
return OP_ERROR_SUCCESS;
}
static int op_alsa_exit(void)
{
snd_pcm_status_free(status);
free(alsa_dsp_device);
alsa_dsp_device = NULL;
return OP_ERROR_SUCCESS;
}
/* randomize hw params */
static int alsa_set_hw_params(void)
{
snd_pcm_hw_params_t *hwparams = NULL;
unsigned int buffer_time_max = 300 * 1000; /* us */
const char *cmd;
unsigned int rate;
int rc, dir;
snd_pcm_hw_params_malloc(&hwparams);
cmd = "snd_pcm_hw_params_any";
rc = snd_pcm_hw_params_any(alsa_handle, hwparams);
if (rc < 0)
goto error;
cmd = "snd_pcm_hw_params_set_buffer_time_max";
rc = snd_pcm_hw_params_set_buffer_time_max(alsa_handle, hwparams,
&buffer_time_max, &dir);
if (rc < 0)
goto error;
alsa_can_pause = snd_pcm_hw_params_can_pause(hwparams);
d_print("can pause = %d\n", alsa_can_pause);
cmd = "snd_pcm_hw_params_set_access";
rc = snd_pcm_hw_params_set_access(alsa_handle, hwparams,
SND_PCM_ACCESS_RW_INTERLEAVED);
if (rc < 0)
goto error;
alsa_fmt = snd_pcm_build_linear_format(sf_get_bits(alsa_sf), sf_get_bits(alsa_sf),
sf_get_signed(alsa_sf) ? 0 : 1,
sf_get_bigendian(alsa_sf));
cmd = "snd_pcm_hw_params_set_format";
rc = snd_pcm_hw_params_set_format(alsa_handle, hwparams, alsa_fmt);
if (rc < 0)
goto error;
cmd = "snd_pcm_hw_params_set_channels";
rc = snd_pcm_hw_params_set_channels(alsa_handle, hwparams, sf_get_channels(alsa_sf));
if (rc < 0)
goto error;
cmd = "snd_pcm_hw_params_set_rate";
rate = sf_get_rate(alsa_sf);
dir = 0;
rc = snd_pcm_hw_params_set_rate_near(alsa_handle, hwparams, &rate, &dir);
if (rc < 0)
goto error;
d_print("rate=%d\n", rate);
cmd = "snd_pcm_hw_params";
rc = snd_pcm_hw_params(alsa_handle, hwparams);
if (rc < 0)
goto error;
goto out;
error:
d_print("%s: error: %s\n", cmd, snd_strerror(rc));
out:
snd_pcm_hw_params_free(hwparams);
return rc;
}
static int op_alsa_open(sample_format_t sf, const channel_position_t *channel_map)
{
int rc;
alsa_sf = sf;
alsa_frame_size = sf_get_frame_size(alsa_sf);
rc = snd_pcm_open(&alsa_handle, alsa_dsp_device, SND_PCM_STREAM_PLAYBACK, 0);
if (rc < 0)
goto error;
rc = alsa_set_hw_params();
if (rc)
goto close_error;
rc = snd_pcm_prepare(alsa_handle);
if (rc < 0)
goto close_error;
return OP_ERROR_SUCCESS;
close_error:
snd_pcm_close(alsa_handle);
error:
return alsa_error_to_op_error(rc);
}
static int op_alsa_write(const char *buffer, int count);
static int op_alsa_close(void)
{
int rc;
rc = snd_pcm_drain(alsa_handle);
debug_ret("snd_pcm_drain", rc);
rc = snd_pcm_close(alsa_handle);
debug_ret("snd_pcm_close", rc);
return alsa_error_to_op_error(rc);
}
static int op_alsa_drop(void)
{
int rc;
rc = snd_pcm_drop(alsa_handle);
debug_ret("snd_pcm_drop", rc);
rc = snd_pcm_prepare(alsa_handle);
debug_ret("snd_pcm_prepare", rc);
/* drop set state to SETUP
* prepare set state to PREPARED
*
* so if old state was PAUSED we can't UNPAUSE (see op_alsa_unpause)
*/
return alsa_error_to_op_error(rc);
}
static int op_alsa_write(const char *buffer, int count)
{
int rc, len;
int recovered = 0;
len = count / alsa_frame_size;
again:
rc = snd_pcm_writei(alsa_handle, buffer, len);
if (rc < 0) {
// rc _should_ be either -EBADFD, -EPIPE or -ESTRPIPE
if (!recovered && (rc == -EINTR || rc == -EPIPE || rc == -ESTRPIPE)) {
d_print("snd_pcm_writei failed: %s, trying to recover\n",
snd_strerror(rc));
recovered++;
// this handles -EINTR, -EPIPE and -ESTRPIPE
// for other errors it just returns the error code
rc = snd_pcm_recover(alsa_handle, rc, 1);
if (!rc)
goto again;
}
/* this handles EAGAIN too which is not critical error */
return alsa_error_to_op_error(rc);
}
rc *= alsa_frame_size;
return rc;
}
static int op_alsa_buffer_space(void)
{
int rc;
snd_pcm_sframes_t f;
f = snd_pcm_avail_update(alsa_handle);
while (f < 0) {
d_print("snd_pcm_avail_update failed: %s, trying to recover\n",
snd_strerror(f));
rc = snd_pcm_recover(alsa_handle, f, 1);
if (rc < 0) {
d_print("recovery failed: %s\n", snd_strerror(rc));
return alsa_error_to_op_error(rc);
}
f = snd_pcm_avail_update(alsa_handle);
}
return f * alsa_frame_size;
}
static int op_alsa_pause(void)
{
int rc = 0;
if (alsa_can_pause) {
snd_pcm_state_t state = snd_pcm_state(alsa_handle);
if (state == SND_PCM_STATE_PREPARED) {
// state is PREPARED -> no need to pause
} else if (state == SND_PCM_STATE_RUNNING) {
// state is RUNNING - > pause
// infinite timeout
rc = snd_pcm_wait(alsa_handle, -1);
debug_ret("snd_pcm_wait", rc);
rc = snd_pcm_pause(alsa_handle, 1);
debug_ret("snd_pcm_pause", rc);
} else {
d_print("error: state is not RUNNING or PREPARED\n");
rc = -OP_ERROR_INTERNAL;
}
} else {
rc = snd_pcm_drop(alsa_handle);
debug_ret("snd_pcm_drop", rc);
}
return alsa_error_to_op_error(rc);
}
static int op_alsa_unpause(void)
{
int rc = 0;
if (alsa_can_pause) {
snd_pcm_state_t state = snd_pcm_state(alsa_handle);
if (state == SND_PCM_STATE_PREPARED) {
// state is PREPARED -> no need to unpause
} else if (state == SND_PCM_STATE_PAUSED) {
// state is PAUSED -> unpause
// infinite timeout
rc = snd_pcm_wait(alsa_handle, -1);
debug_ret("snd_pcm_wait", rc);
rc = snd_pcm_pause(alsa_handle, 0);
debug_ret("snd_pcm_pause", rc);
} else {
d_print("error: state is not PAUSED nor PREPARED\n");
rc = -OP_ERROR_INTERNAL;
}
} else {
rc = snd_pcm_prepare(alsa_handle);
debug_ret("snd_pcm_prepare", rc);
}
return alsa_error_to_op_error(rc);
}
static int op_alsa_set_device(const char *val)
{
free(alsa_dsp_device);
alsa_dsp_device = xstrdup(val);
return OP_ERROR_SUCCESS;
}
static int op_alsa_get_device(char **val)
{
if (alsa_dsp_device)
*val = xstrdup(alsa_dsp_device);
return OP_ERROR_SUCCESS;
}
const struct output_plugin_ops op_pcm_ops = {
.init = op_alsa_init,
.exit = op_alsa_exit,
.open = op_alsa_open,
.close = op_alsa_close,
.drop = op_alsa_drop,
.write = op_alsa_write,
.buffer_space = op_alsa_buffer_space,
.pause = op_alsa_pause,
.unpause = op_alsa_unpause,
};
const struct output_plugin_opt op_pcm_options[] = {
OPT(op_alsa, device),
{ NULL },
};
const int op_priority = 0;
const unsigned op_abi_version = OP_ABI_VERSION;