xref: /freebsd/sys/dev/sound/pcm/feeder.c (revision daf1cffce2e07931f27c6c6998652e90df6ba87e)
1 /*
2  * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  *
26  * $FreeBSD$
27  */
28 
29 #include <dev/sound/pcm/sound.h>
30 
31 static int chn_addfeeder(pcm_channel *c, pcm_feeder *f);
32 static int chn_removefeeder(pcm_channel *c);
33 
34 #define FEEDBUFSZ	8192
35 
36 static unsigned char ulaw_to_u8[] = {
37      3,    7,   11,   15,   19,   23,   27,   31,
38     35,   39,   43,   47,   51,   55,   59,   63,
39     66,   68,   70,   72,   74,   76,   78,   80,
40     82,   84,   86,   88,   90,   92,   94,   96,
41     98,   99,  100,  101,  102,  103,  104,  105,
42    106,  107,  108,  109,  110,  111,  112,  113,
43    113,  114,  114,  115,  115,  116,  116,  117,
44    117,  118,  118,  119,  119,  120,  120,  121,
45    121,  121,  122,  122,  122,  122,  123,  123,
46    123,  123,  124,  124,  124,  124,  125,  125,
47    125,  125,  125,  125,  126,  126,  126,  126,
48    126,  126,  126,  126,  127,  127,  127,  127,
49    127,  127,  127,  127,  127,  127,  127,  127,
50    128,  128,  128,  128,  128,  128,  128,  128,
51    128,  128,  128,  128,  128,  128,  128,  128,
52    128,  128,  128,  128,  128,  128,  128,  128,
53    253,  249,  245,  241,  237,  233,  229,  225,
54    221,  217,  213,  209,  205,  201,  197,  193,
55    190,  188,  186,  184,  182,  180,  178,  176,
56    174,  172,  170,  168,  166,  164,  162,  160,
57    158,  157,  156,  155,  154,  153,  152,  151,
58    150,  149,  148,  147,  146,  145,  144,  143,
59    143,  142,  142,  141,  141,  140,  140,  139,
60    139,  138,  138,  137,  137,  136,  136,  135,
61    135,  135,  134,  134,  134,  134,  133,  133,
62    133,  133,  132,  132,  132,  132,  131,  131,
63    131,  131,  131,  131,  130,  130,  130,  130,
64    130,  130,  130,  130,  129,  129,  129,  129,
65    129,  129,  129,  129,  129,  129,  129,  129,
66    128,  128,  128,  128,  128,  128,  128,  128,
67    128,  128,  128,  128,  128,  128,  128,  128,
68    128,  128,  128,  128,  128,  128,  128,  128,
69 };
70 
71 static unsigned char u8_to_ulaw[] = {
72      0,    0,    0,    0,    0,    1,    1,    1,
73      1,    2,    2,    2,    2,    3,    3,    3,
74      3,    4,    4,    4,    4,    5,    5,    5,
75      5,    6,    6,    6,    6,    7,    7,    7,
76      7,    8,    8,    8,    8,    9,    9,    9,
77      9,   10,   10,   10,   10,   11,   11,   11,
78     11,   12,   12,   12,   12,   13,   13,   13,
79     13,   14,   14,   14,   14,   15,   15,   15,
80     15,   16,   16,   17,   17,   18,   18,   19,
81     19,   20,   20,   21,   21,   22,   22,   23,
82     23,   24,   24,   25,   25,   26,   26,   27,
83     27,   28,   28,   29,   29,   30,   30,   31,
84     31,   32,   33,   34,   35,   36,   37,   38,
85     39,   40,   41,   42,   43,   44,   45,   46,
86     47,   49,   51,   53,   55,   57,   59,   61,
87     63,   66,   70,   74,   78,   84,   92,  104,
88    254,  231,  219,  211,  205,  201,  197,  193,
89    190,  188,  186,  184,  182,  180,  178,  176,
90    175,  174,  173,  172,  171,  170,  169,  168,
91    167,  166,  165,  164,  163,  162,  161,  160,
92    159,  159,  158,  158,  157,  157,  156,  156,
93    155,  155,  154,  154,  153,  153,  152,  152,
94    151,  151,  150,  150,  149,  149,  148,  148,
95    147,  147,  146,  146,  145,  145,  144,  144,
96    143,  143,  143,  143,  142,  142,  142,  142,
97    141,  141,  141,  141,  140,  140,  140,  140,
98    139,  139,  139,  139,  138,  138,  138,  138,
99    137,  137,  137,  137,  136,  136,  136,  136,
100    135,  135,  135,  135,  134,  134,  134,  134,
101    133,  133,  133,  133,  132,  132,  132,  132,
102    131,  131,  131,  131,  130,  130,  130,  130,
103    129,  129,  129,  129,  128,  128,  128,  128,
104 };
105 
106 /*****************************************************************************/
107 
108 static int
109 feed_root(pcm_feeder *feeder, pcm_channel *ch, u_int8_t *buffer, u_int32_t count, struct uio *stream)
110 {
111 	int ret, c = 0, s;
112 	KASSERT(count, ("feed_root: count == 0"));
113 	count &= ~((1 << ch->align) - 1);
114 	KASSERT(count, ("feed_root: aligned count == 0"));
115 	s = spltty();
116 	if (ch->smegcnt > 0) {
117 		c = min(ch->smegcnt, count);
118 		bcopy(ch->smegbuf, buffer, c);
119 		ch->smegcnt -= c;
120 	}
121 	count = min(count, stream->uio_resid);
122 	if (count) {
123 		ret = uiomove(buffer, count, stream);
124 		KASSERT(ret == 0, ("feed_root: uiomove failed"));
125 	}
126 	splx(s);
127 	return c + count;
128 }
129 pcm_feeder feeder_root = { "root", 0, NULL, NULL, feed_root };
130 
131 /*****************************************************************************/
132 
133 static int
134 feed_8to16(pcm_feeder *f, pcm_channel *c, u_int8_t *b, u_int32_t count, struct uio *stream)
135 {
136 	int i, j, k;
137 	k = f->source->feed(f->source, c, b, count / 2, stream);
138 	j = k - 1;
139 	i = j * 2 + 1;
140 	while (i > 0 && j >= 0) {
141 		b[i--] = b[j--];
142 		b[i--] = 0;
143 	}
144 	return k * 2;
145 }
146 static pcm_feeder feeder_8to16 = { "8to16", 0, NULL, NULL, feed_8to16 };
147 
148 /*****************************************************************************/
149 
150 static int
151 feed_16to8_init(pcm_feeder *f)
152 {
153 	f->data = malloc(FEEDBUFSZ, M_DEVBUF, M_NOWAIT);
154 	return (f->data == NULL);
155 }
156 
157 static int
158 feed_16to8_free(pcm_feeder *f)
159 {
160 	if (f->data) free(f->data, M_DEVBUF);
161 	f->data = NULL;
162 	return 0;
163 }
164 
165 static int
166 feed_16to8le(pcm_feeder *f, pcm_channel *c, u_int8_t *b, u_int32_t count, struct uio *stream)
167 {
168 	u_int32_t i = 0, toget = count * 2;
169 	int j = 1, k;
170 	k = f->source->feed(f->source, c, f->data, min(toget, FEEDBUFSZ), stream);
171 	while (j < k) {
172 		b[i++] = ((u_int8_t *)f->data)[j];
173 		j += 2;
174 	}
175 	return i;
176 }
177 static pcm_feeder feeder_16to8le =
178 	{ "16to8le", 1, feed_16to8_init, feed_16to8_free, feed_16to8le };
179 
180 /*****************************************************************************/
181 
182 static int
183 feed_monotostereo8(pcm_feeder *f, pcm_channel *c, u_int8_t *b, u_int32_t count, struct uio *stream)
184 {
185 	int i, j, k = f->source->feed(f->source, c, b, count / 2, stream);
186 	j = k - 1;
187 	i = j * 2 + 1;
188 	while (i > 0 && j >= 0) {
189 		b[i--] = b[j];
190 		b[i--] = b[j];
191 		j--;
192 	}
193 	return k * 2;
194 }
195 static pcm_feeder feeder_monotostereo8 =
196 	{ "monotostereo8", 0, NULL, NULL, feed_monotostereo8 };
197 
198 /*****************************************************************************/
199 
200 static int
201 feed_stereotomono8_init(pcm_feeder *f)
202 {
203 	f->data = malloc(FEEDBUFSZ, M_DEVBUF, M_NOWAIT);
204 	return (f->data == NULL);
205 }
206 
207 static int
208 feed_stereotomono8_free(pcm_feeder *f)
209 {
210 	if (f->data) free(f->data, M_DEVBUF);
211 	f->data = NULL;
212 	return 0;
213 }
214 
215 static int
216 feed_stereotomono8(pcm_feeder *f, pcm_channel *c, u_int8_t *b, u_int32_t count, struct uio *stream)
217 {
218 	u_int32_t i = 0, toget = count * 2;
219 	int j = 0, k;
220 	k = f->source->feed(f->source, c, f->data, min(toget, FEEDBUFSZ), stream);
221 	while (j < k) {
222 		b[i++] = ((u_int8_t *)f->data)[j];
223 		j += 2;
224 	}
225 	return i;
226 }
227 static pcm_feeder feeder_stereotomono8 =
228 	{ "stereotomono8", 1, feed_stereotomono8_init, feed_stereotomono8_free,
229 	feed_stereotomono8 };
230 
231 /*****************************************************************************/
232 
233 static int
234 feed_endian(pcm_feeder *f, pcm_channel *c, u_int8_t *b, u_int32_t count, struct uio *stream)
235 {
236 	u_int8_t t;
237 	int i = 0, j = f->source->feed(f->source, c, b, count, stream);
238 	while (i < j) {
239 		t = b[i];
240 		b[i] = b[i + 1];
241 		b[i + 1] = t;
242 		i += 2;
243 	}
244 	return i;
245 }
246 static pcm_feeder feeder_endian = { "endian", -1, NULL, NULL, feed_endian };
247 
248 /*****************************************************************************/
249 
250 static int
251 feed_sign(pcm_feeder *f, pcm_channel *c, u_int8_t *b, u_int32_t count, struct uio *stream)
252 {
253 	int i = 0, j = f->source->feed(f->source, c, b, count, stream);
254 	int ssz = (int)f->data, ofs = ssz - 1;
255 	while (i < j) {
256 		b[i + ofs] ^= 0x80;
257 		i += ssz;
258 	}
259 	return i;
260 }
261 static pcm_feeder feeder_sign8 =
262 	{ "sign8", 0, NULL, NULL, feed_sign, (void *)1 };
263 static pcm_feeder feeder_sign16 =
264 	{ "sign16", -1, NULL, NULL, feed_sign, (void *)2 };
265 
266 /*****************************************************************************/
267 
268 static int
269 feed_table(pcm_feeder *f, pcm_channel *c, u_int8_t *b, u_int32_t count, struct uio *stream)
270 {
271 	int i = 0, j = f->source->feed(f->source, c, b, count, stream);
272 	while (i < j) {
273 		b[i] = ((u_int8_t *)f->data)[b[i]];
274 		i++;
275 	}
276 	return i;
277 }
278 static pcm_feeder feeder_ulawtou8 =
279 	{ "ulawtou8", 0, NULL, NULL, feed_table, ulaw_to_u8 };
280 static pcm_feeder feeder_u8toulaw =
281 	{ "u8toulaw", 0, NULL, NULL, feed_table, u8_to_ulaw };
282 
283 /*****************************************************************************/
284 
285 struct fmtspec {
286 	int stereo;
287 	int sign;
288 	int bit16;
289 	int bigendian;
290 	int ulaw;
291 	int bad;
292 };
293 
294 struct fmtcvt {
295 	pcm_feeder *f;
296 	struct fmtspec ispec, ospec;
297 };
298 
299 struct fmtcvt cvttab[] = {
300 	{&feeder_ulawtou8, 	{-1,  0, 0, 0,  1}, 	{-1,  0, 0, 0,  0}},
301 	{&feeder_u8toulaw, 	{-1,  0, 0, 0,  0}, 	{-1,  0, 0, 0,  1}},
302 	{&feeder_sign8,		{-1,  0, 0, 0,  0},	{-1,  1, 0, 0,  0}},
303 	{&feeder_sign8,		{-1,  1, 0, 0,  0},	{-1,  0, 0, 0,  0}},
304 	{&feeder_monotostereo8,	{ 0, -1, 0, 0, -1},	{ 1, -1, 0, 0, -1}},
305 	{&feeder_stereotomono8, { 1, -1, 0, 0, -1},	{ 0, -1, 0, 0, -1}},
306 	{&feeder_sign16,	{-1,  0, 1, 0,  0},	{-1,  1, 1, 0,  0}},
307 	{&feeder_sign16,	{-1,  1, 1, 0,  0},	{-1,  0, 1, 0,  0}},
308 	{&feeder_8to16,		{-1, -1, 0, 0,  0},	{-1, -1, 1, 0,  0}},
309 	{&feeder_16to8le,	{-1, -1, 1, 0,  0},	{-1, -1, 0, 0,  0}},
310 	{&feeder_endian,	{-1, -1, 1, 0,  0},	{-1, -1, 1, 1,  0}},
311 	{&feeder_endian,	{-1, -1, 1, 1,  0},	{-1, -1, 1, 0,  0}},
312 };
313 #define FEEDERTABSZ (sizeof(cvttab) / sizeof(struct fmtcvt))
314 
315 static int
316 getspec(u_int32_t fmt, struct fmtspec *spec)
317 {
318 	spec->stereo = (fmt & AFMT_STEREO)? 1 : 0;
319 	spec->sign = (fmt & AFMT_SIGNED)? 1 : 0;
320 	spec->bit16 = (fmt & AFMT_16BIT)? 1 : 0;
321 	spec->bigendian = (fmt & AFMT_BIGENDIAN)? 1 : 0;
322 	spec->ulaw = (fmt & AFMT_MU_LAW)? 1 : 0;
323 	spec->bad = (fmt & (AFMT_A_LAW | AFMT_MPEG))? 1 : 0;
324 	return 0;
325 }
326 
327 static int
328 cmp(int x, int y)
329 {
330 	return (x == -1 || x == y || y == -1)? 1 : 0;
331 }
332 
333 static int
334 cmpspec(struct fmtspec *x, struct fmtspec *y)
335 {
336 	int i = 0;
337 	if (cmp(x->stereo, y->stereo)) i |= 0x01;
338 	if (cmp(x->sign, y->sign)) i |= 0x02;
339 	if (cmp(x->bit16, y->bit16)) i |= 0x04;
340 	if (cmp(x->bigendian, y->bigendian)) i |= 0x08;
341 	if (cmp(x->ulaw, y->ulaw)) i |= 0x10;
342 	return i;
343 }
344 
345 static int
346 cvtapply(pcm_channel *c, struct fmtcvt *cvt, struct fmtspec *s)
347 {
348 	int i = cmpspec(s, &cvt->ospec);
349 	chn_addfeeder(c, cvt->f);
350 	if (cvt->ospec.stereo != -1) s->stereo = cvt->ospec.stereo;
351 	if (cvt->ospec.sign != -1) s->sign = cvt->ospec.sign;
352 	if (cvt->ospec.bit16 != -1) s->bit16 = cvt->ospec.bit16;
353 	if (cvt->ospec.bigendian != -1) s->bigendian = cvt->ospec.bigendian;
354 	if (cvt->ospec.ulaw != -1) s->ulaw = cvt->ospec.ulaw;
355 	return i;
356 }
357 
358 int
359 chn_feedchain(pcm_channel *c)
360 {
361 	int i, chosen, iter;
362 	u_int32_t mask;
363 	struct fmtspec s, t;
364 	struct fmtcvt *e;
365 
366 	while (chn_removefeeder(c) != -1);
367 	c->align = 0;
368 	if ((c->format & chn_getcaps(c)->formats) == c->format)
369 		return c->format;
370 	getspec(c->format, &s);
371 	if (s.bad) return -1;
372 	getspec(chn_getcaps(c)->bestfmt, &t);
373 	mask = (~cmpspec(&s, &t)) & 0x1f;
374 	iter = 0;
375 	do {
376 		if (mask == 0 || iter >= 8) break;
377 		chosen = -1;
378 		for (i = 0; i < FEEDERTABSZ && chosen == -1; i++) {
379 			e = &cvttab[i];
380 			if ((cmpspec(&s, &e->ispec) == 0x1f) &&
381 			   ((~cmpspec(&e->ispec, &e->ospec)) & mask))
382 			   chosen = i;
383 		}
384 		if (chosen != -1) mask &= cvtapply(c, &cvttab[chosen], &s);
385 		iter++;
386 	} while (chosen != -1);
387 	return (iter < 8)? chn_getcaps(c)->bestfmt : -1;
388 }
389 
390 static int
391 chn_addfeeder(pcm_channel *c, pcm_feeder *f)
392 {
393 	pcm_feeder *n;
394 	n = malloc(sizeof(pcm_feeder), M_DEVBUF, M_NOWAIT);
395 	*n = *f;
396 	n->source = c->feeder;
397 	c->feeder = n;
398 	if (n->init) n->init(n);
399 	if (n->align > 0) c->align += n->align;
400 	else if (n->align < 0 && c->align < -n->align) c->align -= n->align;
401 	return 0;
402 }
403 
404 static int
405 chn_removefeeder(pcm_channel *c)
406 {
407 	pcm_feeder *f;
408 	if (c->feeder == &feeder_root) return -1;
409 	f = c->feeder->source;
410 	if (c->feeder->free) c->feeder->free(c->feeder);
411 	free(c->feeder, M_DEVBUF);
412 	c->feeder = f;
413 	return 0;
414 }
415 
416