push
This commit is contained in:
495
output.c
Normal file
495
output.c
Normal file
@@ -0,0 +1,495 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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 <string.h>
|
||||
#include <strings.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
#include <dirent.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user