xref: /openbsd-src/lib/libsndio/sio.c (revision 8a67bd4248b0acec91967249cf2b74aba1712039)
1 /*	$OpenBSD: sio.c,v 1.3 2011/05/03 20:15:23 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/param.h>
19 #include <sys/types.h>
20 #include <sys/time.h>
21 #include <sys/stat.h>
22 
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <poll.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.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 mode, int nbio)
45 {
46 	static char prefix_aucat[] = "aucat";
47 	static char prefix_sun[] = "sun";
48 	struct sio_hdl *hdl;
49 	struct stat sb;
50 	char *sep, buf[NAME_MAX];
51 	int len;
52 
53 #ifdef DEBUG
54 	sndio_debug_init();
55 #endif
56 	if ((mode & (SIO_PLAY | SIO_REC)) == 0)
57 		return NULL;
58 	if (str == NULL && !issetugid())
59 		str = getenv("AUDIODEVICE");
60 	if (str == NULL) {
61 		hdl = sio_aucat_open(NULL, mode, nbio);
62 		if (hdl != NULL)
63 			return hdl;
64 		hdl = sio_sun_open(NULL, mode, nbio);
65 		if (hdl != NULL)
66 			return hdl;
67 		return NULL;
68 	}
69 	sep = strchr(str, ':');
70 	if (sep == NULL) {
71 		/*
72 		 * try legacy "/dev/audioxxx" or ``socket'' device name
73 		 */
74 		if (stat(str, &sb) < 0 || !S_ISCHR(sb.st_mode)) {
75 			snprintf(buf, sizeof(buf), "0.%s", str);
76 			return sio_aucat_open(buf, mode, nbio);
77 		}
78 		snprintf(buf, sizeof(buf), "%u", minor(sb.st_rdev) & 0xf);
79 		return sio_sun_open(buf, mode, nbio);
80 	}
81 	len = sep - str;
82 	if (len == (sizeof(prefix_aucat) - 1) &&
83 	    memcmp(str, prefix_aucat, len) == 0)
84 		return sio_aucat_open(sep + 1, mode, nbio);
85 	if (len == (sizeof(prefix_sun) - 1) &&
86 	    memcmp(str, prefix_sun, len) == 0)
87 		return sio_sun_open(sep + 1, mode, nbio);
88 	DPRINTF("sio_open: %s: unknown device type\n", str);
89 	return NULL;
90 }
91 
92 void
93 sio_create(struct sio_hdl *hdl, struct sio_ops *ops, unsigned mode, int nbio)
94 {
95 	hdl->ops = ops;
96 	hdl->mode = mode;
97 	hdl->nbio = nbio;
98 	hdl->started = 0;
99 	hdl->eof = 0;
100 	hdl->move_cb = NULL;
101 	hdl->vol_cb = NULL;
102 }
103 
104 void
105 sio_close(struct sio_hdl *hdl)
106 {
107 	hdl->ops->close(hdl);
108 }
109 
110 int
111 sio_start(struct sio_hdl *hdl)
112 {
113 	if (hdl->eof) {
114 		DPRINTF("sio_start: eof\n");
115 		return 0;
116 	}
117 	if (hdl->started) {
118 		DPRINTF("sio_start: already started\n");
119 		hdl->eof = 1;
120 		return 0;
121 	}
122 #ifdef DEBUG
123 	if (!sio_getpar(hdl, &hdl->par))
124 		return 0;
125 	hdl->pollcnt = hdl->wcnt = hdl->rcnt = hdl->realpos = 0;
126 	gettimeofday(&hdl->tv, NULL);
127 #endif
128 	if (!hdl->ops->start(hdl))
129 		return 0;
130 	hdl->started = 1;
131 	return 1;
132 }
133 
134 int
135 sio_stop(struct sio_hdl *hdl)
136 {
137 	if (hdl->eof) {
138 		DPRINTF("sio_stop: eof\n");
139 		return 0;
140 	}
141 	if (!hdl->started) {
142 		DPRINTF("sio_stop: not started\n");
143 		hdl->eof = 1;
144 		return 0;
145 	}
146 	if (!hdl->ops->stop(hdl))
147 		return 0;
148 #ifdef DEBUG
149 	DPRINTF("libsndio: polls: %llu, written = %llu, read: %llu\n",
150 	    hdl->pollcnt, hdl->wcnt, hdl->rcnt);
151 #endif
152 	hdl->started = 0;
153 	return 1;
154 }
155 
156 int
157 sio_setpar(struct sio_hdl *hdl, struct sio_par *par)
158 {
159 	if (hdl->eof) {
160 		DPRINTF("sio_setpar: eof\n");
161 		return 0;
162 	}
163 	if (par->__magic != SIO_PAR_MAGIC) {
164 		DPRINTF("sio_setpar: use of uninitialized sio_par structure\n");
165 		hdl->eof = 1;
166 		return 0;
167 	}
168 	if (hdl->started) {
169 		DPRINTF("sio_setpar: already started\n");
170 		hdl->eof = 1;
171 		return 0;
172 	}
173 	if (par->bufsz != ~0U) {
174 		DPRINTF("sio_setpar: setting bufsz is deprecated\n");
175 		par->appbufsz = par->bufsz;
176 	}
177 	if (par->rate != ~0U && par->appbufsz == ~0U)
178 		par->appbufsz = par->rate * 200 / 1000;
179 	return hdl->ops->setpar(hdl, par);
180 }
181 
182 int
183 sio_getpar(struct sio_hdl *hdl, struct sio_par *par)
184 {
185 	if (hdl->eof) {
186 		DPRINTF("sio_getpar: eof\n");
187 		return 0;
188 	}
189 	if (hdl->started) {
190 		DPRINTF("sio_getpar: already started\n");
191 		hdl->eof = 1;
192 		return 0;
193 	}
194 	if (!hdl->ops->getpar(hdl, par)) {
195 		par->__magic = 0;
196 		return 0;
197 	}
198 	par->__magic = 0;
199 	return 1;
200 }
201 
202 int
203 sio_getcap(struct sio_hdl *hdl, struct sio_cap *cap)
204 {
205 	if (hdl->eof) {
206 		DPRINTF("sio_getcap: eof\n");
207 		return 0;
208 	}
209 	if (hdl->started) {
210 		DPRINTF("sio_getcap: already started\n");
211 		hdl->eof = 1;
212 		return 0;
213 	}
214 	return hdl->ops->getcap(hdl, cap);
215 }
216 
217 static int
218 sio_psleep(struct sio_hdl *hdl, int event)
219 {
220 	struct pollfd pfd;
221 	int revents;
222 
223 	for (;;) {
224 		sio_pollfd(hdl, &pfd, event);
225 		while (poll(&pfd, 1, -1) < 0) {
226 			if (errno == EINTR)
227 				continue;
228 			DPERROR("sio_psleep: poll");
229 			hdl->eof = 1;
230 			return 0;
231 		}
232 		revents = sio_revents(hdl, &pfd);
233 		if (revents & POLLHUP) {
234 			DPRINTF("sio_psleep: hang-up\n");
235 			return 0;
236 		}
237 		if (revents & event)
238 			break;
239 	}
240 	return 1;
241 }
242 
243 size_t
244 sio_read(struct sio_hdl *hdl, void *buf, size_t len)
245 {
246 	unsigned n;
247 	char *data = buf;
248 	size_t todo = len;
249 
250 	if (hdl->eof) {
251 		DPRINTF("sio_read: eof\n");
252 		return 0;
253 	}
254 	if (!hdl->started || !(hdl->mode & SIO_REC)) {
255 		DPRINTF("sio_read: recording not started\n");
256 		hdl->eof = 1;
257 		return 0;
258 	}
259 	if (todo == 0) {
260 		DPRINTF("sio_read: zero length read ignored\n");
261 		return 0;
262 	}
263 	while (todo > 0) {
264 		n = hdl->ops->read(hdl, data, todo);
265 		if (n == 0) {
266 			if (hdl->nbio || hdl->eof || todo < len)
267 				break;
268 			if (!sio_psleep(hdl, POLLIN))
269 				break;
270 			continue;
271 		}
272 		data += n;
273 		todo -= n;
274 #ifdef DEBUG
275 		hdl->rcnt += n;
276 #endif
277 	}
278 	return len - todo;
279 }
280 
281 size_t
282 sio_write(struct sio_hdl *hdl, const void *buf, size_t len)
283 {
284 	unsigned n;
285 	const unsigned char *data = buf;
286 	size_t todo = len;
287 #ifdef DEBUG
288 	struct timeval tv0, tv1, dtv;
289 	unsigned us;
290 
291 	if (sndio_debug >= 2)
292 		gettimeofday(&tv0, NULL);
293 #endif
294 
295 	if (hdl->eof) {
296 		DPRINTF("sio_write: eof\n");
297 		return 0;
298 	}
299 	if (!hdl->started || !(hdl->mode & SIO_PLAY)) {
300 		DPRINTF("sio_write: playback not started\n");
301 		hdl->eof = 1;
302 		return 0;
303 	}
304 	if (todo == 0) {
305 		DPRINTF("sio_write: zero length write ignored\n");
306 		return 0;
307 	}
308 	while (todo > 0) {
309 		n = hdl->ops->write(hdl, data, todo);
310 		if (n == 0) {
311 			if (hdl->nbio || hdl->eof)
312 				break;
313 			if (!sio_psleep(hdl, POLLOUT))
314 				break;
315 			continue;
316 		}
317 		data += n;
318 		todo -= n;
319 #ifdef DEBUG
320 		hdl->wcnt += n;
321 #endif
322 	}
323 #ifdef DEBUG
324 	if (sndio_debug >= 2) {
325 		gettimeofday(&tv1, NULL);
326 		timersub(&tv0, &hdl->tv, &dtv);
327 		DPRINTF("%ld.%06ld: ", dtv.tv_sec, dtv.tv_usec);
328 
329 		timersub(&tv1, &tv0, &dtv);
330 		us = dtv.tv_sec * 1000000 + dtv.tv_usec;
331 		DPRINTF(
332 		    "sio_write: wrote %d bytes of %d in %uus\n",
333 		    (int)(len - todo), (int)len, us);
334 	}
335 #endif
336 	return len - todo;
337 }
338 
339 int
340 sio_nfds(struct sio_hdl *hdl)
341 {
342 	return hdl->ops->nfds(hdl);
343 }
344 
345 int
346 sio_pollfd(struct sio_hdl *hdl, struct pollfd *pfd, int events)
347 {
348 	if (hdl->eof)
349 		return 0;
350 	if (!hdl->started)
351 		events = 0;
352 	return hdl->ops->pollfd(hdl, pfd, events);
353 }
354 
355 int
356 sio_revents(struct sio_hdl *hdl, struct pollfd *pfd)
357 {
358 	int revents;
359 #ifdef DEBUG
360 	struct timeval tv0, tv1, dtv;
361 	unsigned us;
362 
363 	if (sndio_debug >= 2)
364 		gettimeofday(&tv0, NULL);
365 #endif
366 	if (hdl->eof)
367 		return POLLHUP;
368 #ifdef DEBUG
369 	hdl->pollcnt++;
370 #endif
371 	revents = hdl->ops->revents(hdl, pfd);
372 	if (!hdl->started)
373 		return revents & POLLHUP;
374 #ifdef DEBUG
375 	if (sndio_debug >= 2) {
376 		gettimeofday(&tv1, NULL);
377 		timersub(&tv0, &hdl->tv, &dtv);
378 		DPRINTF("%ld.%06ld: ", dtv.tv_sec, dtv.tv_usec);
379 
380 		timersub(&tv1, &tv0, &dtv);
381 		us = dtv.tv_sec * 1000000 + dtv.tv_usec;
382 		DPRINTF("sio_revents: revents = 0x%x, complete in %uus\n",
383 		    revents, us);
384 	}
385 #endif
386 	return revents;
387 }
388 
389 int
390 sio_eof(struct sio_hdl *hdl)
391 {
392 	return hdl->eof;
393 }
394 
395 void
396 sio_onmove(struct sio_hdl *hdl, void (*cb)(void *, int), void *addr)
397 {
398 	if (hdl->started) {
399 		DPRINTF("sio_onmove: already started\n");
400 		hdl->eof = 1;
401 		return;
402 	}
403 	hdl->move_cb = cb;
404 	hdl->move_addr = addr;
405 }
406 
407 void
408 sio_onmove_cb(struct sio_hdl *hdl, int delta)
409 {
410 #ifdef DEBUG
411 	struct timeval tv0, dtv;
412 	long long playpos;
413 
414 	if (sndio_debug >= 3 && (hdl->mode & SIO_PLAY)) {
415 		gettimeofday(&tv0, NULL);
416 		timersub(&tv0, &hdl->tv, &dtv);
417 		DPRINTF("%ld.%06ld: ", dtv.tv_sec, dtv.tv_usec);
418 		hdl->realpos += delta;
419 		playpos = hdl->wcnt / (hdl->par.bps * hdl->par.pchan);
420 		DPRINTF("sio_onmove_cb: delta = %+7d, "
421 		    "plat = %+7lld, "
422 		    "realpos = %+7lld, "
423 		    "bufused = %+7lld\n",
424 		    delta,
425 		    playpos - hdl->realpos,
426 		    hdl->realpos,
427 		    hdl->realpos < 0 ? playpos : playpos - hdl->realpos);
428 	}
429 #endif
430 	if (hdl->move_cb)
431 		hdl->move_cb(hdl->move_addr, delta);
432 }
433 
434 int
435 sio_setvol(struct sio_hdl *hdl, unsigned ctl)
436 {
437 	if (hdl->eof)
438 		return 0;
439 	if (!hdl->ops->setvol)
440 		return 1;
441 	if (!hdl->ops->setvol(hdl, ctl))
442 		return 0;
443 	hdl->ops->getvol(hdl);
444 	return 1;
445 }
446 
447 int
448 sio_onvol(struct sio_hdl *hdl, void (*cb)(void *, unsigned), void *addr)
449 {
450 	if (hdl->started) {
451 		DPRINTF("sio_onvol: already started\n");
452 		hdl->eof = 1;
453 		return 0;
454 	}
455 	if (!hdl->ops->setvol)
456 		return 0;
457 	hdl->vol_cb = cb;
458 	hdl->vol_addr = addr;
459 	hdl->ops->getvol(hdl);
460 	return 1;
461 }
462 
463 void
464 sio_onvol_cb(struct sio_hdl *hdl, unsigned ctl)
465 {
466 	if (hdl->vol_cb)
467 		hdl->vol_cb(hdl->vol_addr, ctl);
468 }
469