/*
* Copyright 2008-2013 Various Authors
* Copyright 2004-2006 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 .
*/
#include "output.h"
#include "op.h"
#include "mixer.h"
#include "sf.h"
#include "utils.h"
#include "xmalloc.h"
#include "list.h"
#include "debug.h"
#include "ui_curses.h"
#include "options.h"
#include "xstrjoin.h"
#include "misc.h"
#include
#include
#include
#include
#include
#include
#include
#include
struct output_plugin {
struct list_head node;
char *name;
void *handle;
const unsigned *abi_version_ptr;
const struct output_plugin_ops *pcm_ops;
const struct mixer_plugin_ops *mixer_ops;
const struct output_plugin_opt *pcm_options;
const struct mixer_plugin_opt *mixer_options;
int priority;
unsigned int pcm_initialized : 1;
unsigned int mixer_initialized : 1;
unsigned int mixer_open : 1;
};
static const char *plugin_dir;
static LIST_HEAD(op_head);
static struct output_plugin *op = NULL;
/* volume is between 0 and volume_max */
int volume_max = 0;
int volume_l = -1;
int volume_r = -1;
static void add_plugin(struct output_plugin *plugin)
{
struct list_head *item = op_head.next;
while (item != &op_head) {
struct output_plugin *o = container_of(item, struct output_plugin, node);
if (plugin->priority < o->priority)
break;
item = item->next;
}
/* add before item */
list_add_tail(&plugin->node, item);
}
void op_load_plugins(void)
{
DIR *dir;
struct dirent *d;
plugin_dir = xstrjoin(cmus_lib_dir, "/op");
dir = opendir(plugin_dir);
if (dir == NULL) {
error_msg("couldn't open directory `%s': %s", plugin_dir, strerror(errno));
return;
}
while ((d = (struct dirent *) readdir(dir)) != NULL) {
char filename[512];
struct output_plugin *plug;
void *so, *symptr;
char *ext;
bool err = false;
if (d->d_name[0] == '.')
continue;
ext = strrchr(d->d_name, '.');
if (ext == NULL)
continue;
if (strcmp(ext, ".so"))
continue;
snprintf(filename, sizeof(filename), "%s/%s", plugin_dir, d->d_name);
so = dlopen(filename, RTLD_NOW);
if (so == NULL) {
d_print("%s: %s\n", filename, dlerror());
continue;
}
plug = xnew(struct output_plugin, 1);
plug->pcm_ops = dlsym(so, "op_pcm_ops");
plug->pcm_options = dlsym(so, "op_pcm_options");
symptr = dlsym(so, "op_priority");
plug->abi_version_ptr = dlsym(so, "op_abi_version");
if (!plug->pcm_ops || !plug->pcm_options || !symptr) {
error_msg("%s: missing symbol", filename);
err = true;
}
if (!plug->abi_version_ptr || *plug->abi_version_ptr != OP_ABI_VERSION) {
error_msg("%s: incompatible plugin version", filename);
err = true;
}
if (err) {
free(plug);
dlclose(so);
continue;
}
plug->priority = *(int *)symptr;
plug->mixer_ops = dlsym(so, "op_mixer_ops");
plug->mixer_options = dlsym(so, "op_mixer_options");
if (plug->mixer_ops == NULL || plug->mixer_options == NULL) {
plug->mixer_ops = NULL;
plug->mixer_options = NULL;
}
plug->name = xstrndup(d->d_name, ext - d->d_name);
plug->handle = so;
plug->pcm_initialized = 0;
plug->mixer_initialized = 0;
plug->mixer_open = 0;
add_plugin(plug);
}
closedir(dir);
}
static void init_plugin(struct output_plugin *o)
{
if (!o->mixer_initialized && o->mixer_ops) {
if (o->mixer_ops->init() == 0) {
d_print("initialized mixer for %s\n", o->name);
o->mixer_initialized = 1;
} else {
d_print("could not initialize mixer `%s'\n", o->name);
}
}
if (!o->pcm_initialized) {
if (o->pcm_ops->init() == 0) {
d_print("initialized pcm for %s\n", o->name);
o->pcm_initialized = 1;
} else {
d_print("could not initialize pcm `%s'\n", o->name);
}
}
}
void op_exit_plugins(void)
{
struct output_plugin *o;
list_for_each_entry(o, &op_head, node) {
if (o->mixer_initialized && o->mixer_ops)
o->mixer_ops->exit();
if (o->pcm_initialized)
o->pcm_ops->exit();
}
}
void mixer_close(void)
{
volume_max = 0;
if (op && op->mixer_open) {
BUG_ON(op->mixer_ops == NULL);
op->mixer_ops->close();
op->mixer_open = 0;
}
}
void mixer_open(void)
{
if (op == NULL)
return;
BUG_ON(op->mixer_open);
if (op->mixer_ops && op->mixer_initialized) {
int rc;
rc = op->mixer_ops->open(&volume_max);
if (rc == 0) {
op->mixer_open = 1;
mixer_read_volume();
} else {
volume_max = 0;
}
}
}
static int select_plugin(struct output_plugin *o)
{
/* try to initialize if not initialized yet */
init_plugin(o);
if (!o->pcm_initialized)
return -OP_ERROR_NOT_INITIALIZED;
op = o;
return 0;
}
int op_select(const char *name)
{
struct output_plugin *o;
list_for_each_entry(o, &op_head, node) {
if (strcasecmp(name, o->name) == 0)
return select_plugin(o);
}
return -OP_ERROR_NO_PLUGIN;
}
int op_select_any(void)
{
struct output_plugin *o;
int rc = -OP_ERROR_NO_PLUGIN;
sample_format_t sf = sf_channels(2) | sf_rate(44100) | sf_bits(16) | sf_signed(1);
list_for_each_entry(o, &op_head, node) {
rc = select_plugin(o);
if (rc != 0)
continue;
rc = o->pcm_ops->open(sf, NULL);
if (rc == 0) {
o->pcm_ops->close();
break;
}
}
return rc;
}
int op_open(sample_format_t sf, const channel_position_t *channel_map)
{
if (op == NULL)
return -OP_ERROR_NOT_INITIALIZED;
return op->pcm_ops->open(sf, channel_map);
}
int op_drop(void)
{
if (op->pcm_ops->drop == NULL)
return -OP_ERROR_NOT_SUPPORTED;
return op->pcm_ops->drop();
}
int op_close(void)
{
return op->pcm_ops->close();
}
int op_write(const char *buffer, int count)
{
return op->pcm_ops->write(buffer, count);
}
int op_pause(void)
{
if (op->pcm_ops->pause == NULL)
return 0;
return op->pcm_ops->pause();
}
int op_unpause(void)
{
if (op->pcm_ops->unpause == NULL)
return 0;
return op->pcm_ops->unpause();
}
int op_buffer_space(void)
{
return op->pcm_ops->buffer_space();
}
int mixer_set_volume(int left, int right)
{
if (op == NULL)
return -OP_ERROR_NOT_INITIALIZED;
if (!op->mixer_open)
return -OP_ERROR_NOT_OPEN;
return op->mixer_ops->set_volume(left, right);
}
int mixer_read_volume(void)
{
if (op == NULL)
return -OP_ERROR_NOT_INITIALIZED;
if (!op->mixer_open)
return -OP_ERROR_NOT_OPEN;
return op->mixer_ops->get_volume(&volume_l, &volume_r);
}
int mixer_get_fds(int what, int *fds)
{
if (op == NULL)
return -OP_ERROR_NOT_INITIALIZED;
if (!op->mixer_open)
return -OP_ERROR_NOT_OPEN;
switch (*op->abi_version_ptr) {
case 1:
if (!op->mixer_ops->get_fds.abi_1)
return -OP_ERROR_NOT_SUPPORTED;
if (what != MIXER_FDS_VOLUME)
return 0;
return op->mixer_ops->get_fds.abi_1(fds);
default:
if (!op->mixer_ops->get_fds.abi_2)
return -OP_ERROR_NOT_SUPPORTED;
return op->mixer_ops->get_fds.abi_2(what, fds);
}
}
extern int soft_vol;
static void option_error(int rc)
{
char *msg = op_get_error_msg(rc, "setting option");
error_msg("%s", msg);
free(msg);
}
static void set_dsp_option(void *data, const char *val)
{
const struct output_plugin_opt *o = data;
int rc;
rc = o->set(val);
if (rc)
option_error(rc);
}
static bool option_of_current_mixer(const struct mixer_plugin_opt *opt)
{
const struct mixer_plugin_opt *mpo;
if (!op)
return false;
for (mpo = op->mixer_options; mpo && mpo->name; mpo++) {
if (mpo == opt)
return true;
}
return false;
}
static void set_mixer_option(void *data, const char *val)
{
const struct mixer_plugin_opt *o = data;
int rc;
rc = o->set(val);
if (rc) {
option_error(rc);
} else if (option_of_current_mixer(o)) {
/* option of the current op was set
* try to reopen the mixer */
mixer_close();
if (!soft_vol || pause_on_output_change)
mixer_open();
}
}
static void get_dsp_option(void *data, char *buf, size_t size)
{
const struct output_plugin_opt *o = data;
char *val = NULL;
o->get(&val);
if (val) {
strscpy(buf, val, size);
free(val);
}
}
static void get_mixer_option(void *data, char *buf, size_t size)
{
const struct mixer_plugin_opt *o = data;
char *val = NULL;
o->get(&val);
if (val) {
strscpy(buf, val, size);
free(val);
}
}
void op_add_options(void)
{
struct output_plugin *o;
const struct output_plugin_opt *opo;
const struct mixer_plugin_opt *mpo;
char key[64];
list_for_each_entry(o, &op_head, node) {
for (opo = o->pcm_options; opo->name; opo++) {
snprintf(key, sizeof(key), "dsp.%s.%s", o->name,
opo->name);
option_add(xstrdup(key), opo, get_dsp_option,
set_dsp_option, NULL, 0);
}
for (mpo = o->mixer_options; mpo && mpo->name; mpo++) {
snprintf(key, sizeof(key), "mixer.%s.%s", o->name,
mpo->name);
option_add(xstrdup(key), mpo, get_mixer_option,
set_mixer_option, NULL, 0);
}
}
}
char *op_get_error_msg(int rc, const char *arg)
{
char buffer[1024];
switch (-rc) {
case OP_ERROR_ERRNO:
snprintf(buffer, sizeof(buffer), "%s: %s", arg, strerror(errno));
break;
case OP_ERROR_NO_PLUGIN:
snprintf(buffer, sizeof(buffer),
"%s: no such plugin", arg);
break;
case OP_ERROR_NOT_INITIALIZED:
snprintf(buffer, sizeof(buffer),
"%s: couldn't initialize required output plugin", arg);
break;
case OP_ERROR_NOT_SUPPORTED:
snprintf(buffer, sizeof(buffer),
"%s: function not supported", arg);
break;
case OP_ERROR_NOT_OPEN:
snprintf(buffer, sizeof(buffer),
"%s: mixer is not open", arg);
break;
case OP_ERROR_SAMPLE_FORMAT:
snprintf(buffer, sizeof(buffer),
"%s: sample format not supported", arg);
break;
case OP_ERROR_NOT_OPTION:
snprintf(buffer, sizeof(buffer),
"%s: no such option", arg);
break;
case OP_ERROR_INTERNAL:
snprintf(buffer, sizeof(buffer), "%s: internal error", arg);
break;
case OP_ERROR_SUCCESS:
default:
snprintf(buffer, sizeof(buffer),
"%s: this is not an error (%d), this is a bug",
arg, rc);
break;
}
return xstrdup(buffer);
}
void op_dump_plugins(void)
{
struct output_plugin *o;
printf("\nOutput Plugins: %s\n", plugin_dir);
list_for_each_entry(o, &op_head, node) {
printf(" %s\n", o->name);
}
}
const char *op_get_current(void)
{
if (op)
return op->name;
return NULL;
}