xref: /freebsd/sys/dev/sound/pcm/feeder.c (revision 4cf49a43559ed9fdad601bdcccd2c55963008675)
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/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, u_int8_t *buffer, u_int32_t count, struct uio *stream)
110 {
111 	int ret, tmp = 0, c = 0;
112 	if (!count) panic("feed_root: count == 0");
113 	while ((stream->uio_resid > 0) && (c < count)) {
114 		tmp = stream->uio_resid;
115 		ret = uiomove(buffer + c, count - c, stream);
116 		if (ret) panic("feed_root: uiomove failed");
117 		tmp -= stream->uio_resid;
118 		c += tmp;
119 	}
120 	if (!tmp) panic("feed_root: uiomove didn't");
121 	return tmp;
122 }
123 pcm_feeder feeder_root = { "root", 0, NULL, NULL, feed_root };
124 
125 /*****************************************************************************/
126 
127 static int
128 feed_8to16(pcm_feeder *f, u_int8_t *b, u_int32_t count, struct uio *stream)
129 {
130 	int i, j, k;
131 	k = f->source->feed(f->source, b, count / 2, stream);
132 	j = k - 1;
133 	i = j * 2 + 1;
134 	while (i > 0 && j >= 0) {
135 		b[i--] = b[j--];
136 		b[i--] = 0;
137 	}
138 	return k * 2;
139 }
140 static pcm_feeder feeder_8to16 = { "8to16", 0, NULL, NULL, feed_8to16 };
141 
142 /*****************************************************************************/
143 
144 static int
145 feed_16to8_init(pcm_feeder *f)
146 {
147 	f->data = malloc(FEEDBUFSZ, M_DEVBUF, M_NOWAIT);
148 	return (f->data == NULL);
149 }
150 
151 static int
152 feed_16to8_free(pcm_feeder *f)
153 {
154 	if (f->data) free(f->data, M_DEVBUF);
155 	f->data = NULL;
156 	return 0;
157 }
158 
159 static int
160 feed_16to8le(pcm_feeder *f, u_int8_t *b, u_int32_t count, struct uio *stream)
161 {
162 	u_int32_t i = 0, toget = count * 2;
163 	int j = 1, k;
164 	k = f->source->feed(f->source, f->data, min(toget, FEEDBUFSZ), stream);
165 	while (j < k) {
166 		b[i++] = ((u_int8_t *)f->data)[j];
167 		j += 2;
168 	}
169 	return i;
170 }
171 static pcm_feeder feeder_16to8le =
172 	{ "16to8le", 1, feed_16to8_init, feed_16to8_free, feed_16to8le };
173 
174 /*****************************************************************************/
175 
176 static int
177 feed_monotostereo8(pcm_feeder *f, u_int8_t *b, u_int32_t count, struct uio *stream)
178 {
179 	int i, j, k = f->source->feed(f->source, b, count / 2, stream);
180 	j = k - 1;
181 	i = j * 2 + 1;
182 	while (i > 0 && j >= 0) {
183 		b[i--] = b[j];
184 		b[i--] = b[j];
185 		j--;
186 	}
187 	return k * 2;
188 }
189 static pcm_feeder feeder_monotostereo8 =
190 	{ "monotostereo8", 0, NULL, NULL, feed_monotostereo8 };
191 
192 /*****************************************************************************/
193 
194 static int
195 feed_stereotomono8_init(pcm_feeder *f)
196 {
197 	f->data = malloc(FEEDBUFSZ, M_DEVBUF, M_NOWAIT);
198 	return (f->data == NULL);
199 }
200 
201 static int
202 feed_stereotomono8_free(pcm_feeder *f)
203 {
204 	if (f->data) free(f->data, M_DEVBUF);
205 	f->data = NULL;
206 	return 0;
207 }
208 
209 static int
210 feed_stereotomono8(pcm_feeder *f, u_int8_t *b, u_int32_t count, struct uio *stream)
211 {
212 	u_int32_t i = 0, toget = count * 2;
213 	int j = 0, k;
214 	k = f->source->feed(f->source, f->data, min(toget, FEEDBUFSZ), stream);
215 	while (j < k) {
216 		b[i++] = ((u_int8_t *)f->data)[j];
217 		j += 2;
218 	}
219 	return i;
220 }
221 static pcm_feeder feeder_stereotomono8 =
222 	{ "stereotomono8", 1, feed_stereotomono8_init, feed_stereotomono8_free,
223 	feed_stereotomono8 };
224 
225 /*****************************************************************************/
226 
227 static int
228 feed_endian(pcm_feeder *f, u_int8_t *b, u_int32_t count, struct uio *stream)
229 {
230 	u_int8_t t;
231 	int i = 0, j = f->source->feed(f->source, b, count, stream);
232 	while (i < j) {
233 		t = b[i];
234 		b[i] = b[i + 1];
235 		b[i + 1] = t;
236 		i += 2;
237 	}
238 	return i;
239 }
240 static pcm_feeder feeder_endian = { "endian", -1, NULL, NULL, feed_endian };
241 
242 /*****************************************************************************/
243 
244 static int
245 feed_sign(pcm_feeder *f, u_int8_t *b, u_int32_t count, struct uio *stream)
246 {
247 	int i = 0, j = f->source->feed(f->source, b, count, stream);
248 	int ssz = (int)f->data, ofs = ssz - 1;
249 	while (i < j) {
250 		b[i + ofs] ^= 0x80;
251 		i += ssz;
252 	}
253 	return i;
254 }
255 static pcm_feeder feeder_sign8 =
256 	{ "sign8", 0, NULL, NULL, feed_sign, (void *)1 };
257 static pcm_feeder feeder_sign16 =
258 	{ "sign16", -1, NULL, NULL, feed_sign, (void *)2 };
259 
260 /*****************************************************************************/
261 
262 static int
263 feed_table(pcm_feeder *f, u_int8_t *b, u_int32_t count, struct uio *stream)
264 {
265 	int i = 0, j = f->source->feed(f->source, b, count, stream);
266 	while (i < j) {
267 		b[i] = ((u_int8_t *)f->data)[b[i]];
268 		i++;
269 	}
270 	return i;
271 }
272 static pcm_feeder feeder_ulawtou8 =
273 	{ "ulawtou8", 0, NULL, NULL, feed_table, ulaw_to_u8 };
274 static pcm_feeder feeder_u8toulaw =
275 	{ "u8toulaw", 0, NULL, NULL, feed_table, u8_to_ulaw };
276 
277 /*****************************************************************************/
278 
279 struct fmtspec {
280 	int stereo;
281 	int sign;
282 	int bit16;
283 	int bigendian;
284 	int ulaw;
285 	int bad;
286 };
287 
288 struct fmtcvt {
289 	pcm_feeder *f;
290 	struct fmtspec ispec, ospec;
291 };
292 
293 struct fmtcvt cvttab[] = {
294 	{&feeder_ulawtou8, 	{-1,  0, 0, 0,  1}, 	{-1,  0, 0, 0,  0}},
295 	{&feeder_u8toulaw, 	{-1,  0, 0, 0,  0}, 	{-1,  0, 0, 0,  1}},
296 	{&feeder_sign8,		{-1,  0, 0, 0,  0},	{-1,  1, 0, 0,  0}},
297 	{&feeder_sign8,		{-1,  1, 0, 0,  0},	{-1,  0, 0, 0,  0}},
298 	{&feeder_monotostereo8,	{ 0, -1, 0, 0, -1},	{ 1, -1, 0, 0, -1}},
299 	{&feeder_stereotomono8, { 1, -1, 0, 0, -1},	{ 0, -1, 0, 0, -1}},
300 	{&feeder_sign16,	{-1,  0, 1, 0,  0},	{-1,  1, 1, 0,  0}},
301 	{&feeder_sign16,	{-1,  1, 1, 0,  0},	{-1,  0, 1, 0,  0}},
302 	{&feeder_8to16,		{-1, -1, 0, 0,  0},	{-1, -1, 1, 0,  0}},
303 	{&feeder_16to8le,	{-1, -1, 1, 0,  0},	{-1, -1, 0, 0,  0}},
304 	{&feeder_endian,	{-1, -1, 1, 0,  0},	{-1, -1, 1, 1,  0}},
305 	{&feeder_endian,	{-1, -1, 1, 1,  0},	{-1, -1, 1, 0,  0}},
306 };
307 #define FEEDERTABSZ (sizeof(cvttab) / sizeof(struct fmtcvt))
308 
309 static int
310 getspec(u_int32_t fmt, struct fmtspec *spec)
311 {
312 	spec->stereo = (fmt & AFMT_STEREO)? 1 : 0;
313 	spec->sign = (fmt & AFMT_SIGNED)? 1 : 0;
314 	spec->bit16 = (fmt & AFMT_16BIT)? 1 : 0;
315 	spec->bigendian = (fmt & AFMT_BIGENDIAN)? 1 : 0;
316 	spec->ulaw = (fmt & AFMT_MU_LAW)? 1 : 0;
317 	spec->bad = (fmt & (AFMT_A_LAW | AFMT_MPEG))? 1 : 0;
318 	return 0;
319 }
320 
321 static int
322 cmp(int x, int y)
323 {
324 	return (x == -1 || x == y || y == -1)? 1 : 0;
325 }
326 
327 static int
328 cmpspec(struct fmtspec *x, struct fmtspec *y)
329 {
330 	int i = 0;
331 	if (cmp(x->stereo, y->stereo)) i |= 0x01;
332 	if (cmp(x->sign, y->sign)) i |= 0x02;
333 	if (cmp(x->bit16, y->bit16)) i |= 0x04;
334 	if (cmp(x->bigendian, y->bigendian)) i |= 0x08;
335 	if (cmp(x->ulaw, y->ulaw)) i |= 0x10;
336 	return i;
337 }
338 
339 static int
340 cvtapply(pcm_channel *c, struct fmtcvt *cvt, struct fmtspec *s)
341 {
342 	int i = cmpspec(s, &cvt->ospec);
343 	chn_addfeeder(c, cvt->f);
344 	if (cvt->ospec.stereo != -1) s->stereo = cvt->ospec.stereo;
345 	if (cvt->ospec.sign != -1) s->sign = cvt->ospec.sign;
346 	if (cvt->ospec.bit16 != -1) s->bit16 = cvt->ospec.bit16;
347 	if (cvt->ospec.bigendian != -1) s->bigendian = cvt->ospec.bigendian;
348 	if (cvt->ospec.ulaw != -1) s->ulaw = cvt->ospec.ulaw;
349 	return i;
350 }
351 
352 int
353 chn_feedchain(pcm_channel *c)
354 {
355 	int i, chosen, iter;
356 	u_int32_t mask;
357 	struct fmtspec s, t;
358 	struct fmtcvt *e;
359 
360 	while (chn_removefeeder(c) != -1);
361 	c->align = 0;
362 	if ((c->format & chn_getcaps(c)->formats) == c->format)
363 		return c->format;
364 	getspec(c->format, &s);
365 	if (s.bad) return -1;
366 	getspec(chn_getcaps(c)->bestfmt, &t);
367 	mask = (~cmpspec(&s, &t)) & 0x1f;
368 	iter = 0;
369 	do {
370 		if (mask == 0 || iter >= 8) break;
371 		chosen = -1;
372 		for (i = 0; i < FEEDERTABSZ && chosen == -1; i++) {
373 			e = &cvttab[i];
374 			if ((cmpspec(&s, &e->ispec) == 0x1f) &&
375 			   ((~cmpspec(&e->ispec, &e->ospec)) & mask))
376 			   chosen = i;
377 		}
378 		if (chosen != -1) mask &= cvtapply(c, &cvttab[chosen], &s);
379 		iter++;
380 	} while (chosen != -1);
381 	return (iter < 8)? chn_getcaps(c)->bestfmt : -1;
382 }
383 
384 static int
385 chn_addfeeder(pcm_channel *c, pcm_feeder *f)
386 {
387 	pcm_feeder *n;
388 	n = malloc(sizeof(pcm_feeder), M_DEVBUF, M_NOWAIT);
389 	*n = *f;
390 	n->source = c->feeder;
391 	c->feeder = n;
392 	if (n->init) n->init(n);
393 	if (n->align > 0) c->align += n->align;
394 	else if (n->align < 0 && c->align < -n->align) c->align -= n->align;
395 	return 0;
396 }
397 
398 static int
399 chn_removefeeder(pcm_channel *c)
400 {
401 	pcm_feeder *f;
402 	if (c->feeder == &feeder_root) return -1;
403 	f = c->feeder->source;
404 	if (c->feeder->free) c->feeder->free(c->feeder);
405 	free(c->feeder, M_DEVBUF);
406 	c->feeder = f;
407 	return 0;
408 }
409 
410