xref: /illumos-gate/usr/src/uts/common/io/audio/impl/audio_input.c (revision 45ede40b2394db7967e59f19288fae9b62efd4aa)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright (C) 4Front Technologies 1996-2008.
23  *
24  * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
25  */
26 
27 /*
28  * Purpose: Virtual mixing audio input routines
29  *
30  * This file contains the actual mixing and resampling engine for input.
31  */
32 
33 #include <sys/ddi.h>
34 #include <sys/sunddi.h>
35 #include <sys/sysmacros.h>
36 #include <sys/sdt.h>
37 #include "audio_impl.h"
38 
39 #define	DECL_AUDIO_IMPORT(NAME, TYPE, SWAP, SHIFT)			\
40 void									\
41 auimpl_import_##NAME(audio_engine_t *e, uint_t nfr, audio_stream_t *sp)	\
42 {									\
43 	int		nch = e->e_nchan;				\
44 	int32_t		*out = (void *)sp->s_cnv_src;			\
45 	TYPE		*in = (void *)e->e_data;			\
46 	int		ch = 0;						\
47 	int		vol = sp->s_gain_eff;				\
48 									\
49 	do {	/* for each channel */					\
50 		TYPE 	*ip;						\
51 		int32_t *op;						\
52 		int 	i;						\
53 		int 	incr = e->e_chincr[ch];				\
54 		uint_t	tidx = e->e_tidx;				\
55 									\
56 		/* get value and adjust next channel offset */		\
57 		op = out++;						\
58 		ip = in + e->e_choffs[ch] + (tidx * incr);		\
59 									\
60 		i = nfr;						\
61 									\
62 		do {	/* for each frame */				\
63 			int32_t	sample = (TYPE)SWAP(*ip);		\
64 			int32_t	scaled = sample SHIFT;			\
65 									\
66 			scaled *= vol;					\
67 			scaled /= AUDIO_VOL_SCALE;			\
68 									\
69 			*op = scaled;					\
70 			op += nch;					\
71 									\
72 			ip += incr;					\
73 			if (++tidx == e->e_nframes) {			\
74 				tidx = 0;				\
75 				ip = in + e->e_choffs[ch];		\
76 			}						\
77 		} while (--i);						\
78 		ch++;							\
79 	} while (ch < nch);						\
80 }
81 
82 DECL_AUDIO_IMPORT(16ne, int16_t, /* nop */, << 8)
83 DECL_AUDIO_IMPORT(16oe, int16_t, ddi_swap16, << 8)
84 DECL_AUDIO_IMPORT(32ne, int32_t, /* nop */, >> 8)
85 DECL_AUDIO_IMPORT(32oe, int32_t, ddi_swap32, >> 8)
86 DECL_AUDIO_IMPORT(24ne, int32_t, /* nop */, /* nop */)
87 DECL_AUDIO_IMPORT(24oe, int32_t, ddi_swap32, /* nop */)
88 
89 /*
90  * Produce capture data.  This takes data from the conversion buffer
91  * and copies it into the stream data buffer.
92  */
93 static void
94 auimpl_produce_data(audio_stream_t *sp, uint_t count)
95 {
96 	uint_t	nframes;
97 	uint_t	framesz;
98 	caddr_t	cnvsrc;
99 	caddr_t	data;
100 
101 	nframes = sp->s_nframes;
102 	framesz = sp->s_framesz;
103 
104 	ASSERT(sp->s_head >= sp->s_tail);
105 	ASSERT(sp->s_hidx < nframes);
106 	ASSERT(sp->s_tidx < nframes);
107 
108 	/*
109 	 * Copy data.  We deal properly with wraps.  Done as a
110 	 * do...while to minimize the number of tests.
111 	 */
112 	cnvsrc = sp->s_cnv_src;
113 	data = sp->s_data + (sp->s_hidx * framesz);
114 	do {
115 		unsigned nf;
116 		unsigned nb;
117 
118 		nf = min(nframes - sp->s_hidx, count);
119 		nb = nf * framesz;
120 
121 		bcopy(cnvsrc, data, nb);
122 		data += nb;
123 		cnvsrc += nb;
124 		sp->s_hidx += nf;
125 		sp->s_head += nf;
126 		count -= nf;
127 		sp->s_samples += nf;
128 		if (sp->s_hidx == nframes) {
129 			sp->s_hidx = 0;
130 			data = sp->s_data;
131 		}
132 	} while (count);
133 
134 	ASSERT(sp->s_tail <= sp->s_head);
135 	ASSERT(sp->s_hidx < nframes);
136 }
137 
138 void
139 auimpl_input_callback(void *arg)
140 {
141 	audio_engine_t	*e = arg;
142 	uint_t		fragfr = e->e_fragfr;
143 	audio_stream_t	*sp;
144 	audio_client_t	*c;
145 	audio_client_t	*clist = NULL;
146 	list_t		*l = &e->e_streams;
147 	uint64_t	h;
148 
149 	mutex_enter(&e->e_lock);
150 
151 	if (e->e_suspended || e->e_failed || !e->e_periodic) {
152 		mutex_exit(&e->e_lock);
153 		return;
154 	}
155 
156 	if (e->e_need_start) {
157 		int rv;
158 		if ((rv = ENG_START(e)) != 0) {
159 			e->e_failed = B_TRUE;
160 			mutex_exit(&e->e_lock);
161 			audio_dev_warn(e->e_dev,
162 			    "failed starting input, rv = %d", rv);
163 			return;
164 		}
165 		e->e_need_start = B_FALSE;
166 	}
167 
168 	h = ENG_COUNT(e);
169 	ASSERT(h >= e->e_head);
170 	if (h < e->e_head) {
171 		/*
172 		 * This is a sign of a serious bug.  We should
173 		 * probably offline the device via FMA, if we ever
174 		 * support FMA for audio devices.
175 		 */
176 		e->e_failed = B_TRUE;
177 		ENG_STOP(e);
178 		mutex_exit(&e->e_lock);
179 		audio_dev_warn(e->e_dev,
180 		    "device malfunction: broken capture sample counter");
181 		return;
182 	}
183 	e->e_head = h;
184 	ASSERT(e->e_head >= e->e_tail);
185 
186 	if ((e->e_head - e->e_tail) > e->e_nframes) {
187 		/* no room for data, not much we can do */
188 		e->e_errors++;
189 		e->e_overruns++;
190 	}
191 
192 	/* consume all fragments in the buffer */
193 	while ((e->e_head - e->e_tail) > fragfr) {
194 
195 		/*
196 		 * Consider doing the SYNC outside of the lock.
197 		 */
198 		ENG_SYNC(e, fragfr);
199 
200 		for (sp = list_head(l); sp != NULL; sp = list_next(l, sp)) {
201 			int space;
202 			int count;
203 
204 			mutex_enter(&sp->s_lock);
205 			/* skip over streams paused or not running */
206 			if (sp->s_paused || !sp->s_running) {
207 				mutex_exit(&sp->s_lock);
208 				continue;
209 			}
210 			sp->s_cnv_src = sp->s_cnv_buf0;
211 			sp->s_cnv_dst = sp->s_cnv_buf1;
212 
213 			e->e_import(e, fragfr, sp);
214 
215 			/*
216 			 * Optionally convert fragment to requested sample
217 			 * format and rate.
218 			 */
219 			if (sp->s_converter != NULL) {
220 				count = sp->s_converter(sp, fragfr);
221 			} else {
222 				count = fragfr;
223 			}
224 
225 			ASSERT(sp->s_head >= sp->s_tail);
226 			space = sp->s_nframes - (sp->s_head - sp->s_tail);
227 			if (count > space) {
228 				e->e_stream_overruns++;
229 				e->e_errors++;
230 				sp->s_errors += count - space;
231 				count = space;
232 			}
233 
234 			auimpl_produce_data(sp, count);
235 
236 			/* wake blocked threads (blocking reads, etc.) */
237 			cv_broadcast(&sp->s_cv);
238 
239 			mutex_exit(&sp->s_lock);
240 
241 			/*
242 			 * Add client to notification list.  We'll
243 			 * process it after dropping the lock.
244 			 */
245 			c = sp->s_client;
246 
247 			if ((c->c_input != NULL) &&
248 			    (c->c_next_input == NULL)) {
249 				auclnt_hold(c);
250 				c->c_next_input = clist;
251 				clist = c;
252 			}
253 		}
254 
255 		/*
256 		 * Update the tail pointer, and the data pointer.
257 		 */
258 		e->e_tail += fragfr;
259 		e->e_tidx += fragfr;
260 		if (e->e_tidx >= e->e_nframes) {
261 			e->e_tidx -= e->e_nframes;
262 		}
263 	}
264 
265 	mutex_exit(&e->e_lock);
266 
267 	/*
268 	 * Notify client personalities.
269 	 */
270 
271 	while ((c = clist) != NULL) {
272 		clist = c->c_next_input;
273 		c->c_next_input = NULL;
274 		c->c_input(c);
275 		auclnt_release(c);
276 	}
277 }
278