xref: /openbsd-src/lib/libsndio/sio.c (revision 27e638e420fdf0c213127b7029bcc9fd38b54087)
1 /*	$OpenBSD: sio.c,v 1.13 2013/04/03 03:13:32 guenther Exp $	*/
2 /*
3  * Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include <sys/param.h>
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <poll.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <time.h>
29 #include <unistd.h>
30 
31 #include "debug.h"
32 #include "sio_priv.h"
33 
34 #define SIO_PAR_MAGIC	0x83b905a4
35 
36 void
37 sio_initpar(struct sio_par *par)
38 {
39 	memset(par, 0xff, sizeof(struct sio_par));
40 	par->__magic = SIO_PAR_MAGIC;
41 }
42 
43 struct sio_hdl *
44 sio_open(const char *str, unsigned int mode, int nbio)
45 {
46 	static char devany[] = SIO_DEVANY;
47 	struct sio_hdl *hdl;
48 	const char *p;
49 
50 #ifdef DEBUG
51 	sndio_debug_init();
52 #endif
53 	if ((mode & (SIO_PLAY | SIO_REC)) == 0)
54 		return NULL;
55 	if (str == NULL) /* backward compat */
56 		str = devany;
57 	if (strcmp(str, devany) == 0 && !issetugid()) {
58 		str = getenv("AUDIODEVICE");
59 		if (str == NULL)
60 			str = devany;
61 	}
62 	if (strcmp(str, devany) == 0) {
63 		hdl = sio_aucat_open("/0", mode, nbio);
64 		if (hdl != NULL)
65 			return hdl;
66 		return sio_sun_open("/0", mode, nbio);
67 	}
68 	if ((p = sndio_parsetype(str, "snd")) != NULL ||
69 	    (p = sndio_parsetype(str, "aucat")) != NULL)
70 		return sio_aucat_open(p, mode, nbio);
71 	if ((p = sndio_parsetype(str, "rsnd")) != NULL ||
72 	    (p = sndio_parsetype(str, "sun")) != NULL) {
73 		return sio_sun_open(p, mode, nbio);
74 	}
75 	DPRINTF("sio_open: %s: unknown device type\n", str);
76 	return NULL;
77 }
78 
79 void
80 sio_create(struct sio_hdl *hdl, struct sio_ops *ops,
81     unsigned int mode, int nbio)
82 {
83 	hdl->ops = ops;
84 	hdl->mode = mode;
85 	hdl->nbio = nbio;
86 	hdl->started = 0;
87 	hdl->eof = 0;
88 	hdl->move_cb = NULL;
89 	hdl->vol_cb = NULL;
90 }
91 
92 void
93 sio_close(struct sio_hdl *hdl)
94 {
95 	hdl->ops->close(hdl);
96 }
97 
98 int
99 sio_start(struct sio_hdl *hdl)
100 {
101 	if (hdl->eof) {
102 		DPRINTF("sio_start: eof\n");
103 		return 0;
104 	}
105 	if (hdl->started) {
106 		DPRINTF("sio_start: already started\n");
107 		hdl->eof = 1;
108 		return 0;
109 	}
110 #ifdef DEBUG
111 	if (!sio_getpar(hdl, &hdl->par))
112 		return 0;
113 	hdl->pollcnt = hdl->wcnt = hdl->rcnt = hdl->realpos = 0;
114 	clock_gettime(CLOCK_MONOTONIC, &hdl->ts);
115 #endif
116 	if (!hdl->ops->start(hdl))
117 		return 0;
118 	hdl->started = 1;
119 	return 1;
120 }
121 
122 int
123 sio_stop(struct sio_hdl *hdl)
124 {
125 	if (hdl->eof) {
126 		DPRINTF("sio_stop: eof\n");
127 		return 0;
128 	}
129 	if (!hdl->started) {
130 		DPRINTF("sio_stop: not started\n");
131 		hdl->eof = 1;
132 		return 0;
133 	}
134 	if (!hdl->ops->stop(hdl))
135 		return 0;
136 #ifdef DEBUG
137 	DPRINTF("libsndio: polls: %llu, written = %llu, read: %llu\n",
138 	    hdl->pollcnt, hdl->wcnt, hdl->rcnt);
139 #endif
140 	hdl->started = 0;
141 	return 1;
142 }
143 
144 int
145 sio_setpar(struct sio_hdl *hdl, struct sio_par *par)
146 {
147 	if (hdl->eof) {
148 		DPRINTF("sio_setpar: eof\n");
149 		return 0;
150 	}
151 	if (par->__magic != SIO_PAR_MAGIC) {
152 		DPRINTF("sio_setpar: use of uninitialized sio_par structure\n");
153 		hdl->eof = 1;
154 		return 0;
155 	}
156 	if (hdl->started) {
157 		DPRINTF("sio_setpar: already started\n");
158 		hdl->eof = 1;
159 		return 0;
160 	}
161 	if (par->bufsz != ~0U) {
162 		DPRINTF("sio_setpar: setting bufsz is deprecated\n");
163 		par->appbufsz = par->bufsz;
164 		par->bufsz = ~0U;
165 	}
166 	if (par->rate != ~0U && par->appbufsz == ~0U)
167 		par->appbufsz = par->rate * 200 / 1000;
168 	return hdl->ops->setpar(hdl, par);
169 }
170 
171 int
172 sio_getpar(struct sio_hdl *hdl, struct sio_par *par)
173 {
174 	if (hdl->eof) {
175 		DPRINTF("sio_getpar: eof\n");
176 		return 0;
177 	}
178 	if (hdl->started) {
179 		DPRINTF("sio_getpar: already started\n");
180 		hdl->eof = 1;
181 		return 0;
182 	}
183 	if (!hdl->ops->getpar(hdl, par)) {
184 		par->__magic = 0;
185 		return 0;
186 	}
187 	par->__magic = 0;
188 	return 1;
189 }
190 
191 int
192 sio_getcap(struct sio_hdl *hdl, struct sio_cap *cap)
193 {
194 	if (hdl->eof) {
195 		DPRINTF("sio_getcap: eof\n");
196 		return 0;
197 	}
198 	if (hdl->started) {
199 		DPRINTF("sio_getcap: already started\n");
200 		hdl->eof = 1;
201 		return 0;
202 	}
203 	return hdl->ops->getcap(hdl, cap);
204 }
205 
206 static int
207 sio_psleep(struct sio_hdl *hdl, int event)
208 {
209 	struct pollfd pfd[SIO_MAXNFDS];
210 	int revents;
211 	int nfds;
212 
213 	nfds = sio_nfds(hdl);
214 	if (nfds > SIO_MAXNFDS) {
215 		DPRINTF("sio_psleep: %d: too many descriptors\n", nfds);
216 		hdl->eof = 1;
217 		return 0;
218 	}
219 	for (;;) {
220 		nfds = sio_pollfd(hdl, pfd, event);
221 		while (poll(pfd, nfds, -1) < 0) {
222 			if (errno == EINTR)
223 				continue;
224 			DPERROR("sio_psleep: poll");
225 			hdl->eof = 1;
226 			return 0;
227 		}
228 		revents = sio_revents(hdl, pfd);
229 		if (revents & POLLHUP) {
230 			DPRINTF("sio_psleep: hang-up\n");
231 			return 0;
232 		}
233 		if (revents & event)
234 			break;
235 	}
236 	return 1;
237 }
238 
239 size_t
240 sio_read(struct sio_hdl *hdl, void *buf, size_t len)
241 {
242 	unsigned int n;
243 	char *data = buf;
244 	size_t todo = len;
245 
246 	if (hdl->eof) {
247 		DPRINTF("sio_read: eof\n");
248 		return 0;
249 	}
250 	if (!hdl->started || !(hdl->mode & SIO_REC)) {
251 		DPRINTF("sio_read: recording not started\n");
252 		hdl->eof = 1;
253 		return 0;
254 	}
255 	if (todo == 0) {
256 		DPRINTF("sio_read: zero length read ignored\n");
257 		return 0;
258 	}
259 	while (todo > 0) {
260 		n = hdl->ops->read(hdl, data, todo);
261 		if (n == 0) {
262 			if (hdl->nbio || hdl->eof || todo < len)
263 				break;
264 			if (!sio_psleep(hdl, POLLIN))
265 				break;
266 			continue;
267 		}
268 		data += n;
269 		todo -= n;
270 #ifdef DEBUG
271 		hdl->rcnt += n;
272 #endif
273 	}
274 	return len - todo;
275 }
276 
277 size_t
278 sio_write(struct sio_hdl *hdl, const void *buf, size_t len)
279 {
280 	unsigned int n;
281 	const unsigned char *data = buf;
282 	size_t todo = len;
283 #ifdef DEBUG
284 	struct timespec ts0, ts1, dts;
285 	unsigned int us;
286 
287 	if (sndio_debug >= 2)
288 		clock_gettime(CLOCK_MONOTONIC, &ts0);
289 #endif
290 
291 	if (hdl->eof) {
292 		DPRINTF("sio_write: eof\n");
293 		return 0;
294 	}
295 	if (!hdl->started || !(hdl->mode & SIO_PLAY)) {
296 		DPRINTF("sio_write: playback not started\n");
297 		hdl->eof = 1;
298 		return 0;
299 	}
300 	if (todo == 0) {
301 		DPRINTF("sio_write: zero length write ignored\n");
302 		return 0;
303 	}
304 	while (todo > 0) {
305 		n = hdl->ops->write(hdl, data, todo);
306 		if (n == 0) {
307 			if (hdl->nbio || hdl->eof)
308 				break;
309 			if (!sio_psleep(hdl, POLLOUT))
310 				break;
311 			continue;
312 		}
313 		data += n;
314 		todo -= n;
315 #ifdef DEBUG
316 		hdl->wcnt += n;
317 #endif
318 	}
319 #ifdef DEBUG
320 	if (sndio_debug >= 2) {
321 		clock_gettime(CLOCK_MONOTONIC, &ts1);
322 		timespecsub(&ts0, &hdl->ts, &dts);
323 		DPRINTF("%lld.%09ld: ", (long long)dts.tv_sec, dts.tv_nsec);
324 
325 		timespecsub(&ts1, &ts0, &dts);
326 		us = dts.tv_sec * 1000000 + dts.tv_nsec/1000;
327 		DPRINTF(
328 		    "sio_write: wrote %d bytes of %d in %uus\n",
329 		    (int)(len - todo), (int)len, us);
330 	}
331 #endif
332 	return len - todo;
333 }
334 
335 int
336 sio_nfds(struct sio_hdl *hdl)
337 {
338 	return hdl->ops->nfds(hdl);
339 }
340 
341 int
342 sio_pollfd(struct sio_hdl *hdl, struct pollfd *pfd, int events)
343 {
344 	if (hdl->eof)
345 		return 0;
346 	if (!hdl->started)
347 		events = 0;
348 	return hdl->ops->pollfd(hdl, pfd, events);
349 }
350 
351 int
352 sio_revents(struct sio_hdl *hdl, struct pollfd *pfd)
353 {
354 	int revents;
355 #ifdef DEBUG
356 	struct timespec ts0, ts1, dts;
357 	unsigned int us;
358 
359 	if (sndio_debug >= 2)
360 		clock_gettime(CLOCK_MONOTONIC, &ts0);
361 #endif
362 	if (hdl->eof)
363 		return POLLHUP;
364 #ifdef DEBUG
365 	hdl->pollcnt++;
366 #endif
367 	revents = hdl->ops->revents(hdl, pfd);
368 	if (!hdl->started)
369 		return revents & POLLHUP;
370 #ifdef DEBUG
371 	if (sndio_debug >= 2) {
372 		clock_gettime(CLOCK_MONOTONIC, &ts1);
373 		timespecsub(&ts0, &hdl->ts, &dts);
374 		DPRINTF("%lld.%09ld: ", (long long)dts.tv_sec, dts.tv_nsec);
375 
376 		timespecsub(&ts1, &ts0, &dts);
377 		us = dts.tv_sec * 1000000 + dts.tv_nsec/1000;
378 		DPRINTF("sio_revents: revents = 0x%x, complete in %uus\n",
379 		    revents, us);
380 	}
381 #endif
382 	return revents;
383 }
384 
385 int
386 sio_eof(struct sio_hdl *hdl)
387 {
388 	return hdl->eof;
389 }
390 
391 void
392 sio_onmove(struct sio_hdl *hdl, void (*cb)(void *, int), void *addr)
393 {
394 	if (hdl->started) {
395 		DPRINTF("sio_onmove: already started\n");
396 		hdl->eof = 1;
397 		return;
398 	}
399 	hdl->move_cb = cb;
400 	hdl->move_addr = addr;
401 }
402 
403 void
404 sio_onmove_cb(struct sio_hdl *hdl, int delta)
405 {
406 #ifdef DEBUG
407 	struct timespec ts0, dts;
408 	long long playpos;
409 
410 	if (sndio_debug >= 3 && (hdl->mode & SIO_PLAY)) {
411 		clock_gettime(CLOCK_MONOTONIC, &ts0);
412 		timespecsub(&ts0, &hdl->ts, &dts);
413 		DPRINTF("%lld.%09ld: ", (long long)dts.tv_sec, dts.tv_nsec);
414 		hdl->realpos += delta;
415 		playpos = hdl->wcnt / (hdl->par.bps * hdl->par.pchan);
416 		DPRINTF("sio_onmove_cb: delta = %+7d, "
417 		    "plat = %+7lld, "
418 		    "realpos = %+7lld, "
419 		    "bufused = %+7lld\n",
420 		    delta,
421 		    playpos - hdl->realpos,
422 		    hdl->realpos,
423 		    hdl->realpos < 0 ? playpos : playpos - hdl->realpos);
424 	}
425 #endif
426 	if (hdl->move_cb)
427 		hdl->move_cb(hdl->move_addr, delta);
428 }
429 
430 int
431 sio_setvol(struct sio_hdl *hdl, unsigned int ctl)
432 {
433 	if (hdl->eof)
434 		return 0;
435 	if (!hdl->ops->setvol)
436 		return 1;
437 	if (!hdl->ops->setvol(hdl, ctl))
438 		return 0;
439 	hdl->ops->getvol(hdl);
440 	return 1;
441 }
442 
443 int
444 sio_onvol(struct sio_hdl *hdl, void (*cb)(void *, unsigned int), void *addr)
445 {
446 	if (hdl->started) {
447 		DPRINTF("sio_onvol: already started\n");
448 		hdl->eof = 1;
449 		return 0;
450 	}
451 	if (!hdl->ops->setvol)
452 		return 0;
453 	hdl->vol_cb = cb;
454 	hdl->vol_addr = addr;
455 	hdl->ops->getvol(hdl);
456 	return 1;
457 }
458 
459 void
460 sio_onvol_cb(struct sio_hdl *hdl, unsigned int ctl)
461 {
462 	if (hdl->vol_cb)
463 		hdl->vol_cb(hdl->vol_addr, ctl);
464 }
465