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