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