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