Logo Search packages:      
Sourcecode: cmus version File versions  Download package

output.c

/* 
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 */

#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 "config/libdir.h"

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <dirent.h>
#include <dlfcn.h>

00039 struct output_plugin {
      struct list_head node;
      char *name;
      void *handle;

      const struct output_plugin_ops *pcm_ops;
      const struct mixer_plugin_ops *mixer_ops;
      const char * const *pcm_options;
      const char * const *mixer_options;
      int priority;

      unsigned int pcm_initialized : 1;
      unsigned int mixer_initialized : 1;
      unsigned int mixer_open : 1;
};

static const char * const plugin_dir = LIBDIR "/cmus/op";
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;

      dir = opendir(plugin_dir);
      if (dir == NULL) {
            error_msg("couldn't open directory `%s': %s", plugin_dir, strerror(errno));
            return;
      }
      while ((d = readdir(dir)) != NULL) {
            char filename[256];
            struct output_plugin *plug;
            void *so, *symptr;
            char *ext;

            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) {
                  error_msg("%s", 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");
            if (!plug->pcm_ops || !plug->pcm_options || !symptr) {
                  error_msg("%s: missing symbol", filename);
                  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;

      list_for_each_entry(o, &op_head, node) {
            rc = select_plugin(o);
            if (rc == 0)
                  break;
      }
      return rc;
}

int op_open(sample_format_t sf)
{
      if (op == NULL)
            return -OP_ERROR_NOT_INITIALIZED;
      return op->pcm_ops->open(sf);
}

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)
{
      int rc;

      rc = op->pcm_ops->write(buffer, count);
      return rc;
}

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)
{
      int rc;
      
      rc = op->pcm_ops->buffer_space();
      return rc;
}

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 *fds)
{
      if (op == NULL)
            return -OP_ERROR_NOT_INITIALIZED;
      if (!op->mixer_open)
            return -OP_ERROR_NOT_OPEN;
      if (!op->mixer_ops->get_fds)
            return -OP_ERROR_NOT_SUPPORTED;
      return op->mixer_ops->get_fds(fds);
}

static struct output_plugin *find_plugin(int idx)
{
      struct output_plugin *o;

      list_for_each_entry(o, &op_head, node) {
            if (idx == 0)
                  return o;
            idx--;
      }
      return NULL;
}

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(unsigned int id, const char *val)
{
      const struct output_plugin *o = find_plugin(id >> 16);
      int rc;

      rc = o->pcm_ops->set_option(id & 0xffff, val);
      if (rc)
            option_error(rc);
}

static void set_mixer_option(unsigned int id, const char *val)
{
      const struct output_plugin *o = find_plugin(id >> 16);
      int rc;

      rc = o->mixer_ops->set_option(id & 0xffff, val);
      if (rc) {
            option_error(rc);
            return;
      }
      if (op && op->mixer_ops == o->mixer_ops) {
            /* option of the current op was set
             * try to reopen the mixer */
            mixer_close();
            if (!soft_vol)
                  mixer_open();
      }
}

static void get_dsp_option(unsigned int id, char *buf)
{
      const struct output_plugin *o = find_plugin(id >> 16);
      char *val = NULL;

      o->pcm_ops->get_option(id & 0xffff, &val);
      if (val) {
            strcpy(buf, val);
            free(val);
      }
}

static void get_mixer_option(unsigned int id, char *buf)
{
      const struct output_plugin *o = find_plugin(id >> 16);
      char *val = NULL;

      o->mixer_ops->get_option(id & 0xffff, &val);
      if (val) {
            strcpy(buf, val);
            free(val);
      }
}

void op_add_options(void)
{
      struct output_plugin *o;
      unsigned int oid, pid = 0;
      char key[64];

      list_for_each_entry(o, &op_head, node) {
            for (oid = 0; o->pcm_options[oid]; oid++) {
                  snprintf(key, sizeof(key), "dsp.%s.%s",
                              o->name,
                              o->pcm_options[oid]);
                  option_add(xstrdup(key), (pid << 16) | oid, get_dsp_option, set_dsp_option, NULL);
            }
            for (oid = 0; o->mixer_ops && o->mixer_options[oid]; oid++) {
                  snprintf(key, sizeof(key), "mixer.%s.%s",
                              o->name,
                              o->mixer_options[oid]);
                  option_add(xstrdup(key), (pid << 16) | oid, get_mixer_option, set_mixer_option, NULL);
            }
            pid++;
      }
}

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

Generated by  Doxygen 1.6.0   Back to index