feeder_matrix.c revision 193640
1/*-
2 * Copyright (c) 2008-2009 Ariff Abdullah <ariff@FreeBSD.org>
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
27/*
28 * feeder_matrix: Generic any-to-any channel matrixing. Probably not the
29 *                accurate way of doing things, but it should be fast and
30 *                transparent enough, not to mention capable of handling
31 *                possible non-standard way of multichannel interleaving
32 *                order. In other words, it is tough to break.
33 *
34 * The Good:
35 * + very generic and compact, provided that the supplied matrix map is in a
36 *   sane form.
37 * + should be fast enough.
38 *
39 * The Bad:
40 * + somebody might disagree with it.
41 * + 'matrix' is kind of 0x7a69, due to prolong mental block.
42 */
43
44#ifdef _KERNEL
45#ifdef HAVE_KERNEL_OPTION_HEADERS
46#include "opt_snd.h"
47#endif
48#include <dev/sound/pcm/sound.h>
49#include <dev/sound/pcm/pcm.h>
50#include "feeder_if.h"
51
52#define SND_USE_FXDIV
53#include "snd_fxdiv_gen.h"
54
55SND_DECLARE_FILE("$FreeBSD: head/sys/dev/sound/pcm/feeder_matrix.c 193640 2009-06-07 19:12:08Z ariff $");
56#endif
57
58#define FEEDMATRIX_RESERVOIR	(SND_CHN_MAX * PCM_32_BPS)
59
60#define SND_CHN_T_EOF		0x00e0fe0f
61#define SND_CHN_T_NULL		0x0e0e0e0e
62
63struct feed_matrix_info;
64
65typedef void (*feed_matrix_t)(struct feed_matrix_info *, uint8_t *,
66    uint8_t *, uint32_t);
67
68struct feed_matrix_info {
69	uint32_t bps;
70	uint32_t ialign, oalign;
71	uint32_t in, out;
72	feed_matrix_t apply;
73#ifdef FEEDMATRIX_GENERIC
74	intpcm_read_t *rd;
75	intpcm_write_t *wr;
76#endif
77	struct {
78		int chn[SND_CHN_T_MAX + 1];
79		int mul, shift;
80	} matrix[SND_CHN_T_MAX + 1];
81	uint8_t reservoir[FEEDMATRIX_RESERVOIR];
82};
83
84static struct pcmchan_matrix feeder_matrix_maps[SND_CHN_MATRIX_MAX] = {
85	[SND_CHN_MATRIX_1_0] = SND_CHN_MATRIX_MAP_1_0,
86	[SND_CHN_MATRIX_2_0] = SND_CHN_MATRIX_MAP_2_0,
87	[SND_CHN_MATRIX_2_1] = SND_CHN_MATRIX_MAP_2_1,
88	[SND_CHN_MATRIX_3_0] = SND_CHN_MATRIX_MAP_3_0,
89	[SND_CHN_MATRIX_4_0] = SND_CHN_MATRIX_MAP_4_0,
90	[SND_CHN_MATRIX_4_1] = SND_CHN_MATRIX_MAP_4_1,
91	[SND_CHN_MATRIX_5_0] = SND_CHN_MATRIX_MAP_5_0,
92	[SND_CHN_MATRIX_5_1] = SND_CHN_MATRIX_MAP_5_1,
93	[SND_CHN_MATRIX_6_0] = SND_CHN_MATRIX_MAP_6_0,
94	[SND_CHN_MATRIX_6_1] = SND_CHN_MATRIX_MAP_6_1,
95	[SND_CHN_MATRIX_7_1] = SND_CHN_MATRIX_MAP_7_1
96};
97
98static int feeder_matrix_default_ids[9] = {
99	[0] = SND_CHN_MATRIX_UNKNOWN,
100	[1] = SND_CHN_MATRIX_1,
101	[2] = SND_CHN_MATRIX_2,
102	[3] = SND_CHN_MATRIX_3,
103	[4] = SND_CHN_MATRIX_4,
104	[5] = SND_CHN_MATRIX_5,
105	[6] = SND_CHN_MATRIX_6,
106	[7] = SND_CHN_MATRIX_7,
107	[8] = SND_CHN_MATRIX_8
108};
109
110#ifdef _KERNEL
111#define FEEDMATRIX_CLIP_CHECK(...)
112#else
113#define FEEDMATRIX_CLIP_CHECK(v, BIT)	do {				\
114	if ((v) < PCM_S##BIT##_MIN || (v) > PCM_S##BIT##_MAX)		\
115	    errx(1, "\n\n%s(): Sample clipping: %jd\n",			\
116		__func__, (intmax_t)(v));				\
117} while (0)
118#endif
119
120#define FEEDMATRIX_DECLARE(SIGN, BIT, ENDIAN)				\
121static void								\
122feed_matrix_##SIGN##BIT##ENDIAN(struct feed_matrix_info *info,		\
123    uint8_t *src, uint8_t *dst, uint32_t count)				\
124{									\
125	intpcm64_t accum;						\
126	intpcm_t v;							\
127	int i, j;							\
128									\
129	do {								\
130		for (i = 0; info->matrix[i].chn[0] != SND_CHN_T_EOF;	\
131		    i++) {						\
132			if (info->matrix[i].chn[0] == SND_CHN_T_NULL) {	\
133				_PCM_WRITE_##SIGN##BIT##_##ENDIAN(dst,	\
134				    0);					\
135				dst += PCM_##BIT##_BPS;			\
136				continue;				\
137			} else if (info->matrix[i].chn[1] ==		\
138			    SND_CHN_T_EOF) {				\
139				v = _PCM_READ_##SIGN##BIT##_##ENDIAN(	\
140				    src + info->matrix[i].chn[0]);	\
141				_PCM_WRITE_##SIGN##BIT##_##ENDIAN(dst,	\
142				    v);					\
143				dst += PCM_##BIT##_BPS;			\
144				continue;				\
145			}						\
146									\
147			accum = 0;					\
148			for (j = 0;					\
149			    info->matrix[i].chn[j] != SND_CHN_T_EOF;	\
150			    j++) {					\
151				v = _PCM_READ_##SIGN##BIT##_##ENDIAN(	\
152				    src + info->matrix[i].chn[j]);	\
153				accum += v;				\
154			}						\
155									\
156			accum = (accum * info->matrix[i].mul) >>	\
157			    info->matrix[i].shift;			\
158									\
159			FEEDMATRIX_CLIP_CHECK(accum, BIT);		\
160									\
161			v = (accum > PCM_S##BIT##_MAX) ?		\
162			    PCM_S##BIT##_MAX :				\
163			    ((accum < PCM_S##BIT##_MIN) ?		\
164			    PCM_S##BIT##_MIN :				\
165			    accum);					\
166			_PCM_WRITE_##SIGN##BIT##_##ENDIAN(dst, v);	\
167			dst += PCM_##BIT##_BPS;				\
168		}							\
169		src += info->ialign;					\
170	} while (--count != 0);						\
171}
172
173#if BYTE_ORDER == LITTLE_ENDIAN || defined(SND_FEEDER_MULTIFORMAT)
174FEEDMATRIX_DECLARE(S, 16, LE)
175FEEDMATRIX_DECLARE(S, 32, LE)
176#endif
177#if BYTE_ORDER == BIG_ENDIAN || defined(SND_FEEDER_MULTIFORMAT)
178FEEDMATRIX_DECLARE(S, 16, BE)
179FEEDMATRIX_DECLARE(S, 32, BE)
180#endif
181#ifdef SND_FEEDER_MULTIFORMAT
182FEEDMATRIX_DECLARE(S,  8, NE)
183FEEDMATRIX_DECLARE(S, 24, LE)
184FEEDMATRIX_DECLARE(S, 24, BE)
185FEEDMATRIX_DECLARE(U,  8, NE)
186FEEDMATRIX_DECLARE(U, 16, LE)
187FEEDMATRIX_DECLARE(U, 24, LE)
188FEEDMATRIX_DECLARE(U, 32, LE)
189FEEDMATRIX_DECLARE(U, 16, BE)
190FEEDMATRIX_DECLARE(U, 24, BE)
191FEEDMATRIX_DECLARE(U, 32, BE)
192#endif
193
194#define FEEDMATRIX_ENTRY(SIGN, BIT, ENDIAN)				\
195	{								\
196		AFMT_##SIGN##BIT##_##ENDIAN,				\
197		feed_matrix_##SIGN##BIT##ENDIAN				\
198	}
199
200static const struct {
201	uint32_t format;
202	feed_matrix_t apply;
203} feed_matrix_tab[] = {
204#if BYTE_ORDER == LITTLE_ENDIAN || defined(SND_FEEDER_MULTIFORMAT)
205	FEEDMATRIX_ENTRY(S, 16, LE),
206	FEEDMATRIX_ENTRY(S, 32, LE),
207#endif
208#if BYTE_ORDER == BIG_ENDIAN || defined(SND_FEEDER_MULTIFORMAT)
209	FEEDMATRIX_ENTRY(S, 16, BE),
210	FEEDMATRIX_ENTRY(S, 32, BE),
211#endif
212#ifdef SND_FEEDER_MULTIFORMAT
213	FEEDMATRIX_ENTRY(S,  8, NE),
214	FEEDMATRIX_ENTRY(S, 24, LE),
215	FEEDMATRIX_ENTRY(S, 24, BE),
216	FEEDMATRIX_ENTRY(U,  8, NE),
217	FEEDMATRIX_ENTRY(U, 16, LE),
218	FEEDMATRIX_ENTRY(U, 24, LE),
219	FEEDMATRIX_ENTRY(U, 32, LE),
220	FEEDMATRIX_ENTRY(U, 16, BE),
221	FEEDMATRIX_ENTRY(U, 24, BE),
222	FEEDMATRIX_ENTRY(U, 32, BE)
223#endif
224};
225
226static void
227feed_matrix_reset(struct feed_matrix_info *info)
228{
229	uint32_t i, j;
230
231	for (i = 0; i < (sizeof(info->matrix) / sizeof(info->matrix[0])); i++) {
232		for (j = 0;
233		    j < (sizeof(info->matrix[i].chn) /
234		    sizeof(info->matrix[i].chn[0])); j++) {
235			info->matrix[i].chn[j] = SND_CHN_T_EOF;
236		}
237		info->matrix[i].mul   = 1;
238		info->matrix[i].shift = 0;
239	}
240}
241
242#ifdef FEEDMATRIX_GENERIC
243static void
244feed_matrix_apply_generic(struct feed_matrix_info *info,
245    uint8_t *src, uint8_t *dst, uint32_t count)
246{
247	intpcm64_t accum;
248	intpcm_t v;
249	int i, j;
250
251	do {
252		for (i = 0; info->matrix[i].chn[0] != SND_CHN_T_EOF;
253		    i++) {
254			if (info->matrix[i].chn[0] == SND_CHN_T_NULL) {
255				info->wr(dst, 0);
256				dst += info->bps;
257				continue;
258			} else if (info->matrix[i].chn[1] ==
259			    SND_CHN_T_EOF) {
260				v = info->rd(src + info->matrix[i].chn[0]);
261				info->wr(dst, v);
262				dst += info->bps;
263				continue;
264			}
265
266			accum = 0;
267			for (j = 0;
268			    info->matrix[i].chn[j] != SND_CHN_T_EOF;
269			    j++) {
270				v = info->rd(src + info->matrix[i].chn[j]);
271				accum += v;
272			}
273
274			accum = (accum * info->matrix[i].mul) >>
275			    info->matrix[i].shift;
276
277			FEEDMATRIX_CLIP_CHECK(accum, 32);
278
279			v = (accum > PCM_S32_MAX) ? PCM_S32_MAX :
280			    ((accum < PCM_S32_MIN) ? PCM_S32_MIN : accum);
281			info->wr(dst, v);
282			dst += info->bps;
283		}
284		src += info->ialign;
285	} while (--count != 0);
286}
287#endif
288
289static int
290feed_matrix_setup(struct feed_matrix_info *info, struct pcmchan_matrix *m_in,
291    struct pcmchan_matrix *m_out)
292{
293	uint32_t i, j, ch, in_mask, merge_mask;
294	int mul, shift;
295
296
297	if (info == NULL || m_in == NULL || m_out == NULL ||
298	    AFMT_CHANNEL(info->in) != m_in->channels ||
299	    AFMT_CHANNEL(info->out) != m_out->channels ||
300	    m_in->channels < SND_CHN_MIN || m_in->channels > SND_CHN_MAX ||
301	    m_out->channels < SND_CHN_MIN || m_out->channels > SND_CHN_MAX)
302		return (EINVAL);
303
304	feed_matrix_reset(info);
305
306	/*
307	 * If both in and out are part of standard matrix and identical, skip
308	 * everything alltogether.
309	 */
310	if (m_in->id == m_out->id && !(m_in->id < SND_CHN_MATRIX_BEGIN ||
311	    m_in->id > SND_CHN_MATRIX_END))
312		return (0);
313
314	/*
315	 * Special case for mono input matrix. If the output supports
316	 * possible 'center' channel, route it there. Otherwise, let it be
317	 * matrixed to left/right.
318	 */
319	if (m_in->id == SND_CHN_MATRIX_1_0) {
320		if (m_out->id == SND_CHN_MATRIX_1_0)
321			in_mask = SND_CHN_T_MASK_FL;
322		else if (m_out->mask & SND_CHN_T_MASK_FC)
323			in_mask = SND_CHN_T_MASK_FC;
324		else
325			in_mask = SND_CHN_T_MASK_FL | SND_CHN_T_MASK_FR;
326	} else
327		in_mask = m_in->mask;
328
329	/* Merge, reduce, expand all possibilites. */
330	for (ch = SND_CHN_T_BEGIN; ch <= SND_CHN_T_END &&
331	    m_out->map[ch].type != SND_CHN_T_MAX; ch += SND_CHN_T_STEP) {
332		merge_mask = m_out->map[ch].members & in_mask;
333		if (merge_mask == 0) {
334			info->matrix[ch].chn[0] = SND_CHN_T_NULL;
335			continue;
336		}
337
338		j = 0;
339		for (i = SND_CHN_T_BEGIN; i <= SND_CHN_T_END;
340		    i += SND_CHN_T_STEP) {
341			if (merge_mask & (1 << i)) {
342				if (m_in->offset[i] >= 0 &&
343				    m_in->offset[i] < (int)m_in->channels)
344					info->matrix[ch].chn[j++] =
345					    m_in->offset[i] * info->bps;
346				else {
347					info->matrix[ch].chn[j++] =
348					    SND_CHN_T_EOF;
349					break;
350				}
351			}
352		}
353
354#define FEEDMATRIX_ATTN_SHIFT	16
355
356		if (j > 1) {
357			/*
358			 * XXX For channel that require accumulation from
359			 * multiple channels, apply a slight attenuation to
360			 * avoid clipping.
361			 */
362			mul   = (1 << (FEEDMATRIX_ATTN_SHIFT - 1)) + 143 - j;
363			shift = FEEDMATRIX_ATTN_SHIFT;
364			while ((mul & 1) == 0 && shift > 0) {
365				mul >>= 1;
366				shift--;
367			}
368			info->matrix[ch].mul   = mul;
369			info->matrix[ch].shift = shift;
370		}
371	}
372
373#ifndef _KERNEL
374	fprintf(stderr, "Total: %d\n", ch);
375
376	for (i = 0; info->matrix[i].chn[0] != SND_CHN_T_EOF; i++) {
377		fprintf(stderr, "%d: [", i);
378		for (j = 0; info->matrix[i].chn[j] != SND_CHN_T_EOF; j++) {
379			if (j != 0)
380				fprintf(stderr, ", ");
381			fprintf(stderr, "%d",
382			    (info->matrix[i].chn[j] == SND_CHN_T_NULL) ?
383			    0xffffffff : info->matrix[i].chn[j] / info->bps);
384		}
385		fprintf(stderr, "] attn: (x * %d) >> %d\n",
386		    info->matrix[i].mul, info->matrix[i].shift);
387	}
388#endif
389
390	return (0);
391}
392
393static int
394feed_matrix_init(struct pcm_feeder *f)
395{
396	struct feed_matrix_info *info;
397	struct pcmchan_matrix *m_in, *m_out;
398	uint32_t i;
399	int ret;
400
401	if (AFMT_ENCODING(f->desc->in) != AFMT_ENCODING(f->desc->out))
402		return (EINVAL);
403
404	info = malloc(sizeof(*info), M_DEVBUF, M_NOWAIT | M_ZERO);
405	if (info == NULL)
406		return (ENOMEM);
407
408	info->in = f->desc->in;
409	info->out = f->desc->out;
410	info->bps = AFMT_BPS(info->in);
411	info->ialign = AFMT_ALIGN(info->in);
412	info->oalign = AFMT_ALIGN(info->out);
413	info->apply = NULL;
414
415	for (i = 0; info->apply == NULL &&
416	    i < (sizeof(feed_matrix_tab) / sizeof(feed_matrix_tab[0])); i++) {
417		if (AFMT_ENCODING(info->in) == feed_matrix_tab[i].format)
418			info->apply = feed_matrix_tab[i].apply;
419	}
420
421	if (info->apply == NULL) {
422#ifdef FEEDMATRIX_GENERIC
423		info->rd = feeder_format_read_op(info->in);
424		info->wr = feeder_format_write_op(info->out);
425		if (info->rd == NULL || info->wr == NULL) {
426			free(info, M_DEVBUF);
427			return (EINVAL);
428		}
429		info->apply = feed_matrix_apply_generic;
430#else
431		free(info, M_DEVBUF);
432		return (EINVAL);
433#endif
434	}
435
436	m_in  = feeder_matrix_format_map(info->in);
437	m_out = feeder_matrix_format_map(info->out);
438
439	ret = feed_matrix_setup(info, m_in, m_out);
440	if (ret != 0) {
441		free(info, M_DEVBUF);
442		return (ret);
443	}
444
445	f->data = info;
446
447	return (0);
448}
449
450static int
451feed_matrix_free(struct pcm_feeder *f)
452{
453	struct feed_matrix_info *info;
454
455	info = f->data;
456	if (info != NULL)
457		free(info, M_DEVBUF);
458
459	f->data = NULL;
460
461	return (0);
462}
463
464static int
465feed_matrix_feed(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b,
466    uint32_t count, void *source)
467{
468	struct feed_matrix_info *info;
469	uint32_t j, inmax;
470	uint8_t *src, *dst;
471
472	info = f->data;
473	if (info->matrix[0].chn[0] == SND_CHN_T_EOF)
474		return (FEEDER_FEED(f->source, c, b, count, source));
475
476	dst = b;
477	count = SND_FXROUND(count, info->oalign);
478	inmax = info->ialign + info->oalign;
479
480	/*
481	 * This loop might look simmilar to other feeder_* loops, but be
482	 * advised: matrixing might involve overlapping (think about
483	 * swapping end to front or something like that). In this regard it
484	 * might be simmilar to feeder_format, but feeder_format works on
485	 * 'sample' domain where it can be fitted into single 32bit integer
486	 * while matrixing works on 'sample frame' domain.
487	 */
488	do {
489		if (count < info->oalign)
490			break;
491
492		if (count < inmax) {
493			src = info->reservoir;
494			j = info->ialign;
495		} else {
496			if (info->ialign == info->oalign)
497				j = count - info->oalign;
498			else if (info->ialign > info->oalign)
499				j = SND_FXROUND(count - info->oalign,
500				    info->ialign);
501			else
502				j = (SND_FXDIV(count, info->oalign) - 1) *
503				    info->ialign;
504			src = dst + count - j;
505		}
506
507		j = SND_FXDIV(FEEDER_FEED(f->source, c, src, j, source),
508		    info->ialign);
509		if (j == 0)
510			break;
511
512		info->apply(info, src, dst, j);
513
514		j *= info->oalign;
515		dst += j;
516		count -= j;
517
518	} while (count != 0);
519
520	return (dst - b);
521}
522
523static struct pcm_feederdesc feeder_matrix_desc[] = {
524	{ FEEDER_MATRIX, 0, 0, 0, 0 },
525	{ 0, 0, 0, 0, 0 }
526};
527
528static kobj_method_t feeder_matrix_methods[] = {
529	KOBJMETHOD(feeder_init,		feed_matrix_init),
530	KOBJMETHOD(feeder_free,		feed_matrix_free),
531	KOBJMETHOD(feeder_feed,		feed_matrix_feed),
532	KOBJMETHOD_END
533};
534
535FEEDER_DECLARE(feeder_matrix, NULL);
536
537/* External */
538int
539feeder_matrix_setup(struct pcm_feeder *f, struct pcmchan_matrix *m_in,
540    struct pcmchan_matrix *m_out)
541{
542
543	if (f == NULL || f->desc == NULL || f->desc->type != FEEDER_MATRIX ||
544	    f->data == NULL)
545		return (EINVAL);
546
547	return (feed_matrix_setup(f->data, m_in, m_out));
548}
549
550/*
551 * feeder_matrix_default_id(): For a given number of channels, return
552 *                             default prefered id (example: both 5.1 and
553 *                             6.0 are simply 6 channels, but 5.1 is more
554 *                             preferable).
555 */
556int
557feeder_matrix_default_id(uint32_t ch)
558{
559
560	if (ch < feeder_matrix_maps[SND_CHN_MATRIX_BEGIN].channels ||
561	    ch > feeder_matrix_maps[SND_CHN_MATRIX_END].channels)
562		return (SND_CHN_MATRIX_UNKNOWN);
563
564	return (feeder_matrix_maps[feeder_matrix_default_ids[ch]].id);
565}
566
567/*
568 * feeder_matrix_default_channel_map(): Ditto, but return matrix map
569 *                                      instead.
570 */
571struct pcmchan_matrix *
572feeder_matrix_default_channel_map(uint32_t ch)
573{
574
575	if (ch < feeder_matrix_maps[SND_CHN_MATRIX_BEGIN].channels ||
576	    ch > feeder_matrix_maps[SND_CHN_MATRIX_END].channels)
577		return (NULL);
578
579	return (&feeder_matrix_maps[feeder_matrix_default_ids[ch]]);
580}
581
582/*
583 * feeder_matrix_default_format(): For a given audio format, return the
584 *                                 proper audio format based on preferable
585 *                                 matrix.
586 */
587uint32_t
588feeder_matrix_default_format(uint32_t format)
589{
590	struct pcmchan_matrix *m;
591	uint32_t i, ch, ext;
592
593	ch = AFMT_CHANNEL(format);
594	ext = AFMT_EXTCHANNEL(format);
595
596	if (ext != 0) {
597		for (i = SND_CHN_MATRIX_BEGIN; i <= SND_CHN_MATRIX_END; i++) {
598			if (feeder_matrix_maps[i].channels == ch &&
599			    feeder_matrix_maps[i].ext == ext)
600			return (SND_FORMAT(format, ch, ext));
601		}
602	}
603
604	m = feeder_matrix_default_channel_map(ch);
605	if (m == NULL)
606		return (0x00000000);
607
608	return (SND_FORMAT(format, ch, m->ext));
609}
610
611/*
612 * feeder_matrix_format_id(): For a given audio format, return its matrix
613 *                            id.
614 */
615int
616feeder_matrix_format_id(uint32_t format)
617{
618	uint32_t i, ch, ext;
619
620	ch = AFMT_CHANNEL(format);
621	ext = AFMT_EXTCHANNEL(format);
622
623	for (i = SND_CHN_MATRIX_BEGIN; i <= SND_CHN_MATRIX_END; i++) {
624		if (feeder_matrix_maps[i].channels == ch &&
625		    feeder_matrix_maps[i].ext == ext)
626			return (feeder_matrix_maps[i].id);
627	}
628
629	return (SND_CHN_MATRIX_UNKNOWN);
630}
631
632/*
633 * feeder_matrix_format_map(): For a given audio format, return its matrix
634 *                             map.
635 */
636struct pcmchan_matrix *
637feeder_matrix_format_map(uint32_t format)
638{
639	uint32_t i, ch, ext;
640
641	ch = AFMT_CHANNEL(format);
642	ext = AFMT_EXTCHANNEL(format);
643
644	for (i = SND_CHN_MATRIX_BEGIN; i <= SND_CHN_MATRIX_END; i++) {
645		if (feeder_matrix_maps[i].channels == ch &&
646		    feeder_matrix_maps[i].ext == ext)
647			return (&feeder_matrix_maps[i]);
648	}
649
650	return (NULL);
651}
652
653/*
654 * feeder_matrix_id_map(): For a given matrix id, return its matrix map.
655 */
656struct pcmchan_matrix *
657feeder_matrix_id_map(int id)
658{
659
660	if (id < SND_CHN_MATRIX_BEGIN || id > SND_CHN_MATRIX_END)
661		return (NULL);
662
663	return (&feeder_matrix_maps[id]);
664}
665
666/*
667 * feeder_matrix_compare(): Compare the simmilarities of matrices.
668 */
669int
670feeder_matrix_compare(struct pcmchan_matrix *m_in, struct pcmchan_matrix *m_out)
671{
672	uint32_t i;
673
674	if (m_in == m_out)
675		return (0);
676
677	if (m_in->channels != m_out->channels || m_in->ext != m_out->ext ||
678	    m_in->mask != m_out->mask)
679		return (1);
680
681	for (i = 0; i < (sizeof(m_in->map) / sizeof(m_in->map[0])); i++) {
682		if (m_in->map[i].type != m_out->map[i].type)
683			return (1);
684		if (m_in->map[i].type == SND_CHN_T_MAX)
685			break;
686		if (m_in->map[i].members != m_out->map[i].members)
687			return (1);
688		if (i <= SND_CHN_T_END) {
689			if (m_in->offset[m_in->map[i].type] !=
690			    m_out->offset[m_out->map[i].type])
691				return (1);
692		}
693	}
694
695	return (0);
696}
697
698/*
699 * XXX 4front intepretation of "surround" is ambigous and sort of
700 *     conflicting with "rear"/"back". Map it to "side". Well..
701 *     who cares?
702 */
703static int snd_chn_to_oss[SND_CHN_T_MAX] = {
704	[SND_CHN_T_FL] = CHID_L,
705	[SND_CHN_T_FR] = CHID_R,
706	[SND_CHN_T_FC] = CHID_C,
707	[SND_CHN_T_LF] = CHID_LFE,
708	[SND_CHN_T_SL] = CHID_LS,
709	[SND_CHN_T_SR] = CHID_RS,
710	[SND_CHN_T_BL] = CHID_LR,
711	[SND_CHN_T_BR] = CHID_RR
712};
713
714#define SND_CHN_OSS_VALIDMASK						\
715			(SND_CHN_T_MASK_FL | SND_CHN_T_MASK_FR |	\
716			 SND_CHN_T_MASK_FC | SND_CHN_T_MASK_LF |	\
717			 SND_CHN_T_MASK_SL | SND_CHN_T_MASK_SR |	\
718			 SND_CHN_T_MASK_BL | SND_CHN_T_MASK_BR)
719
720#define SND_CHN_OSS_MAX		8
721#define SND_CHN_OSS_BEGIN	CHID_L
722#define SND_CHN_OSS_END		CHID_RR
723
724static int oss_to_snd_chn[SND_CHN_OSS_END + 1] = {
725	[CHID_L]   = SND_CHN_T_FL,
726	[CHID_R]   = SND_CHN_T_FR,
727	[CHID_C]   = SND_CHN_T_FC,
728	[CHID_LFE] = SND_CHN_T_LF,
729	[CHID_LS]  = SND_CHN_T_SL,
730	[CHID_RS]  = SND_CHN_T_SR,
731	[CHID_LR]  = SND_CHN_T_BL,
732	[CHID_RR]  = SND_CHN_T_BR
733};
734
735/*
736 * Used by SNDCTL_DSP_GET_CHNORDER.
737 */
738int
739feeder_matrix_oss_get_channel_order(struct pcmchan_matrix *m,
740    unsigned long long *map)
741{
742	unsigned long long tmpmap;
743	uint32_t i;
744
745	if (m == NULL || map == NULL || (m->mask & ~SND_CHN_OSS_VALIDMASK) ||
746	    m->channels > SND_CHN_OSS_MAX)
747		return (EINVAL);
748
749	tmpmap = 0x0000000000000000ULL;
750
751	for (i = 0; m->map[i].type != SND_CHN_T_MAX &&
752	    i < SND_CHN_OSS_MAX; i++) {
753		if ((1 << m->map[i].type) & ~SND_CHN_OSS_VALIDMASK)
754			return (EINVAL);
755		tmpmap |=
756		    (unsigned long long)snd_chn_to_oss[m->map[i].type] <<
757		    (i * 4);
758	}
759
760	*map = tmpmap;
761
762	return (0);
763}
764
765/*
766 * Used by SNDCTL_DSP_SET_CHNORDER.
767 */
768int
769feeder_matrix_oss_set_channel_order(struct pcmchan_matrix *m,
770    unsigned long long *map)
771{
772	struct pcmchan_matrix tmp;
773	uint32_t chmask, i;
774	int ch, cheof;
775
776	if (m == NULL || map == NULL || (m->mask & ~SND_CHN_OSS_VALIDMASK) ||
777	    m->channels > SND_CHN_OSS_MAX || (*map & 0xffffffff00000000ULL))
778		return (EINVAL);
779
780	tmp = *m;
781	tmp.channels = 0;
782	tmp.ext = 0;
783	tmp.mask = 0;
784	memset(tmp.offset, -1, sizeof(tmp.offset));
785	cheof = 0;
786
787	for (i = 0; i < SND_CHN_OSS_MAX; i++) {
788		ch = (*map >> (i * 4)) & 0xf;
789		if (ch < SND_CHN_OSS_BEGIN) {
790			if (cheof == 0 && m->map[i].type != SND_CHN_T_MAX)
791				return (EINVAL);
792			cheof++;
793			tmp.map[i] = m->map[i];
794			continue;
795		} else if (ch > SND_CHN_OSS_END)
796			return (EINVAL);
797		else if (cheof != 0)
798			return (EINVAL);
799		ch = oss_to_snd_chn[ch];
800		chmask = 1 << ch;
801		/* channel not exist in matrix */
802		if (!(chmask & m->mask))
803			return (EINVAL);
804		/* duplicated channel */
805		if (chmask & tmp.mask)
806			return (EINVAL);
807		tmp.map[i] = m->map[m->offset[ch]];
808		if (tmp.map[i].type != ch)
809			return (EINVAL);
810		tmp.offset[ch] = i;
811		tmp.mask |= chmask;
812		tmp.channels++;
813		if (chmask & SND_CHN_T_MASK_LF)
814			tmp.ext++;
815	}
816
817	if (tmp.channels != m->channels || tmp.ext != m->ext ||
818	    tmp.mask != m->mask ||
819	    tmp.map[m->channels].type != SND_CHN_T_MAX)
820		return (EINVAL);
821
822	*m = tmp;
823
824	return (0);
825}
826