]> code.delx.au - pulseaudio/blob - src/modules/module-solaris.c
Catch volume update events.
[pulseaudio] / src / modules / module-solaris.c
1 /* $Id$ */
2
3 /***
4 This file is part of polypaudio.
5
6 polypaudio is free software; you can redistribute it and/or modify
7 it under the terms of the GNU Lesser General Public License as published
8 by the Free Software Foundation; either version 2 of the License,
9 or (at your option) any later version.
10
11 polypaudio is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public License
17 along with polypaudio; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
19 USA.
20 ***/
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <assert.h>
29 #include <errno.h>
30 #include <string.h>
31 #include <fcntl.h>
32 #include <unistd.h>
33 #include <limits.h>
34 #include <sys/ioctl.h>
35 #include <sys/stat.h>
36 #include <sys/types.h>
37
38 #include <signal.h>
39 #include <stropts.h>
40 #include <sys/conf.h>
41 #include <sys/audio.h>
42
43 #include <polyp/mainloop-signal.h>
44
45 #include <polypcore/iochannel.h>
46 #include <polypcore/sink.h>
47 #include <polypcore/source.h>
48 #include <polypcore/module.h>
49 #include <polypcore/sample-util.h>
50 #include <polypcore/util.h>
51 #include <polypcore/modargs.h>
52 #include <polypcore/xmalloc.h>
53 #include <polypcore/log.h>
54
55 #include "module-solaris-symdef.h"
56
57 PA_MODULE_AUTHOR("Pierre Ossman")
58 PA_MODULE_DESCRIPTION("Solaris Sink/Source")
59 PA_MODULE_VERSION(PACKAGE_VERSION)
60 PA_MODULE_USAGE("sink_name=<name for the sink> source_name=<name for the source> device=<OSS device> record=<enable source?> playback=<enable sink?> format=<sample format> channels=<number of channels> rate=<sample rate> buffer_size=<record buffer size>")
61
62 struct userdata {
63 pa_sink *sink;
64 pa_source *source;
65 pa_iochannel *io;
66 pa_core *core;
67 pa_signal_event *sig;
68
69 pa_memchunk memchunk, silence;
70
71 uint32_t sample_size;
72 uint32_t buffer_size;
73 unsigned int written_bytes, read_bytes;
74
75 int fd;
76 pa_module *module;
77 };
78
79 static const char* const valid_modargs[] = {
80 "sink_name",
81 "source_name",
82 "device",
83 "record",
84 "playback",
85 "buffer_size",
86 "format",
87 "rate",
88 "channels",
89 NULL
90 };
91
92 #define DEFAULT_SINK_NAME "solaris_output"
93 #define DEFAULT_SOURCE_NAME "solaris_input"
94 #define DEFAULT_DEVICE "/dev/audio"
95
96 #define CHUNK_SIZE 2048
97
98 static void update_usage(struct userdata *u) {
99 pa_module_set_used(u->module,
100 (u->sink ? pa_idxset_size(u->sink->inputs) : 0) +
101 (u->sink ? pa_idxset_size(u->sink->monitor_source->outputs) : 0) +
102 (u->source ? pa_idxset_size(u->source->outputs) : 0));
103 }
104
105 static void do_write(struct userdata *u) {
106 audio_info_t info;
107 int err;
108 pa_memchunk *memchunk;
109 size_t len;
110 ssize_t r;
111
112 assert(u);
113
114 if (!u->sink || !pa_iochannel_is_writable(u->io))
115 return;
116
117 update_usage(u);
118
119 err = ioctl(u->fd, AUDIO_GETINFO, &info);
120 assert(err >= 0);
121
122 /*
123 * Since we cannot modify the size of the output buffer we fake it
124 * by not filling it more than u->buffer_size.
125 */
126 len = u->buffer_size;
127 len -= u->written_bytes - (info.play.samples * u->sample_size);
128
129 /*
130 * Do not fill more than half the buffer in one chunk since we only
131 * get notifications upon completion of entire chunks.
132 */
133 if (len > (u->buffer_size / 2))
134 len = u->buffer_size / 2;
135
136 if (len < u->sample_size)
137 return;
138
139 memchunk = &u->memchunk;
140
141 if (!memchunk->length)
142 if (pa_sink_render(u->sink, len, memchunk) < 0)
143 memchunk = &u->silence;
144
145 assert(memchunk->memblock);
146 assert(memchunk->memblock->data);
147 assert(memchunk->length);
148
149 if (memchunk->length < len)
150 len = memchunk->length;
151
152 if ((r = pa_iochannel_write(u->io, (uint8_t*) memchunk->memblock->data + memchunk->index, len)) < 0) {
153 pa_log(__FILE__": write() failed: %s", strerror(errno));
154 return;
155 }
156
157 if (memchunk == &u->silence)
158 assert(r % u->sample_size == 0);
159 else {
160 u->memchunk.index += r;
161 u->memchunk.length -= r;
162
163 if (u->memchunk.length <= 0) {
164 pa_memblock_unref(u->memchunk.memblock);
165 u->memchunk.memblock = NULL;
166 }
167 }
168
169 u->written_bytes += r;
170
171 /*
172 * Write 0 bytes which will generate a SIGPOLL when "played".
173 */
174 if (write(u->fd, NULL, 0) < 0) {
175 pa_log(__FILE__": write() failed: %s", strerror(errno));
176 return;
177 }
178 }
179
180 static void do_read(struct userdata *u) {
181 pa_memchunk memchunk;
182 int err, l;
183 ssize_t r;
184 assert(u);
185
186 if (!u->source || !pa_iochannel_is_readable(u->io))
187 return;
188
189 update_usage(u);
190
191 err = ioctl(u->fd, I_NREAD, &l);
192 assert(err >= 0);
193
194 memchunk.memblock = pa_memblock_new(l, u->core->memblock_stat);
195 assert(memchunk.memblock);
196 if ((r = pa_iochannel_read(u->io, memchunk.memblock->data, memchunk.memblock->length)) < 0) {
197 pa_memblock_unref(memchunk.memblock);
198 if (errno != EAGAIN)
199 pa_log(__FILE__": read() failed: %s", strerror(errno));
200 return;
201 }
202
203 assert(r <= (ssize_t) memchunk.memblock->length);
204 memchunk.length = memchunk.memblock->length = r;
205 memchunk.index = 0;
206
207 pa_source_post(u->source, &memchunk);
208 pa_memblock_unref(memchunk.memblock);
209
210 u->read_bytes += r;
211 }
212
213 static void io_callback(pa_iochannel *io, void*userdata) {
214 struct userdata *u = userdata;
215 assert(u);
216 do_write(u);
217 do_read(u);
218 }
219
220 static void sig_callback(pa_mainloop_api *api, pa_signal_event*e, int sig, void *userdata) {
221 struct userdata *u = userdata;
222 pa_cvolume old_vol;
223
224 assert(u && u->sink && u->sink->get_hw_volume);
225
226 do_write(u);
227
228 memcpy(&old_vol, &u->sink->hw_volume, sizeof(pa_cvolume));
229 if (u->sink->get_hw_volume(u->sink) < 0)
230 return;
231 if (memcmp(&old_vol, &u->sink->hw_volume, sizeof(pa_cvolume)) != 0) {
232 pa_subscription_post(u->sink->core,
233 PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE,
234 u->sink->index);
235 }
236 }
237
238 static pa_usec_t sink_get_latency_cb(pa_sink *s) {
239 pa_usec_t r = 0;
240 audio_info_t info;
241 int err;
242 struct userdata *u = s->userdata;
243 assert(s && u && u->sink);
244
245 err = ioctl(u->fd, AUDIO_GETINFO, &info);
246 assert(err >= 0);
247
248 r += pa_bytes_to_usec(u->written_bytes, &s->sample_spec);
249 r -= pa_bytes_to_usec(info.play.samples * u->sample_size, &s->sample_spec);
250
251 if (u->memchunk.memblock)
252 r += pa_bytes_to_usec(u->memchunk.length, &s->sample_spec);
253
254 return r;
255 }
256
257 static pa_usec_t source_get_latency_cb(pa_source *s) {
258 pa_usec_t r = 0;
259 struct userdata *u = s->userdata;
260 audio_info_t info;
261 int err;
262 assert(s && u && u->source);
263
264 err = ioctl(u->fd, AUDIO_GETINFO, &info);
265 assert(err >= 0);
266
267 r += pa_bytes_to_usec(info.record.samples * u->sample_size, &s->sample_spec);
268 r -= pa_bytes_to_usec(u->read_bytes, &s->sample_spec);
269
270 return r;
271 }
272
273 static int sink_get_hw_volume_cb(pa_sink *s) {
274 struct userdata *u = s->userdata;
275 audio_info_t info;
276 int err;
277
278 err = ioctl(u->fd, AUDIO_GETINFO, &info);
279 assert(err >= 0);
280
281 pa_cvolume_set(&s->hw_volume, s->hw_volume.channels,
282 info.play.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN);
283
284 return 0;
285 }
286
287 static int sink_set_hw_volume_cb(pa_sink *s) {
288 struct userdata *u = s->userdata;
289 audio_info_t info;
290
291 AUDIO_INITINFO(&info);
292
293 info.play.gain = pa_cvolume_avg(&s->hw_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM;
294 assert(info.play.gain <= AUDIO_MAX_GAIN);
295
296 if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) {
297 if (errno == EINVAL)
298 pa_log(__FILE__": AUDIO_SETINFO: Unsupported volume.");
299 else
300 pa_log(__FILE__": AUDIO_SETINFO: %s", strerror(errno));
301 return -1;
302 }
303
304 return 0;
305 }
306
307 static int sink_get_hw_mute_cb(pa_sink *s) {
308 struct userdata *u = s->userdata;
309 audio_info_t info;
310 int err;
311
312 err = ioctl(u->fd, AUDIO_GETINFO, &info);
313 assert(err >= 0);
314
315 s->hw_muted = !!info.output_muted;
316
317 return 0;
318 }
319
320 static int sink_set_hw_mute_cb(pa_sink *s) {
321 struct userdata *u = s->userdata;
322 audio_info_t info;
323
324 AUDIO_INITINFO(&info);
325
326 info.output_muted = !!s->hw_muted;
327
328 if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) {
329 pa_log(__FILE__": AUDIO_SETINFO: %s", strerror(errno));
330 return -1;
331 }
332
333 return 0;
334 }
335
336 static int source_get_hw_volume_cb(pa_source *s) {
337 struct userdata *u = s->userdata;
338 audio_info_t info;
339 int err;
340
341 err = ioctl(u->fd, AUDIO_GETINFO, &info);
342 assert(err >= 0);
343
344 pa_cvolume_set(&s->hw_volume, s->hw_volume.channels,
345 info.record.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN);
346
347 return 0;
348 }
349
350 static int source_set_hw_volume_cb(pa_source *s) {
351 struct userdata *u = s->userdata;
352 audio_info_t info;
353
354 AUDIO_INITINFO(&info);
355
356 info.record.gain = pa_cvolume_avg(&s->hw_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM;
357 assert(info.record.gain <= AUDIO_MAX_GAIN);
358
359 if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) {
360 if (errno == EINVAL)
361 pa_log(__FILE__": AUDIO_SETINFO: Unsupported volume.");
362 else
363 pa_log(__FILE__": AUDIO_SETINFO: %s", strerror(errno));
364 return -1;
365 }
366
367 return 0;
368 }
369
370 static int pa_solaris_auto_format(int fd, int mode, pa_sample_spec *ss) {
371 audio_info_t info;
372
373 AUDIO_INITINFO(&info);
374
375 if (mode != O_RDONLY) {
376 info.play.sample_rate = ss->rate;
377 info.play.channels = ss->channels;
378 switch (ss->format) {
379 case PA_SAMPLE_U8:
380 info.play.precision = 8;
381 info.play.encoding = AUDIO_ENCODING_LINEAR;
382 break;
383 case PA_SAMPLE_ALAW:
384 info.play.precision = 8;
385 info.play.encoding = AUDIO_ENCODING_ALAW;
386 break;
387 case PA_SAMPLE_ULAW:
388 info.play.precision = 8;
389 info.play.encoding = AUDIO_ENCODING_ULAW;
390 break;
391 case PA_SAMPLE_S16NE:
392 info.play.precision = 16;
393 info.play.encoding = AUDIO_ENCODING_LINEAR;
394 break;
395 default:
396 return -1;
397 }
398 }
399
400 if (mode != O_WRONLY) {
401 info.record.sample_rate = ss->rate;
402 info.record.channels = ss->channels;
403 switch (ss->format) {
404 case PA_SAMPLE_U8:
405 info.record.precision = 8;
406 info.record.encoding = AUDIO_ENCODING_LINEAR;
407 break;
408 case PA_SAMPLE_ALAW:
409 info.record.precision = 8;
410 info.record.encoding = AUDIO_ENCODING_ALAW;
411 break;
412 case PA_SAMPLE_ULAW:
413 info.record.precision = 8;
414 info.record.encoding = AUDIO_ENCODING_ULAW;
415 break;
416 case PA_SAMPLE_S16NE:
417 info.record.precision = 16;
418 info.record.encoding = AUDIO_ENCODING_LINEAR;
419 break;
420 default:
421 return -1;
422 }
423 }
424
425 if (ioctl(fd, AUDIO_SETINFO, &info) < 0) {
426 if (errno == EINVAL)
427 pa_log(__FILE__": AUDIO_SETINFO: Unsupported sample format.");
428 else
429 pa_log(__FILE__": AUDIO_SETINFO: %s", strerror(errno));
430 return -1;
431 }
432
433 return 0;
434 }
435
436 static int pa_solaris_set_buffer(int fd, int buffer_size) {
437 audio_info_t info;
438
439 AUDIO_INITINFO(&info);
440
441 info.record.buffer_size = buffer_size;
442
443 if (ioctl(fd, AUDIO_SETINFO, &info) < 0) {
444 if (errno == EINVAL)
445 pa_log(__FILE__": AUDIO_SETINFO: Unsupported buffer size.");
446 else
447 pa_log(__FILE__": AUDIO_SETINFO: %s", strerror(errno));
448 return -1;
449 }
450
451 return 0;
452 }
453
454 int pa__init(pa_core *c, pa_module*m) {
455 struct userdata *u = NULL;
456 const char *p;
457 int fd = -1;
458 int buffer_size;
459 int mode;
460 int record = 1, playback = 1;
461 pa_sample_spec ss;
462 pa_modargs *ma = NULL;
463 assert(c && m);
464
465 if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
466 pa_log(__FILE__": failed to parse module arguments.");
467 goto fail;
468 }
469
470 if (pa_modargs_get_value_boolean(ma, "record", &record) < 0 || pa_modargs_get_value_boolean(ma, "playback", &playback) < 0) {
471 pa_log(__FILE__": record= and playback= expect numeric argument.");
472 goto fail;
473 }
474
475 if (!playback && !record) {
476 pa_log(__FILE__": neither playback nor record enabled for device.");
477 goto fail;
478 }
479
480 mode = (playback&&record) ? O_RDWR : (playback ? O_WRONLY : (record ? O_RDONLY : 0));
481
482 buffer_size = 16384;
483 if (pa_modargs_get_value_s32(ma, "buffer_size", &buffer_size) < 0) {
484 pa_log(__FILE__": failed to parse buffer size argument");
485 goto fail;
486 }
487
488 ss = c->default_sample_spec;
489 if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
490 pa_log(__FILE__": failed to parse sample specification");
491 goto fail;
492 }
493
494 if ((fd = open(p = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), mode | O_NONBLOCK)) < 0)
495 goto fail;
496
497 pa_log_info(__FILE__": device opened in %s mode.", mode == O_WRONLY ? "O_WRONLY" : (mode == O_RDONLY ? "O_RDONLY" : "O_RDWR"));
498
499 if (pa_solaris_auto_format(fd, mode, &ss) < 0)
500 goto fail;
501
502 if ((mode != O_WRONLY) && (buffer_size >= 1))
503 if (pa_solaris_set_buffer(fd, buffer_size) < 0)
504 goto fail;
505
506 u = pa_xmalloc(sizeof(struct userdata));
507 u->core = c;
508
509 if (mode != O_WRONLY) {
510 u->source = pa_source_new(c, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, NULL);
511 assert(u->source);
512 u->source->userdata = u;
513 u->source->get_latency = source_get_latency_cb;
514 u->source->get_hw_volume = source_get_hw_volume_cb;
515 u->source->set_hw_volume = source_set_hw_volume_cb;
516 pa_source_set_owner(u->source, m);
517 u->source->description = pa_sprintf_malloc("Solaris PCM on '%s'", p);
518 } else
519 u->source = NULL;
520
521 if (mode != O_RDONLY) {
522 u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, NULL);
523 assert(u->sink);
524 u->sink->get_latency = sink_get_latency_cb;
525 u->sink->get_hw_volume = sink_get_hw_volume_cb;
526 u->sink->set_hw_volume = sink_set_hw_volume_cb;
527 u->sink->get_hw_mute = sink_get_hw_mute_cb;
528 u->sink->set_hw_mute = sink_set_hw_mute_cb;
529 u->sink->userdata = u;
530 pa_sink_set_owner(u->sink, m);
531 u->sink->description = pa_sprintf_malloc("Solaris PCM on '%s'", p);
532 } else
533 u->sink = NULL;
534
535 assert(u->source || u->sink);
536
537 u->io = pa_iochannel_new(c->mainloop, u->source ? fd : -1, u->sink ? fd : 0);
538 assert(u->io);
539 pa_iochannel_set_callback(u->io, io_callback, u);
540 u->fd = fd;
541
542 u->memchunk.memblock = NULL;
543 u->memchunk.length = 0;
544 u->sample_size = pa_frame_size(&ss);
545 u->buffer_size = buffer_size;
546
547 u->silence.memblock = pa_memblock_new(u->silence.length = CHUNK_SIZE, u->core->memblock_stat);
548 assert(u->silence.memblock);
549 pa_silence_memblock(u->silence.memblock, &ss);
550 u->silence.index = 0;
551
552 u->written_bytes = 0;
553 u->read_bytes = 0;
554
555 u->module = m;
556 m->userdata = u;
557
558 u->sig = pa_signal_new(SIGPOLL, sig_callback, u);
559 assert(u->sig);
560 ioctl(u->fd, I_SETSIG, S_MSG);
561
562 pa_modargs_free(ma);
563
564 /* Read mixer settings */
565 if (u->source)
566 source_get_hw_volume_cb(u->source);
567 if (u->sink) {
568 sink_get_hw_volume_cb(u->sink);
569 sink_get_hw_mute_cb(u->sink);
570 }
571
572 return 0;
573
574 fail:
575 if (fd >= 0)
576 close(fd);
577
578 if (ma)
579 pa_modargs_free(ma);
580
581 return -1;
582 }
583
584 void pa__done(pa_core *c, pa_module*m) {
585 struct userdata *u;
586 assert(c && m);
587
588 if (!(u = m->userdata))
589 return;
590
591 ioctl(u->fd, I_SETSIG, 0);
592 pa_signal_free(u->sig);
593
594 if (u->memchunk.memblock)
595 pa_memblock_unref(u->memchunk.memblock);
596 if (u->silence.memblock)
597 pa_memblock_unref(u->silence.memblock);
598
599 if (u->sink) {
600 pa_sink_disconnect(u->sink);
601 pa_sink_unref(u->sink);
602 }
603
604 if (u->source) {
605 pa_source_disconnect(u->source);
606 pa_source_unref(u->source);
607 }
608
609 pa_iochannel_free(u->io);
610 pa_xfree(u);
611 }