Document Replay Gain
[cmus.git] / output.c
blobbaba22ceb78c192ca66b00ec23311f8d6de08d1e
1 /*
2 * Copyright 2004-2006 Timo Hirvonen
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
17 * 02111-1307, USA.
20 #include "output.h"
21 #include "op.h"
22 #include "mixer.h"
23 #include "sf.h"
24 #include "utils.h"
25 #include "xmalloc.h"
26 #include "list.h"
27 #include "debug.h"
28 #include "ui_curses.h"
29 #include "config/libdir.h"
31 #include <string.h>
32 #include <stdlib.h>
33 #include <sys/types.h>
34 #include <dirent.h>
35 #include <dlfcn.h>
37 struct output_plugin {
38 struct list_head node;
39 char *name;
40 void *handle;
42 const struct output_plugin_ops *pcm_ops;
43 const struct mixer_plugin_ops *mixer_ops;
44 const char * const *pcm_options;
45 const char * const *mixer_options;
46 int priority;
48 unsigned int pcm_initialized : 1;
49 unsigned int mixer_initialized : 1;
50 unsigned int mixer_open : 1;
53 static const char * const plugin_dir = LIBDIR "/cmus/op";
54 static LIST_HEAD(op_head);
55 static struct output_plugin *op = NULL;
57 int soft_vol;
58 int soft_vol_l = 100;
59 int soft_vol_r = 100;
61 /* volume is between 0 and volume_max */
62 int volume_max = 0;
64 static void add_plugin(struct output_plugin *plugin)
66 struct list_head *item = op_head.next;
68 while (item != &op_head) {
69 struct output_plugin *o = container_of(item, struct output_plugin, node);
71 if (plugin->priority < o->priority)
72 break;
73 item = item->next;
76 /* add before item */
77 list_add_tail(&plugin->node, item);
80 void op_load_plugins(void)
82 DIR *dir;
83 struct dirent *d;
85 dir = opendir(plugin_dir);
86 if (dir == NULL) {
87 error_msg("couldn't open directory `%s': %s", plugin_dir, strerror(errno));
88 return;
90 while ((d = readdir(dir)) != NULL) {
91 char filename[256];
92 struct output_plugin *plug;
93 void *so, *symptr;
94 char *ext;
95 const char *sym;
97 if (d->d_name[0] == '.')
98 continue;
99 ext = strrchr(d->d_name, '.');
100 if (ext == NULL)
101 continue;
102 if (strcmp(ext, ".so"))
103 continue;
105 snprintf(filename, sizeof(filename), "%s/%s", plugin_dir, d->d_name);
107 so = dlopen(filename, RTLD_NOW);
108 if (so == NULL) {
109 error_msg("%s", dlerror());
110 continue;
113 plug = xnew(struct output_plugin, 1);
115 sym = "op_pcm_ops";
116 if (!(plug->pcm_ops = dlsym(so, sym)))
117 goto sym_err;
119 sym = "op_pcm_options";
120 if (!(plug->pcm_options = dlsym(so, sym)))
121 goto sym_err;
123 sym = "op_priority";
124 symptr = dlsym(so, sym);
125 if (symptr == NULL)
126 goto sym_err;
127 plug->priority = *(int *)symptr;
129 plug->mixer_ops = dlsym(so, "op_mixer_ops");
130 plug->mixer_options = dlsym(so, "op_mixer_options");
132 if (plug->mixer_ops == NULL || plug->mixer_options == NULL) {
133 plug->mixer_ops = NULL;
134 plug->mixer_options = NULL;
137 plug->name = xstrndup(d->d_name, ext - d->d_name);
138 plug->handle = so;
139 plug->pcm_initialized = 0;
140 plug->mixer_initialized = 0;
141 plug->mixer_open = 0;
143 add_plugin(plug);
144 continue;
145 sym_err:
146 error_msg("%s: symbol %s not found", filename, sym);
147 free(plug);
148 dlclose(so);
150 closedir(dir);
153 static void init_plugin(struct output_plugin *o)
155 if (!o->mixer_initialized && o->mixer_ops) {
156 if (o->mixer_ops->init() == 0) {
157 d_print("initialized mixer for %s\n", o->name);
158 o->mixer_initialized = 1;
159 } else {
160 d_print("could not initialize mixer `%s'\n", o->name);
163 if (!o->pcm_initialized) {
164 if (o->pcm_ops->init() == 0) {
165 d_print("initialized pcm for %s\n", o->name);
166 o->pcm_initialized = 1;
167 } else {
168 d_print("could not initialize pcm `%s'\n", o->name);
173 void op_exit_plugins(void)
175 struct output_plugin *o;
177 list_for_each_entry(o, &op_head, node) {
178 if (o->mixer_initialized && o->mixer_ops)
179 o->mixer_ops->exit();
180 if (o->pcm_initialized)
181 o->pcm_ops->exit();
185 static void close_mixer(void)
187 volume_max = 0;
188 if (op && op->mixer_open) {
189 BUG_ON(op->mixer_ops == NULL);
190 op->mixer_ops->close();
191 op->mixer_open = 0;
195 static void open_mixer(void)
197 if (soft_vol) {
198 volume_max = 100;
199 return;
202 if (op == NULL)
203 return;
205 BUG_ON(op->mixer_open);
206 if (op->mixer_ops && op->mixer_initialized) {
207 int rc;
209 rc = op->mixer_ops->open(&volume_max);
210 if (rc == 0) {
211 op->mixer_open = 1;
212 } else {
213 volume_max = 0;
218 static int select_plugin(struct output_plugin *o)
220 /* try to initialize if not initialized yet */
221 init_plugin(o);
223 if (!o->pcm_initialized)
224 return -OP_ERROR_NOT_INITIALIZED;
226 close_mixer();
227 op = o;
228 open_mixer();
229 return 0;
232 int op_select(const char *name)
234 struct output_plugin *o;
236 list_for_each_entry(o, &op_head, node) {
237 if (strcasecmp(name, o->name) == 0)
238 return select_plugin(o);
240 return -OP_ERROR_NO_PLUGIN;
243 int op_select_any(void)
245 struct output_plugin *o;
246 int rc = -OP_ERROR_NO_PLUGIN;
248 list_for_each_entry(o, &op_head, node) {
249 rc = select_plugin(o);
250 if (rc == 0)
251 break;
253 return rc;
256 int op_open(sample_format_t sf)
258 if (op == NULL)
259 return -OP_ERROR_NOT_INITIALIZED;
260 return op->pcm_ops->open(sf);
263 int op_drop(void)
265 if (op->pcm_ops->drop == NULL)
266 return -OP_ERROR_NOT_SUPPORTED;
267 return op->pcm_ops->drop();
270 int op_close(void)
272 return op->pcm_ops->close();
275 int op_write(const char *buffer, int count)
277 int rc;
279 rc = op->pcm_ops->write(buffer, count);
280 return rc;
283 int op_pause(void)
285 if (op->pcm_ops->pause == NULL)
286 return 0;
287 return op->pcm_ops->pause();
290 int op_unpause(void)
292 if (op->pcm_ops->unpause == NULL)
293 return 0;
294 return op->pcm_ops->unpause();
297 int op_buffer_space(void)
299 int rc;
301 rc = op->pcm_ops->buffer_space();
302 return rc;
305 int op_set_volume(int left, int right)
307 if (soft_vol) {
308 soft_vol_l = left;
309 soft_vol_r = right;
310 return 0;
312 if (op == NULL)
313 return -OP_ERROR_NOT_INITIALIZED;
314 if (!op->mixer_open)
315 return -OP_ERROR_NOT_SUPPORTED;
316 return op->mixer_ops->set_volume(left, right);
319 int op_get_volume(int *left, int *right)
321 if (soft_vol) {
322 *left = soft_vol_l;
323 *right = soft_vol_r;
324 return 0;
326 if (op == NULL)
327 return -OP_ERROR_NOT_INITIALIZED;
328 if (!op->mixer_open)
329 return -OP_ERROR_NOT_SUPPORTED;
330 return op->mixer_ops->get_volume(left, right);
333 void op_set_soft_vol(int soft)
335 close_mixer();
336 soft_vol = soft;
337 open_mixer();
340 #define OP_OPT_ID(plugin_idx, is_mixer, option_idx) \
341 (((plugin_idx) << 16) | ((is_mixer) << 15) | (option_idx))
343 static struct output_plugin *find_plugin(int idx)
345 struct output_plugin *o;
347 list_for_each_entry(o, &op_head, node) {
348 if (idx == 0)
349 return o;
350 idx--;
352 return NULL;
355 int op_set_option(unsigned int id, const char *val)
357 unsigned int pid = id >> 16;
358 unsigned int mix = id & 0x8000;
359 unsigned int oid = id & 0x7fff;
360 const struct output_plugin *o = find_plugin(pid);
362 if (o == NULL)
363 return -OP_ERROR_NOT_OPTION;
365 if (mix) {
366 const struct mixer_plugin_ops *mo = o->mixer_ops;
367 int rc = mo->set_option(oid, val);
369 if (rc == 0 && op && op->mixer_ops == mo) {
370 /* option of the current op was set
371 * try to reopen the mixer */
372 close_mixer();
373 open_mixer();
375 return rc;
377 return o->pcm_ops->set_option(oid, val);
380 int op_get_option(unsigned int id, char **val)
382 unsigned int pid = id >> 16;
383 unsigned int mix = id & 0x8000;
384 unsigned int oid = id & 0x7fff;
385 const struct output_plugin *o = find_plugin(pid);
387 if (o == NULL)
388 return -OP_ERROR_NOT_OPTION;
390 if (mix)
391 return o->mixer_ops->get_option(oid, val);
392 return o->pcm_ops->get_option(oid, val);
395 int op_for_each_option(void (*cb)(unsigned int id, const char *key))
397 struct output_plugin *o;
398 unsigned int pid, oid;
399 char key[64];
401 pid = -1;
402 list_for_each_entry(o, &op_head, node) {
403 pid++;
405 for (oid = 0; o->pcm_options[oid]; oid++) {
406 snprintf(key, sizeof(key), "dsp.%s.%s",
407 o->name,
408 o->pcm_options[oid]);
409 cb(OP_OPT_ID(pid, 0, oid), key);
411 if (o->mixer_ops == NULL)
412 continue;
413 for (oid = 0; o->mixer_options[oid]; oid++) {
414 snprintf(key, sizeof(key), "mixer.%s.%s",
415 o->name,
416 o->mixer_options[oid]);
417 cb(OP_OPT_ID(pid, 1, oid), key);
420 return 0;
423 char *op_get_error_msg(int rc, const char *arg)
425 char buffer[1024];
427 switch (-rc) {
428 case OP_ERROR_ERRNO:
429 snprintf(buffer, sizeof(buffer), "%s: %s", arg, strerror(errno));
430 break;
431 case OP_ERROR_NO_PLUGIN:
432 snprintf(buffer, sizeof(buffer),
433 "%s: no such plugin", arg);
434 break;
435 case OP_ERROR_NOT_INITIALIZED:
436 snprintf(buffer, sizeof(buffer),
437 "%s: couldn't initialize required output plugin", arg);
438 break;
439 case OP_ERROR_NOT_SUPPORTED:
440 snprintf(buffer, sizeof(buffer),
441 "%s: function not supported", arg);
442 break;
443 case OP_ERROR_SAMPLE_FORMAT:
444 snprintf(buffer, sizeof(buffer),
445 "%s: sample format not supported", arg);
446 break;
447 case OP_ERROR_NOT_OPTION:
448 snprintf(buffer, sizeof(buffer),
449 "%s: no such option", arg);
450 break;
451 case OP_ERROR_INTERNAL:
452 snprintf(buffer, sizeof(buffer), "%s: internal error", arg);
453 break;
454 case OP_ERROR_SUCCESS:
455 default:
456 snprintf(buffer, sizeof(buffer),
457 "%s: this is not an error (%d), this is a bug",
458 arg, rc);
459 break;
461 return xstrdup(buffer);
464 void op_dump_plugins(void)
466 struct output_plugin *o;
468 printf("\nOutput Plugins: %s\n", plugin_dir);
469 list_for_each_entry(o, &op_head, node) {
470 printf(" %s\n", o->name);
474 char *op_get_current(void)
476 if (op)
477 return xstrdup(op->name);
478 return NULL;