1 /*
2 * Copyright 2008-2013 Various Authors
3 * Copyright 2004-2006 Timo Hirvonen
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "output.h"
20 #include "op.h"
21 #include "mixer.h"
22 #include "sf.h"
23 #include "utils.h"
24 #include "xmalloc.h"
25 #include "list.h"
26 #include "debug.h"
27 #include "ui_curses.h"
28 #include "options.h"
29 #include "xstrjoin.h"
30 #include "misc.h"
31
32 #include <string.h>
33 #include <strings.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <stdbool.h>
37 #include <sys/types.h>
38 #include <dirent.h>
39 #include <dlfcn.h>
40
41 struct output_plugin {
42 struct list_head node;
43 char *name;
44 void *handle;
45
46 const struct output_plugin_ops *pcm_ops;
47 const struct mixer_plugin_ops *mixer_ops;
48 const struct output_plugin_opt *pcm_options;
49 const struct mixer_plugin_opt *mixer_options;
50 int priority;
51
52 unsigned int pcm_initialized : 1;
53 unsigned int mixer_initialized : 1;
54 unsigned int mixer_open : 1;
55 };
56
57 static const char *plugin_dir;
58 static LIST_HEAD(op_head);
59 static struct output_plugin *op = NULL;
60
61 /* volume is between 0 and volume_max */
62 int volume_max = 0;
63 int volume_l = -1;
64 int volume_r = -1;
65
add_plugin(struct output_plugin * plugin)66 static void add_plugin(struct output_plugin *plugin)
67 {
68 struct list_head *item = op_head.next;
69
70 while (item != &op_head) {
71 struct output_plugin *o = container_of(item, struct output_plugin, node);
72
73 if (plugin->priority < o->priority)
74 break;
75 item = item->next;
76 }
77
78 /* add before item */
79 list_add_tail(&plugin->node, item);
80 }
81
op_load_plugins(void)82 void op_load_plugins(void)
83 {
84 DIR *dir;
85 struct dirent *d;
86
87 plugin_dir = xstrjoin(cmus_lib_dir, "/op");
88 dir = opendir(plugin_dir);
89 if (dir == NULL) {
90 error_msg("couldn't open directory `%s': %s", plugin_dir, strerror(errno));
91 return;
92 }
93 while ((d = (struct dirent *) readdir(dir)) != NULL) {
94 char filename[512];
95 struct output_plugin *plug;
96 void *so, *symptr;
97 char *ext;
98 const unsigned *abi_version_ptr;
99 bool err = false;
100
101 if (d->d_name[0] == '.')
102 continue;
103 ext = strrchr(d->d_name, '.');
104 if (ext == NULL)
105 continue;
106 if (strcmp(ext, ".so"))
107 continue;
108
109 snprintf(filename, sizeof(filename), "%s/%s", plugin_dir, d->d_name);
110
111 so = dlopen(filename, RTLD_NOW);
112 if (so == NULL) {
113 d_print("%s: %s\n", filename, dlerror());
114 continue;
115 }
116
117 plug = xnew(struct output_plugin, 1);
118
119 plug->pcm_ops = dlsym(so, "op_pcm_ops");
120 plug->pcm_options = dlsym(so, "op_pcm_options");
121 symptr = dlsym(so, "op_priority");
122 abi_version_ptr = dlsym(so, "op_abi_version");
123 if (!plug->pcm_ops || !plug->pcm_options || !symptr) {
124 error_msg("%s: missing symbol", filename);
125 err = true;
126 }
127 if (!abi_version_ptr || *abi_version_ptr != OP_ABI_VERSION) {
128 error_msg("%s: incompatible plugin version", filename);
129 err = true;
130 }
131 if (err) {
132 free(plug);
133 dlclose(so);
134 continue;
135 }
136 plug->priority = *(int *)symptr;
137
138 plug->mixer_ops = dlsym(so, "op_mixer_ops");
139 plug->mixer_options = dlsym(so, "op_mixer_options");
140 if (plug->mixer_ops == NULL || plug->mixer_options == NULL) {
141 plug->mixer_ops = NULL;
142 plug->mixer_options = NULL;
143 }
144
145 plug->name = xstrndup(d->d_name, ext - d->d_name);
146 plug->handle = so;
147 plug->pcm_initialized = 0;
148 plug->mixer_initialized = 0;
149 plug->mixer_open = 0;
150
151 add_plugin(plug);
152 }
153 closedir(dir);
154 }
155
init_plugin(struct output_plugin * o)156 static void init_plugin(struct output_plugin *o)
157 {
158 if (!o->mixer_initialized && o->mixer_ops) {
159 if (o->mixer_ops->init() == 0) {
160 d_print("initialized mixer for %s\n", o->name);
161 o->mixer_initialized = 1;
162 } else {
163 d_print("could not initialize mixer `%s'\n", o->name);
164 }
165 }
166 if (!o->pcm_initialized) {
167 if (o->pcm_ops->init() == 0) {
168 d_print("initialized pcm for %s\n", o->name);
169 o->pcm_initialized = 1;
170 } else {
171 d_print("could not initialize pcm `%s'\n", o->name);
172 }
173 }
174 }
175
op_exit_plugins(void)176 void op_exit_plugins(void)
177 {
178 struct output_plugin *o;
179
180 list_for_each_entry(o, &op_head, node) {
181 if (o->mixer_initialized && o->mixer_ops)
182 o->mixer_ops->exit();
183 if (o->pcm_initialized)
184 o->pcm_ops->exit();
185 }
186 }
187
mixer_close(void)188 void mixer_close(void)
189 {
190 volume_max = 0;
191 if (op && op->mixer_open) {
192 BUG_ON(op->mixer_ops == NULL);
193 op->mixer_ops->close();
194 op->mixer_open = 0;
195 }
196 }
197
mixer_open(void)198 void mixer_open(void)
199 {
200 if (op == NULL)
201 return;
202
203 BUG_ON(op->mixer_open);
204 if (op->mixer_ops && op->mixer_initialized) {
205 int rc;
206
207 rc = op->mixer_ops->open(&volume_max);
208 if (rc == 0) {
209 op->mixer_open = 1;
210 mixer_read_volume();
211 } else {
212 volume_max = 0;
213 }
214 }
215 }
216
select_plugin(struct output_plugin * o)217 static int select_plugin(struct output_plugin *o)
218 {
219 /* try to initialize if not initialized yet */
220 init_plugin(o);
221
222 if (!o->pcm_initialized)
223 return -OP_ERROR_NOT_INITIALIZED;
224 op = o;
225 return 0;
226 }
227
op_select(const char * name)228 int op_select(const char *name)
229 {
230 struct output_plugin *o;
231
232 list_for_each_entry(o, &op_head, node) {
233 if (strcasecmp(name, o->name) == 0)
234 return select_plugin(o);
235 }
236 return -OP_ERROR_NO_PLUGIN;
237 }
238
op_select_any(void)239 int op_select_any(void)
240 {
241 struct output_plugin *o;
242 int rc = -OP_ERROR_NO_PLUGIN;
243 sample_format_t sf = sf_channels(2) | sf_rate(44100) | sf_bits(16) | sf_signed(1);
244
245 list_for_each_entry(o, &op_head, node) {
246 rc = select_plugin(o);
247 if (rc != 0)
248 continue;
249 rc = o->pcm_ops->open(sf, NULL);
250 if (rc == 0) {
251 o->pcm_ops->close();
252 break;
253 }
254 }
255 return rc;
256 }
257
op_open(sample_format_t sf,const channel_position_t * channel_map)258 int op_open(sample_format_t sf, const channel_position_t *channel_map)
259 {
260 if (op == NULL)
261 return -OP_ERROR_NOT_INITIALIZED;
262 return op->pcm_ops->open(sf, channel_map);
263 }
264
op_drop(void)265 int op_drop(void)
266 {
267 if (op->pcm_ops->drop == NULL)
268 return -OP_ERROR_NOT_SUPPORTED;
269 return op->pcm_ops->drop();
270 }
271
op_close(void)272 int op_close(void)
273 {
274 return op->pcm_ops->close();
275 }
276
op_write(const char * buffer,int count)277 int op_write(const char *buffer, int count)
278 {
279 return op->pcm_ops->write(buffer, count);
280 }
281
op_pause(void)282 int op_pause(void)
283 {
284 if (op->pcm_ops->pause == NULL)
285 return 0;
286 return op->pcm_ops->pause();
287 }
288
op_unpause(void)289 int op_unpause(void)
290 {
291 if (op->pcm_ops->unpause == NULL)
292 return 0;
293 return op->pcm_ops->unpause();
294 }
295
op_buffer_space(void)296 int op_buffer_space(void)
297 {
298 return op->pcm_ops->buffer_space();
299 }
300
mixer_set_volume(int left,int right)301 int mixer_set_volume(int left, int right)
302 {
303 if (op == NULL)
304 return -OP_ERROR_NOT_INITIALIZED;
305 if (!op->mixer_open)
306 return -OP_ERROR_NOT_OPEN;
307 return op->mixer_ops->set_volume(left, right);
308 }
309
mixer_read_volume(void)310 int mixer_read_volume(void)
311 {
312 if (op == NULL)
313 return -OP_ERROR_NOT_INITIALIZED;
314 if (!op->mixer_open)
315 return -OP_ERROR_NOT_OPEN;
316 return op->mixer_ops->get_volume(&volume_l, &volume_r);
317 }
318
mixer_get_fds(int * fds)319 int mixer_get_fds(int *fds)
320 {
321 if (op == NULL)
322 return -OP_ERROR_NOT_INITIALIZED;
323 if (!op->mixer_open)
324 return -OP_ERROR_NOT_OPEN;
325 if (!op->mixer_ops->get_fds)
326 return -OP_ERROR_NOT_SUPPORTED;
327 return op->mixer_ops->get_fds(fds);
328 }
329
330 extern int soft_vol;
331
option_error(int rc)332 static void option_error(int rc)
333 {
334 char *msg = op_get_error_msg(rc, "setting option");
335 error_msg("%s", msg);
336 free(msg);
337 }
338
set_dsp_option(void * data,const char * val)339 static void set_dsp_option(void *data, const char *val)
340 {
341 const struct output_plugin_opt *o = data;
342 int rc;
343
344 rc = o->set(val);
345 if (rc)
346 option_error(rc);
347 }
348
option_of_current_mixer(const struct mixer_plugin_opt * opt)349 static bool option_of_current_mixer(const struct mixer_plugin_opt *opt)
350 {
351 const struct mixer_plugin_opt *mpo;
352
353 if (!op)
354 return false;
355 for (mpo = op->mixer_options; mpo && mpo->name; mpo++) {
356 if (mpo == opt)
357 return true;
358 }
359 return false;
360 }
361
set_mixer_option(void * data,const char * val)362 static void set_mixer_option(void *data, const char *val)
363 {
364 const struct mixer_plugin_opt *o = data;
365 int rc;
366
367 rc = o->set(val);
368 if (rc) {
369 option_error(rc);
370 } else if (option_of_current_mixer(o)) {
371 /* option of the current op was set
372 * try to reopen the mixer */
373 mixer_close();
374 if (!soft_vol)
375 mixer_open();
376 }
377 }
378
get_dsp_option(void * data,char * buf,size_t size)379 static void get_dsp_option(void *data, char *buf, size_t size)
380 {
381 const struct output_plugin_opt *o = data;
382 char *val = NULL;
383
384 o->get(&val);
385 if (val) {
386 strscpy(buf, val, size);
387 free(val);
388 }
389 }
390
get_mixer_option(void * data,char * buf,size_t size)391 static void get_mixer_option(void *data, char *buf, size_t size)
392 {
393 const struct mixer_plugin_opt *o = data;
394 char *val = NULL;
395
396 o->get(&val);
397 if (val) {
398 strscpy(buf, val, size);
399 free(val);
400 }
401 }
402
op_add_options(void)403 void op_add_options(void)
404 {
405 struct output_plugin *o;
406 const struct output_plugin_opt *opo;
407 const struct mixer_plugin_opt *mpo;
408 char key[64];
409
410 list_for_each_entry(o, &op_head, node) {
411 for (opo = o->pcm_options; opo->name; opo++) {
412 snprintf(key, sizeof(key), "dsp.%s.%s", o->name,
413 opo->name);
414 option_add(xstrdup(key), opo, get_dsp_option,
415 set_dsp_option, NULL, 0);
416 }
417 for (mpo = o->mixer_options; mpo && mpo->name; mpo++) {
418 snprintf(key, sizeof(key), "mixer.%s.%s", o->name,
419 mpo->name);
420 option_add(xstrdup(key), mpo, get_mixer_option,
421 set_mixer_option, NULL, 0);
422 }
423 }
424 }
425
op_get_error_msg(int rc,const char * arg)426 char *op_get_error_msg(int rc, const char *arg)
427 {
428 char buffer[1024];
429
430 switch (-rc) {
431 case OP_ERROR_ERRNO:
432 snprintf(buffer, sizeof(buffer), "%s: %s", arg, strerror(errno));
433 break;
434 case OP_ERROR_NO_PLUGIN:
435 snprintf(buffer, sizeof(buffer),
436 "%s: no such plugin", arg);
437 break;
438 case OP_ERROR_NOT_INITIALIZED:
439 snprintf(buffer, sizeof(buffer),
440 "%s: couldn't initialize required output plugin", arg);
441 break;
442 case OP_ERROR_NOT_SUPPORTED:
443 snprintf(buffer, sizeof(buffer),
444 "%s: function not supported", arg);
445 break;
446 case OP_ERROR_NOT_OPEN:
447 snprintf(buffer, sizeof(buffer),
448 "%s: mixer is not open", arg);
449 break;
450 case OP_ERROR_SAMPLE_FORMAT:
451 snprintf(buffer, sizeof(buffer),
452 "%s: sample format not supported", arg);
453 break;
454 case OP_ERROR_NOT_OPTION:
455 snprintf(buffer, sizeof(buffer),
456 "%s: no such option", arg);
457 break;
458 case OP_ERROR_INTERNAL:
459 snprintf(buffer, sizeof(buffer), "%s: internal error", arg);
460 break;
461 case OP_ERROR_SUCCESS:
462 default:
463 snprintf(buffer, sizeof(buffer),
464 "%s: this is not an error (%d), this is a bug",
465 arg, rc);
466 break;
467 }
468 return xstrdup(buffer);
469 }
470
op_dump_plugins(void)471 void op_dump_plugins(void)
472 {
473 struct output_plugin *o;
474
475 printf("\nOutput Plugins: %s\n", plugin_dir);
476 list_for_each_entry(o, &op_head, node) {
477 printf(" %s\n", o->name);
478 }
479 }
480
op_get_current(void)481 const char *op_get_current(void)
482 {
483 if (op)
484 return op->name;
485 return NULL;
486 }
487