xref: /openbsd/lib/libsndio/sio.c (revision 9b7c3dbb)
1 /*	$OpenBSD: sio.c,v 1.21 2016/01/09 08:27:24 ratchov 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/types.h>
19 #include <sys/stat.h>
20 
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <poll.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <time.h>
28 #include <unistd.h>
29 
30 #include "debug.h"
31 #include "sio_priv.h"
32 
33 #define SIO_PAR_MAGIC	0x83b905a4
34 
35 void
36 sio_initpar(struct sio_par *par)
37 {
38 	memset(par, 0xff, sizeof(struct sio_par));
39 	par->__magic = SIO_PAR_MAGIC;
40 }
41 
42 struct sio_hdl *
43 sio_open(const char *str, unsigned int mode, int nbio)
44 {
45 	static char devany[] = SIO_DEVANY;
46 	struct sio_hdl *hdl;
47 
48 #ifdef DEBUG
49 	_sndio_debug_init();
50 #endif
51 	if ((mode & (SIO_PLAY | SIO_REC)) == 0)
52 		return NULL;
53 	if (str == NULL) /* backward compat */
54 		str = devany;
55 	if (strcmp(str, devany) == 0 && !issetugid()) {
56 		str = getenv("AUDIODEVICE");
57 		if (str == NULL)
58 			str = devany;
59 	}
60 	if (strcmp(str, devany) == 0) {
61 		hdl = _sio_aucat_open("snd/0", mode, nbio);
62 		if (hdl != NULL)
63 			return hdl;
64 		return _sio_sun_open("rsnd/0", mode, nbio);
65 	}
66 	if (_sndio_parsetype(str, "snd"))
67 		return _sio_aucat_open(str, mode, nbio);
68 	if (_sndio_parsetype(str, "rsnd"))
69 		return _sio_sun_open(str, mode, nbio);
70 	DPRINTF("sio_open: %s: unknown device type\n", str);
71 	return NULL;
72 }
73 
74 void
75 _sio_create(struct sio_hdl *hdl, struct sio_ops *ops,
76     unsigned int mode, int nbio)
77 {
78 	hdl->ops = ops;
79 	hdl->mode = mode;
80 	hdl->nbio = nbio;
81 	hdl->started = 0;
82 	hdl->eof = 0;
83 	hdl->move_cb = NULL;
84 	hdl->vol_cb = NULL;
85 }
86 
87 void
88 sio_close(struct sio_hdl *hdl)
89 {
90 	hdl->ops->close(hdl);
91 }
92 
93 int
94 sio_start(struct sio_hdl *hdl)
95 {
96 #ifdef DEBUG
97 	struct timespec ts;
98 #endif
99 
100 	if (hdl->eof) {
101 		DPRINTF("sio_start: eof\n");
102 		return 0;
103 	}
104 	if (hdl->started) {
105 		DPRINTF("sio_start: already started\n");
106 		hdl->eof = 1;
107 		return 0;
108 	}
109 	hdl->cpos = 0;
110 	hdl->rused = hdl->wused = 0;
111 	if (!sio_getpar(hdl, &hdl->par))
112 		return 0;
113 #ifdef DEBUG
114 	hdl->pollcnt = 0;
115 	clock_gettime(CLOCK_MONOTONIC, &ts);
116 	hdl->start_nsec = 1000000000LL * ts.tv_sec + ts.tv_nsec;
117 #endif
118 	hdl->rdrop = hdl->wsil = 0;
119 	if (!hdl->ops->start(hdl))
120 		return 0;
121 	hdl->started = 1;
122 	return 1;
123 }
124 
125 int
126 sio_stop(struct sio_hdl *hdl)
127 {
128 	if (hdl->eof) {
129 		DPRINTF("sio_stop: eof\n");
130 		return 0;
131 	}
132 	if (!hdl->started) {
133 		DPRINTF("sio_stop: not started\n");
134 		hdl->eof = 1;
135 		return 0;
136 	}
137 	if (!hdl->ops->stop(hdl))
138 		return 0;
139 #ifdef DEBUG
140 	DPRINTFN(2, "libsndio: polls: %llu, samples = %llu\n",
141 	    hdl->pollcnt, hdl->cpos);
142 #endif
143 	hdl->started = 0;
144 	return 1;
145 }
146 
147 int
148 sio_setpar(struct sio_hdl *hdl, struct sio_par *par)
149 {
150 	if (hdl->eof) {
151 		DPRINTF("sio_setpar: eof\n");
152 		return 0;
153 	}
154 	if (par->__magic != SIO_PAR_MAGIC) {
155 		DPRINTF("sio_setpar: uninitialized sio_par structure\n");
156 		hdl->eof = 1;
157 		return 0;
158 	}
159 	if (hdl->started) {
160 		DPRINTF("sio_setpar: already started\n");
161 		hdl->eof = 1;
162 		return 0;
163 	}
164 	if (par->bufsz != ~0U) {
165 		DPRINTF("sio_setpar: setting bufsz is deprecated\n");
166 		par->appbufsz = par->bufsz;
167 		par->bufsz = ~0U;
168 	}
169 	if (par->rate != ~0U && par->appbufsz == ~0U)
170 		par->appbufsz = par->rate * 200 / 1000;
171 	return hdl->ops->setpar(hdl, par);
172 }
173 
174 int
175 sio_getpar(struct sio_hdl *hdl, struct sio_par *par)
176 {
177 	if (hdl->eof) {
178 		DPRINTF("sio_getpar: eof\n");
179 		return 0;
180 	}
181 	if (hdl->started) {
182 		DPRINTF("sio_getpar: already started\n");
183 		hdl->eof = 1;
184 		return 0;
185 	}
186 	if (!hdl->ops->getpar(hdl, par)) {
187 		par->__magic = 0;
188 		return 0;
189 	}
190 	par->__magic = 0;
191 	return 1;
192 }
193 
194 int
195 sio_getcap(struct sio_hdl *hdl, struct sio_cap *cap)
196 {
197 	if (hdl->eof) {
198 		DPRINTF("sio_getcap: eof\n");
199 		return 0;
200 	}
201 	if (hdl->started) {
202 		DPRINTF("sio_getcap: already started\n");
203 		hdl->eof = 1;
204 		return 0;
205 	}
206 	return hdl->ops->getcap(hdl, cap);
207 }
208 
209 static int
210 sio_psleep(struct sio_hdl *hdl, int event)
211 {
212 	struct pollfd pfd[SIO_MAXNFDS];
213 	int revents;
214 	int nfds;
215 
216 	nfds = sio_nfds(hdl);
217 	if (nfds > SIO_MAXNFDS) {
218 		DPRINTF("sio_psleep: %d: too many descriptors\n", nfds);
219 		hdl->eof = 1;
220 		return 0;
221 	}
222 	for (;;) {
223 		nfds = sio_pollfd(hdl, pfd, event);
224 		while (poll(pfd, nfds, -1) < 0) {
225 			if (errno == EINTR)
226 				continue;
227 			DPERROR("sio_psleep: poll");
228 			hdl->eof = 1;
229 			return 0;
230 		}
231 		revents = sio_revents(hdl, pfd);
232 		if (revents & POLLHUP) {
233 			DPRINTF("sio_psleep: hang-up\n");
234 			return 0;
235 		}
236 		if (revents & event)
237 			break;
238 	}
239 	return 1;
240 }
241 
242 static int
243 sio_rdrop(struct sio_hdl *hdl)
244 {
245 #define DROP_NMAX 0x1000
246 	static char dummy[DROP_NMAX];
247 	ssize_t n, todo;
248 
249 	while (hdl->rdrop > 0) {
250 		todo = hdl->rdrop;
251 		if (todo > DROP_NMAX)
252 			todo = DROP_NMAX;
253 		n = hdl->ops->read(hdl, dummy, todo);
254 		if (n == 0)
255 			return 0;
256 		hdl->rdrop -= n;
257 		DPRINTF("sio_rdrop: dropped %zu bytes\n", n);
258 	}
259 	return 1;
260 }
261 
262 static int
263 sio_wsil(struct sio_hdl *hdl)
264 {
265 #define ZERO_NMAX 0x1000
266 	static char zero[ZERO_NMAX];
267 	ssize_t n, todo;
268 
269 	while (hdl->wsil > 0) {
270 		todo = hdl->wsil;
271 		if (todo > ZERO_NMAX)
272 			todo = ZERO_NMAX;
273 		n = hdl->ops->write(hdl, zero, todo);
274 		if (n == 0)
275 			return 0;
276 		hdl->wsil -= n;
277 		DPRINTF("sio_wsil: inserted %zu bytes\n", n);
278 	}
279 	return 1;
280 }
281 
282 size_t
283 sio_read(struct sio_hdl *hdl, void *buf, size_t len)
284 {
285 	unsigned int n;
286 	char *data = buf;
287 	size_t todo = len, maxread;
288 
289 	if (hdl->eof) {
290 		DPRINTF("sio_read: eof\n");
291 		return 0;
292 	}
293 	if (!hdl->started || !(hdl->mode & SIO_REC)) {
294 		DPRINTF("sio_read: recording not started\n");
295 		hdl->eof = 1;
296 		return 0;
297 	}
298 	if (todo == 0) {
299 		DPRINTF("sio_read: zero length read ignored\n");
300 		return 0;
301 	}
302 	while (todo > 0) {
303 		if (!sio_rdrop(hdl))
304 			return 0;
305 		maxread = hdl->rused;
306 		if (maxread > todo)
307 			maxread = todo;
308 		n = maxread > 0 ? hdl->ops->read(hdl, data, maxread) : 0;
309 		if (n == 0) {
310 			if (hdl->nbio || hdl->eof || todo < len)
311 				break;
312 			if (!sio_psleep(hdl, POLLIN))
313 				break;
314 			continue;
315 		}
316 		data += n;
317 		todo -= n;
318 		hdl->rused -= n;
319 	}
320 	return len - todo;
321 }
322 
323 size_t
324 sio_write(struct sio_hdl *hdl, const void *buf, size_t len)
325 {
326 	unsigned int n;
327 	const unsigned char *data = buf;
328 	size_t todo = len, maxwrite;
329 
330 	if (hdl->eof) {
331 		DPRINTF("sio_write: eof\n");
332 		return 0;
333 	}
334 	if (!hdl->started || !(hdl->mode & SIO_PLAY)) {
335 		DPRINTF("sio_write: playback not started\n");
336 		hdl->eof = 1;
337 		return 0;
338 	}
339 	if (todo == 0) {
340 		DPRINTF("sio_write: zero length write ignored\n");
341 		return 0;
342 	}
343 	while (todo > 0) {
344 		if (!sio_wsil(hdl))
345 			return 0;
346 		maxwrite = hdl->par.bufsz * hdl->par.pchan * hdl->par.bps -
347 		    hdl->wused;
348 		if (maxwrite > todo)
349 			maxwrite = todo;
350 		n = maxwrite > 0 ? hdl->ops->write(hdl, data, maxwrite) : 0;
351 		if (n == 0) {
352 			if (hdl->nbio || hdl->eof)
353 				break;
354 			if (!sio_psleep(hdl, POLLOUT))
355 				break;
356 			continue;
357 		}
358 		data += n;
359 		todo -= n;
360 		hdl->wused += n;
361 	}
362 	return len - todo;
363 }
364 
365 int
366 sio_nfds(struct sio_hdl *hdl)
367 {
368 	return hdl->ops->nfds(hdl);
369 }
370 
371 int
372 sio_pollfd(struct sio_hdl *hdl, struct pollfd *pfd, int events)
373 {
374 	if (hdl->eof)
375 		return 0;
376 	if (!hdl->started)
377 		events = 0;
378 	return hdl->ops->pollfd(hdl, pfd, events);
379 }
380 
381 int
382 sio_revents(struct sio_hdl *hdl, struct pollfd *pfd)
383 {
384 	int revents;
385 #ifdef DEBUG
386 	struct timespec ts0, ts1;
387 
388 	if (_sndio_debug >= 4)
389 		clock_gettime(CLOCK_MONOTONIC, &ts0);
390 #endif
391 	if (hdl->eof)
392 		return POLLHUP;
393 #ifdef DEBUG
394 	hdl->pollcnt++;
395 #endif
396 	revents = hdl->ops->revents(hdl, pfd);
397 	if (!hdl->started)
398 		return revents & POLLHUP;
399 #ifdef DEBUG
400 	if (_sndio_debug >= 4) {
401 		clock_gettime(CLOCK_MONOTONIC, &ts1);
402 		DPRINTF("%09lld: sio_revents: revents = 0x%x, took %lldns\n",
403 		    1000000000LL * ts0.tv_sec +
404 		    ts0.tv_nsec - hdl->start_nsec,
405 		    revents,
406 		    1000000000LL * (ts1.tv_sec - ts0.tv_sec) +
407 		    ts1.tv_nsec - ts0.tv_nsec);
408 	}
409 #endif
410 	if ((hdl->mode & SIO_PLAY) && !sio_wsil(hdl))
411 		revents &= ~POLLOUT;
412 	if ((hdl->mode & SIO_REC) && !sio_rdrop(hdl))
413 		revents &= ~POLLIN;
414 	return revents;
415 }
416 
417 int
418 sio_eof(struct sio_hdl *hdl)
419 {
420 	return hdl->eof;
421 }
422 
423 void
424 sio_onmove(struct sio_hdl *hdl, void (*cb)(void *, int), void *addr)
425 {
426 	if (hdl->started) {
427 		DPRINTF("sio_onmove: already started\n");
428 		hdl->eof = 1;
429 		return;
430 	}
431 	hdl->move_cb = cb;
432 	hdl->move_addr = addr;
433 }
434 
435 #ifdef DEBUG
436 void
437 _sio_printpos(struct sio_hdl *hdl)
438 {
439 	struct timespec ts;
440 	long long rpos, rdiff;
441 	long long cpos, cdiff;
442 	long long wpos, wdiff;
443 	unsigned rbpf, wbpf, rround, wround;
444 
445 	clock_gettime(CLOCK_MONOTONIC, &ts);
446 	rbpf = (hdl->mode & SIO_REC) ? hdl->par.bps * hdl->par.rchan : 1;
447 	wbpf = (hdl->mode & SIO_PLAY) ? hdl->par.bps * hdl->par.pchan : 1;
448 	rround = hdl->par.round * rbpf;
449 	wround = hdl->par.round * wbpf;
450 
451 	rpos = (hdl->mode & SIO_REC) ?
452 	    hdl->cpos * rbpf - hdl->rused : 0;
453 	wpos = (hdl->mode & SIO_PLAY) ?
454 	    hdl->cpos * wbpf + hdl->wused : 0;
455 
456 	cdiff = hdl->cpos % hdl->par.round;
457 	cpos  = hdl->cpos / hdl->par.round;
458 	if (cdiff > hdl->par.round / 2) {
459 		cpos++;
460 		cdiff = cdiff - hdl->par.round;
461 	}
462 	rdiff = rpos % rround;
463 	rpos  = rpos / rround;
464 	if (rdiff > rround / 2) {
465 		rpos++;
466 		rdiff = rdiff - rround;
467 	}
468 	wdiff = wpos % wround;
469 	wpos  = wpos / wround;
470 	if (wdiff > wround / 2) {
471 		wpos++;
472 		wdiff = wdiff - wround;
473 	}
474 	DPRINTF("%011lld: "
475 	    "clk %+5lld%+5lld, wr %+5lld%+5lld rd: %+5lld%+5lld\n",
476 	    1000000000LL * ts.tv_sec + ts.tv_nsec - hdl->start_nsec,
477 	    cpos, cdiff, wpos, wdiff, rpos, rdiff);
478 }
479 #endif
480 
481 void
482 _sio_onmove_cb(struct sio_hdl *hdl, int delta)
483 {
484 	hdl->cpos += delta;
485 	if (hdl->mode & SIO_REC)
486 		hdl->rused += delta * (hdl->par.bps * hdl->par.rchan);
487 	if (hdl->mode & SIO_PLAY)
488 		hdl->wused -= delta * (hdl->par.bps * hdl->par.pchan);
489 #ifdef DEBUG
490 	if (_sndio_debug >= 3)
491 		_sio_printpos(hdl);
492 	if ((hdl->mode & SIO_PLAY) && hdl->wused < 0) {
493 		DPRINTFN(1, "sndio: h/w failure: negative buffer usage\n");
494 		hdl->eof = 1;
495 		return;
496 	}
497 #endif
498 	if (hdl->move_cb)
499 		hdl->move_cb(hdl->move_addr, delta);
500 }
501 
502 int
503 sio_setvol(struct sio_hdl *hdl, unsigned int ctl)
504 {
505 	if (hdl->eof)
506 		return 0;
507 	if (!hdl->ops->setvol)
508 		return 1;
509 	if (!hdl->ops->setvol(hdl, ctl))
510 		return 0;
511 	hdl->ops->getvol(hdl);
512 	return 1;
513 }
514 
515 int
516 sio_onvol(struct sio_hdl *hdl, void (*cb)(void *, unsigned int), void *addr)
517 {
518 	if (hdl->started) {
519 		DPRINTF("sio_onvol: already started\n");
520 		hdl->eof = 1;
521 		return 0;
522 	}
523 	if (!hdl->ops->setvol)
524 		return 0;
525 	hdl->vol_cb = cb;
526 	hdl->vol_addr = addr;
527 	hdl->ops->getvol(hdl);
528 	return 1;
529 }
530 
531 void
532 _sio_onvol_cb(struct sio_hdl *hdl, unsigned int ctl)
533 {
534 	if (hdl->vol_cb)
535 		hdl->vol_cb(hdl->vol_addr, ctl);
536 }
537