/*
* Copyright 2008-2013 Various Authors
* Copyright 2006 Johannes Weißl
*
* 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 .
*/
#include "../op.h"
#include "../xmalloc.h"
#include "../utils.h"
#include "../misc.h"
#include "../debug.h"
/*
* uses FILE but doesn't include stdio.h.
* Also we use snprintf().
*/
#include
#include
#include
#ifdef AO_EBADFORMAT
#define AO_API_1
#endif
static ao_device *libao_device;
static char *wav_dir = NULL;
static int wav_counter = 1;
static int is_wav = 0;
/* configuration */
static char *libao_driver = NULL;
static int libao_buffer_space = 16384;
static int libao_cur_buffer_space = 0;
static char *libao_device_interface = NULL;
static int op_ao_init(void)
{
/* ignore config value */
wav_counter = 1;
ao_initialize();
return 0;
}
static int op_ao_exit(void)
{
free(libao_driver);
ao_shutdown();
return 0;
}
/* http://www.xiph.org/ao/doc/ao_sample_format.html */
static const struct {
channel_position_t pos;
const char *str;
} ao_channel_mapping[] = {
{ CHANNEL_POSITION_LEFT, "L" },
{ CHANNEL_POSITION_RIGHT, "R" },
{ CHANNEL_POSITION_CENTER, "C" },
{ CHANNEL_POSITION_MONO, "M" },
{ CHANNEL_POSITION_FRONT_LEFT_OF_CENTER, "CL" },
{ CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER, "CR" },
{ CHANNEL_POSITION_REAR_LEFT, "BL" },
{ CHANNEL_POSITION_REAR_RIGHT, "BR" },
{ CHANNEL_POSITION_REAR_CENTER, "BC" },
{ CHANNEL_POSITION_SIDE_LEFT, "SL" },
{ CHANNEL_POSITION_SIDE_RIGHT, "SR" },
{ CHANNEL_POSITION_LFE, "LFE" },
{ CHANNEL_POSITION_INVALID, "X" },
};
#ifdef AO_API_1
static char *ao_channel_matrix(int channels, const channel_position_t *map)
{
int i, j;
char buf[256] = "";
if (!map || !channel_map_valid(map))
return NULL;
for (i = 0; i < channels; i++) {
const channel_position_t pos = map[i];
int found = 0;
for (j = 0; j < N_ELEMENTS(ao_channel_mapping); j++) {
if (pos == ao_channel_mapping[j].pos) {
strcat(buf, ao_channel_mapping[j].str);
strcat(buf, ",");
found = 1;
break;
}
}
if (!found)
strcat(buf, "M,");
}
buf[strlen(buf)-1] = '\0';
return xstrdup(buf);
}
#endif
static int op_ao_open(sample_format_t sf, const channel_position_t *channel_map)
{
ao_sample_format format = {
.bits = sf_get_bits(sf),
.rate = sf_get_rate(sf),
.channels = sf_get_channels(sf),
.byte_format = sf_get_bigendian(sf) ? AO_FMT_BIG : AO_FMT_LITTLE,
#ifdef AO_API_1
.matrix = ao_channel_matrix(sf_get_channels(sf), channel_map)
#endif
};
int driver;
if (libao_driver == NULL) {
driver = ao_default_driver_id();
} else {
driver = ao_driver_id(libao_driver);
is_wav = strcasecmp(libao_driver, "wav") == 0;
}
if (driver == -1) {
errno = ENODEV;
return -OP_ERROR_ERRNO;
}
if (is_wav) {
char file[512];
if (wav_dir == NULL)
wav_dir = xstrdup(home_dir);
snprintf(file, sizeof(file), "%s/%02d.wav", wav_dir, wav_counter);
libao_device = ao_open_file(driver, file, 0, &format, NULL);
} else {
ao_option *ao_options = NULL;
if (libao_device_interface) {
ao_append_option(&ao_options, "dev", libao_device_interface);
}
libao_device = ao_open_live(driver, &format, ao_options);
}
if (libao_device == NULL) {
switch (errno) {
case AO_ENODRIVER:
case AO_ENOTFILE:
case AO_ENOTLIVE:
case AO_EOPENDEVICE:
errno = ENODEV;
return -OP_ERROR_ERRNO;
case AO_EBADOPTION:
errno = EINVAL;
return -OP_ERROR_ERRNO;
case AO_EOPENFILE:
errno = EACCES;
return -OP_ERROR_ERRNO;
case AO_EFILEEXISTS:
errno = EEXIST;
return -OP_ERROR_ERRNO;
case AO_EFAIL:
default:
return -OP_ERROR_INTERNAL;
}
}
/* ensure that the buffer size is a multiple of the frame size */
libao_cur_buffer_space = is_wav ? 128 * 1024 : libao_buffer_space;
libao_cur_buffer_space -= libao_cur_buffer_space % sf_get_frame_size(sf);
#ifdef AO_API_1
d_print("channel matrix: %s\n", format.matrix ? format.matrix : "default");
#endif
return 0;
}
static int op_ao_close(void)
{
ao_close(libao_device);
if (is_wav)
wav_counter++;
return 0;
}
static int op_ao_write(const char *buffer, int count)
{
if (ao_play(libao_device, (void *)buffer, count) == 0)
return -1;
return count;
}
static int op_ao_buffer_space(void)
{
return libao_cur_buffer_space;
}
static int op_ao_set_buffer_size(const char *val)
{
long int ival;
if (str_to_int(val, &ival) || ival < 4096) {
errno = EINVAL;
return -OP_ERROR_ERRNO;
}
libao_buffer_space = ival;
return 0;
}
static int op_ao_get_buffer_size(char **val)
{
*val = xnew(char, 22);
snprintf(*val, 22, "%d", libao_buffer_space);
return 0;
}
static int op_ao_set_driver(const char *val)
{
free(libao_driver);
libao_driver = NULL;
if (val[0])
libao_driver = xstrdup(val);
return 0;
}
static int op_ao_get_driver(char **val)
{
if (libao_driver)
*val = xstrdup(libao_driver);
return 0;
}
static int op_ao_set_wav_counter(const char *val)
{
long int ival;
if (str_to_int(val, &ival)) {
errno = EINVAL;
return -OP_ERROR_ERRNO;
}
wav_counter = ival;
return 0;
}
static int op_ao_get_wav_counter(char **val)
{
*val = xnew(char, 22);
snprintf(*val, 22, "%d", wav_counter);
return 0;
}
static int op_ao_set_wav_dir(const char *val)
{
free(wav_dir);
wav_dir = xstrdup(val);
return 0;
}
static int op_ao_get_wav_dir(char **val)
{
if (wav_dir == NULL)
wav_dir = xstrdup(home_dir);
*val = expand_filename(wav_dir);
return 0;
}
static int op_ao_set_device_interface(const char *val)
{
free(libao_device_interface);
libao_device_interface = NULL;
if (val[0])
libao_device_interface = xstrdup(val);
return 0;
}
static int op_ao_get_device_interface(char **val)
{
if (libao_device_interface)
*val = xstrdup(libao_device_interface);
return 0;
}
const struct output_plugin_ops op_pcm_ops = {
.init = op_ao_init,
.exit = op_ao_exit,
.open = op_ao_open,
.close = op_ao_close,
.write = op_ao_write,
.buffer_space = op_ao_buffer_space,
};
const struct output_plugin_opt op_pcm_options[] = {
OPT(op_ao, buffer_size),
OPT(op_ao, driver),
OPT(op_ao, wav_counter),
OPT(op_ao, wav_dir),
OPT(op_ao, device_interface),
{ NULL },
};
const int op_priority = 3;
const unsigned op_abi_version = OP_ABI_VERSION;