xref: /onnv-gate/usr/src/uts/common/io/audio/impl/audio_output.c (revision 12165:e481916a5729)
19484Sgarrett.damore@Sun.COM /*
29484Sgarrett.damore@Sun.COM  * CDDL HEADER START
39484Sgarrett.damore@Sun.COM  *
49484Sgarrett.damore@Sun.COM  * The contents of this file are subject to the terms of the
59484Sgarrett.damore@Sun.COM  * Common Development and Distribution License (the "License").
69484Sgarrett.damore@Sun.COM  * You may not use this file except in compliance with the License.
79484Sgarrett.damore@Sun.COM  *
89484Sgarrett.damore@Sun.COM  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
99484Sgarrett.damore@Sun.COM  * or http://www.opensolaris.org/os/licensing.
109484Sgarrett.damore@Sun.COM  * See the License for the specific language governing permissions
119484Sgarrett.damore@Sun.COM  * and limitations under the License.
129484Sgarrett.damore@Sun.COM  *
139484Sgarrett.damore@Sun.COM  * When distributing Covered Code, include this CDDL HEADER in each
149484Sgarrett.damore@Sun.COM  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
159484Sgarrett.damore@Sun.COM  * If applicable, add the following below this CDDL HEADER, with the
169484Sgarrett.damore@Sun.COM  * fields enclosed by brackets "[]" replaced with your own identifying
179484Sgarrett.damore@Sun.COM  * information: Portions Copyright [yyyy] [name of copyright owner]
189484Sgarrett.damore@Sun.COM  *
199484Sgarrett.damore@Sun.COM  * CDDL HEADER END
209484Sgarrett.damore@Sun.COM  */
219484Sgarrett.damore@Sun.COM /*
229484Sgarrett.damore@Sun.COM  * Copyright (C) 4Front Technologies 1996-2008.
239484Sgarrett.damore@Sun.COM  *
24*12165Sgdamore@opensolaris.org  * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
259484Sgarrett.damore@Sun.COM  */
269484Sgarrett.damore@Sun.COM 
279484Sgarrett.damore@Sun.COM /*
289484Sgarrett.damore@Sun.COM  * Purpose: Virtual mixing audio output routines
299484Sgarrett.damore@Sun.COM  *
309484Sgarrett.damore@Sun.COM  * This file contains the actual mixing and resampling engine for output.
319484Sgarrett.damore@Sun.COM  */
329484Sgarrett.damore@Sun.COM 
339484Sgarrett.damore@Sun.COM #include <sys/ddi.h>
349484Sgarrett.damore@Sun.COM #include <sys/sunddi.h>
359484Sgarrett.damore@Sun.COM #include <sys/sysmacros.h>
369484Sgarrett.damore@Sun.COM #include "audio_impl.h"
379484Sgarrett.damore@Sun.COM 
389484Sgarrett.damore@Sun.COM #define	DECL_AUDIO_EXPORT(NAME, TYPE, SAMPLE)				\
399484Sgarrett.damore@Sun.COM void									\
4011936Sgdamore@opensolaris.org auimpl_export_##NAME(audio_engine_t *eng, uint_t nfr, uint_t froff)	\
419484Sgarrett.damore@Sun.COM {									\
429484Sgarrett.damore@Sun.COM 	int		nch = eng->e_nchan;				\
4311936Sgdamore@opensolaris.org 	uint_t		hidx = eng->e_hidx;				\
449484Sgarrett.damore@Sun.COM 	TYPE		*out = (void *)eng->e_data;			\
459484Sgarrett.damore@Sun.COM 	int		ch = 0;						\
469484Sgarrett.damore@Sun.COM 									\
479484Sgarrett.damore@Sun.COM 	do {	/* for each channel */					\
489484Sgarrett.damore@Sun.COM 		int32_t *ip;						\
499484Sgarrett.damore@Sun.COM 		TYPE	*op;						\
509484Sgarrett.damore@Sun.COM 		int	i;						\
519484Sgarrett.damore@Sun.COM 		int	incr = eng->e_chincr[ch];			\
529484Sgarrett.damore@Sun.COM 									\
539484Sgarrett.damore@Sun.COM 		/* get value and adjust next channel offset */		\
549484Sgarrett.damore@Sun.COM 		op = out + eng->e_choffs[ch] + (hidx * incr);		\
559484Sgarrett.damore@Sun.COM 		ip = eng->e_chbufs[ch];					\
5611936Sgdamore@opensolaris.org 		ip += froff;						\
579484Sgarrett.damore@Sun.COM 									\
5811936Sgdamore@opensolaris.org 		i = nfr;						\
599484Sgarrett.damore@Sun.COM 									\
609484Sgarrett.damore@Sun.COM 		do {	/* for each frame */				\
619484Sgarrett.damore@Sun.COM 			int32_t sample = *ip;				\
629484Sgarrett.damore@Sun.COM 									\
639484Sgarrett.damore@Sun.COM 			*op = SAMPLE;					\
649484Sgarrett.damore@Sun.COM 			op += incr;					\
659484Sgarrett.damore@Sun.COM 			ip++;						\
669484Sgarrett.damore@Sun.COM 									\
679484Sgarrett.damore@Sun.COM 		} while (--i);						\
689484Sgarrett.damore@Sun.COM 									\
699484Sgarrett.damore@Sun.COM 		ch++;							\
709484Sgarrett.damore@Sun.COM 	} while (ch < nch);						\
719484Sgarrett.damore@Sun.COM }
729484Sgarrett.damore@Sun.COM 
739484Sgarrett.damore@Sun.COM DECL_AUDIO_EXPORT(16ne, int16_t, sample >> 8)
749484Sgarrett.damore@Sun.COM DECL_AUDIO_EXPORT(16oe, int16_t, ddi_swap16(sample >> 8))
759484Sgarrett.damore@Sun.COM DECL_AUDIO_EXPORT(32ne, int32_t, sample << 8)
769484Sgarrett.damore@Sun.COM DECL_AUDIO_EXPORT(32oe, int32_t, ddi_swap32(sample << 8))
779484Sgarrett.damore@Sun.COM DECL_AUDIO_EXPORT(24ne, int32_t, sample)
789484Sgarrett.damore@Sun.COM DECL_AUDIO_EXPORT(24oe, int32_t, ddi_swap32(sample))
799484Sgarrett.damore@Sun.COM 
809484Sgarrett.damore@Sun.COM /*
819484Sgarrett.damore@Sun.COM  * Simple limiter to prevent overflows when using fixed point computations
829484Sgarrett.damore@Sun.COM  */
839484Sgarrett.damore@Sun.COM static void
auimpl_output_limiter(audio_engine_t * eng)849484Sgarrett.damore@Sun.COM auimpl_output_limiter(audio_engine_t *eng)
859484Sgarrett.damore@Sun.COM {
869484Sgarrett.damore@Sun.COM 	int k, t;
8711936Sgdamore@opensolaris.org 	uint_t q, amp, amp2;
889484Sgarrett.damore@Sun.COM 	int nchan = eng->e_nchan;
8911936Sgdamore@opensolaris.org 	uint_t fragfr = eng->e_fragfr;
909484Sgarrett.damore@Sun.COM 	int32_t **chbufs = eng->e_chbufs;
9111936Sgdamore@opensolaris.org 	uint_t statevar = eng->e_limiter_state;
929484Sgarrett.damore@Sun.COM 
939484Sgarrett.damore@Sun.COM 	for (t = 0; t < fragfr; t++) {
949484Sgarrett.damore@Sun.COM 
9511936Sgdamore@opensolaris.org 		amp = (uint_t)ABS(chbufs[0][t]);
969484Sgarrett.damore@Sun.COM 
979484Sgarrett.damore@Sun.COM 		for (k = 1; k < nchan; k++)	{
9811936Sgdamore@opensolaris.org 			amp2 = (uint_t)ABS(chbufs[k][t]);
999484Sgarrett.damore@Sun.COM 			if (amp2 > amp)
1009484Sgarrett.damore@Sun.COM 				amp = amp2;
1019484Sgarrett.damore@Sun.COM 		}
1029484Sgarrett.damore@Sun.COM 
1039484Sgarrett.damore@Sun.COM 		amp >>= 8;
1049484Sgarrett.damore@Sun.COM 		q = 0x10000;
1059484Sgarrett.damore@Sun.COM 
1069484Sgarrett.damore@Sun.COM 		if (amp > 0x7FFF)
1079484Sgarrett.damore@Sun.COM 			q = 0x7FFF0000 / amp;
1089484Sgarrett.damore@Sun.COM 
1099484Sgarrett.damore@Sun.COM 		if (statevar > q) {
1109484Sgarrett.damore@Sun.COM 			statevar = q;
1119484Sgarrett.damore@Sun.COM 		} else {
1129484Sgarrett.damore@Sun.COM 			q = statevar;
1139484Sgarrett.damore@Sun.COM 
1149484Sgarrett.damore@Sun.COM 			/*
1159484Sgarrett.damore@Sun.COM 			 * Simplier (linear) tracking algo
1169484Sgarrett.damore@Sun.COM 			 * (gives less distortion, but more pumping)
1179484Sgarrett.damore@Sun.COM 			 */
1189484Sgarrett.damore@Sun.COM 			statevar += 2;
1199484Sgarrett.damore@Sun.COM 			if (statevar > 0x10000)
1209484Sgarrett.damore@Sun.COM 				statevar = 0x10000;
1219484Sgarrett.damore@Sun.COM 
1229484Sgarrett.damore@Sun.COM 			/*
1239484Sgarrett.damore@Sun.COM 			 * Classic tracking algo
1249484Sgarrett.damore@Sun.COM 			 * gives more distortion with no-lookahead
1259484Sgarrett.damore@Sun.COM 			 * statevar=0x10000-((0x10000-statevar)*0xFFF4>>16);
1269484Sgarrett.damore@Sun.COM 			 */
1279484Sgarrett.damore@Sun.COM 		}
1289484Sgarrett.damore@Sun.COM 
1299484Sgarrett.damore@Sun.COM 		for (k = 0; k < nchan; k++) {
1309484Sgarrett.damore@Sun.COM 			int32_t in = chbufs[k][t];
1319484Sgarrett.damore@Sun.COM 			int32_t out = 0;
13211936Sgdamore@opensolaris.org 			uint_t p;
1339484Sgarrett.damore@Sun.COM 
1349484Sgarrett.damore@Sun.COM 			if (in >= 0) {
1359484Sgarrett.damore@Sun.COM 				p = in;
1369484Sgarrett.damore@Sun.COM 				p = ((p & 0xFFFF) * (q >> 4) >> 12) +
1379484Sgarrett.damore@Sun.COM 				    (p >> 16) * q;
1389484Sgarrett.damore@Sun.COM 				out = p;
1399484Sgarrett.damore@Sun.COM 			} else {
1409484Sgarrett.damore@Sun.COM 				p = -in;
1419484Sgarrett.damore@Sun.COM 				p = ((p & 0xFFFF) * (q >> 4) >> 12) +
1429484Sgarrett.damore@Sun.COM 				    (p >> 16) * q;
1439484Sgarrett.damore@Sun.COM 				out = -p;
1449484Sgarrett.damore@Sun.COM 			}
1459484Sgarrett.damore@Sun.COM 			/* safety code */
1469484Sgarrett.damore@Sun.COM 			/*
1479484Sgarrett.damore@Sun.COM 			 * if output after limiter is clamped, then it
1489484Sgarrett.damore@Sun.COM 			 * can be dropped
1499484Sgarrett.damore@Sun.COM 			 */
1509484Sgarrett.damore@Sun.COM 			if (out > 0x7FFFFF)
1519484Sgarrett.damore@Sun.COM 				out = 0x7FFFFF;
1529484Sgarrett.damore@Sun.COM 			else if (out < -0x7FFFFF)
1539484Sgarrett.damore@Sun.COM 				out = -0x7FFFFF;
1549484Sgarrett.damore@Sun.COM 
1559484Sgarrett.damore@Sun.COM 			chbufs[k][t] = out;
1569484Sgarrett.damore@Sun.COM 		}
1579484Sgarrett.damore@Sun.COM 	}
1589484Sgarrett.damore@Sun.COM 
1599484Sgarrett.damore@Sun.COM 	eng->e_limiter_state = statevar;
1609484Sgarrett.damore@Sun.COM }
1619484Sgarrett.damore@Sun.COM 
1629484Sgarrett.damore@Sun.COM /*
1639484Sgarrett.damore@Sun.COM  * Output mixing function.  Assumption: all work is done in 24-bit native PCM.
1649484Sgarrett.damore@Sun.COM  */
1659484Sgarrett.damore@Sun.COM static void
auimpl_output_mix(audio_stream_t * sp,int offset,int nfr)1669484Sgarrett.damore@Sun.COM auimpl_output_mix(audio_stream_t *sp, int offset, int nfr)
1679484Sgarrett.damore@Sun.COM {
1689484Sgarrett.damore@Sun.COM 	audio_engine_t *eng = sp->s_engine;
1699484Sgarrett.damore@Sun.COM 	const int32_t *src;
1709484Sgarrett.damore@Sun.COM 	int choffs;
1719484Sgarrett.damore@Sun.COM 	int nch;
1729484Sgarrett.damore@Sun.COM 	int vol;
1739484Sgarrett.damore@Sun.COM 
1749484Sgarrett.damore@Sun.COM 	/*
1759484Sgarrett.damore@Sun.COM 	 * Initial setup.
1769484Sgarrett.damore@Sun.COM 	 */
1779484Sgarrett.damore@Sun.COM 
1789484Sgarrett.damore@Sun.COM 	src = sp->s_cnv_ptr;
1799484Sgarrett.damore@Sun.COM 	choffs = sp->s_choffs;
1809484Sgarrett.damore@Sun.COM 	nch = sp->s_cnv_dst_nchan;
1819484Sgarrett.damore@Sun.COM 	vol = sp->s_gain_eff;
1829484Sgarrett.damore@Sun.COM 
1839484Sgarrett.damore@Sun.COM 	/*
1849484Sgarrett.damore@Sun.COM 	 * Do the mixing.  We de-interleave the source stream at the
1859484Sgarrett.damore@Sun.COM 	 * same time.
1869484Sgarrett.damore@Sun.COM 	 */
1879484Sgarrett.damore@Sun.COM 	for (int ch = 0; ch < nch; ch++) {
1889484Sgarrett.damore@Sun.COM 		int32_t *op;
1899484Sgarrett.damore@Sun.COM 		const int32_t *ip;
1909484Sgarrett.damore@Sun.COM 
1919484Sgarrett.damore@Sun.COM 
1929484Sgarrett.damore@Sun.COM 		ip = src + ch;
1939484Sgarrett.damore@Sun.COM 		op = eng->e_chbufs[ch + choffs];
1949484Sgarrett.damore@Sun.COM 		op += offset;
1959484Sgarrett.damore@Sun.COM 
1969484Sgarrett.damore@Sun.COM 		for (int i = nfr; i; i--) {
1979484Sgarrett.damore@Sun.COM 
1989484Sgarrett.damore@Sun.COM 			int64_t	samp;
1999484Sgarrett.damore@Sun.COM 
2009484Sgarrett.damore@Sun.COM 			samp = *ip;
2019484Sgarrett.damore@Sun.COM 			samp *= vol;
2029484Sgarrett.damore@Sun.COM 			samp /= AUDIO_VOL_SCALE;
2039484Sgarrett.damore@Sun.COM 
2049484Sgarrett.damore@Sun.COM 			ip += nch;
2059484Sgarrett.damore@Sun.COM 			*op += (int32_t)samp;
2069484Sgarrett.damore@Sun.COM 			op++;
2079484Sgarrett.damore@Sun.COM 		}
2089484Sgarrett.damore@Sun.COM 	}
2099484Sgarrett.damore@Sun.COM 
2109484Sgarrett.damore@Sun.COM 	sp->s_cnv_cnt -= nfr;
2119484Sgarrett.damore@Sun.COM 	sp->s_cnv_ptr += (nch * nfr);
2129484Sgarrett.damore@Sun.COM }
2139484Sgarrett.damore@Sun.COM 
2149484Sgarrett.damore@Sun.COM /*
2159484Sgarrett.damore@Sun.COM  * Consume a fragment's worth of data.  This is called when the data in
2169484Sgarrett.damore@Sun.COM  * the conversion buffer is exhausted, and we need to refill it from the
2179484Sgarrett.damore@Sun.COM  * source buffer.  We always consume data from the client in quantities of
2189484Sgarrett.damore@Sun.COM  * a fragment at a time (assuming that a fragment is available.)
2199484Sgarrett.damore@Sun.COM  */
2209484Sgarrett.damore@Sun.COM static void
auimpl_consume_fragment(audio_stream_t * sp)2219484Sgarrett.damore@Sun.COM auimpl_consume_fragment(audio_stream_t *sp)
2229484Sgarrett.damore@Sun.COM {
22311936Sgdamore@opensolaris.org 	uint_t	count;
22411936Sgdamore@opensolaris.org 	uint_t	avail;
22511936Sgdamore@opensolaris.org 	uint_t	nframes;
22611936Sgdamore@opensolaris.org 	uint_t	fragfr;
22711936Sgdamore@opensolaris.org 	uint_t	framesz;
22811936Sgdamore@opensolaris.org 	caddr_t	cnvbuf;
2299484Sgarrett.damore@Sun.COM 
2309484Sgarrett.damore@Sun.COM 	sp->s_cnv_src = sp->s_cnv_buf0;
2319484Sgarrett.damore@Sun.COM 	sp->s_cnv_dst = sp->s_cnv_buf1;
2329484Sgarrett.damore@Sun.COM 
2339484Sgarrett.damore@Sun.COM 	fragfr = sp->s_fragfr;
2349484Sgarrett.damore@Sun.COM 	nframes = sp->s_nframes;
2359484Sgarrett.damore@Sun.COM 	framesz = sp->s_framesz;
2369484Sgarrett.damore@Sun.COM 
2379484Sgarrett.damore@Sun.COM 	ASSERT(sp->s_head >= sp->s_tail);
2389484Sgarrett.damore@Sun.COM 
2399484Sgarrett.damore@Sun.COM 	avail = sp->s_head - sp->s_tail;
2409484Sgarrett.damore@Sun.COM 	cnvbuf = sp->s_cnv_src;
2419484Sgarrett.damore@Sun.COM 
2429484Sgarrett.damore@Sun.COM 	count = min(avail, fragfr);
2439484Sgarrett.damore@Sun.COM 
2449484Sgarrett.damore@Sun.COM 	/*
2459484Sgarrett.damore@Sun.COM 	 * Copy data.  We deal properly with wraps.  Done as a
2469484Sgarrett.damore@Sun.COM 	 * do...while to minimize the number of tests.
2479484Sgarrett.damore@Sun.COM 	 */
2489484Sgarrett.damore@Sun.COM 	do {
24911936Sgdamore@opensolaris.org 		uint_t n;
25011936Sgdamore@opensolaris.org 		uint_t nbytes;
2519484Sgarrett.damore@Sun.COM 
2529484Sgarrett.damore@Sun.COM 		n = min(nframes - sp->s_tidx, count);
2539484Sgarrett.damore@Sun.COM 		nbytes = framesz * n;
2549484Sgarrett.damore@Sun.COM 		bcopy(sp->s_data + (sp->s_tidx * framesz), cnvbuf, nbytes);
2559484Sgarrett.damore@Sun.COM 		cnvbuf += nbytes;
2569484Sgarrett.damore@Sun.COM 		count -= n;
2579484Sgarrett.damore@Sun.COM 		sp->s_samples += n;
2589484Sgarrett.damore@Sun.COM 		sp->s_tail += n;
2599484Sgarrett.damore@Sun.COM 		sp->s_tidx += n;
2609484Sgarrett.damore@Sun.COM 		if (sp->s_tidx >= nframes)
2619484Sgarrett.damore@Sun.COM 			sp->s_tidx -= nframes;
2629484Sgarrett.damore@Sun.COM 	} while (count);
2639484Sgarrett.damore@Sun.COM 
2649484Sgarrett.damore@Sun.COM 	/* Note: data conversion is optional! */
2659484Sgarrett.damore@Sun.COM 	count = min(avail, fragfr);
2669484Sgarrett.damore@Sun.COM 	if (sp->s_converter != NULL) {
2679484Sgarrett.damore@Sun.COM 		sp->s_cnv_cnt = sp->s_converter(sp, count);
2689484Sgarrett.damore@Sun.COM 	} else {
2699484Sgarrett.damore@Sun.COM 		sp->s_cnv_cnt = count;
2709484Sgarrett.damore@Sun.COM 	}
2719484Sgarrett.damore@Sun.COM }
2729484Sgarrett.damore@Sun.COM 
2739484Sgarrett.damore@Sun.COM static void
auimpl_output_callback_impl(audio_engine_t * eng,audio_client_t ** output,audio_client_t ** drain)27411936Sgdamore@opensolaris.org auimpl_output_callback_impl(audio_engine_t *eng, audio_client_t **output,
27511936Sgdamore@opensolaris.org     audio_client_t **drain)
2769484Sgarrett.damore@Sun.COM {
27711936Sgdamore@opensolaris.org 	uint_t	fragfr = eng->e_fragfr;
27811936Sgdamore@opensolaris.org 	uint_t	resid;
2799484Sgarrett.damore@Sun.COM 
2809484Sgarrett.damore@Sun.COM 	/* clear any preexisting mix results */
2819484Sgarrett.damore@Sun.COM 	for (int i = 0; i < eng->e_nchan; i++)
2829484Sgarrett.damore@Sun.COM 		bzero(eng->e_chbufs[i], AUDIO_CHBUFS * sizeof (int32_t));
2839484Sgarrett.damore@Sun.COM 
2849484Sgarrett.damore@Sun.COM 	for (audio_stream_t *sp = list_head(&eng->e_streams);
2859484Sgarrett.damore@Sun.COM 	    sp != NULL;
2869484Sgarrett.damore@Sun.COM 	    sp = list_next(&eng->e_streams, sp)) {
2879484Sgarrett.damore@Sun.COM 
2889484Sgarrett.damore@Sun.COM 		int		need;
2899484Sgarrett.damore@Sun.COM 		int		avail;
2909484Sgarrett.damore@Sun.COM 		int		used;
2919484Sgarrett.damore@Sun.COM 		int		offset;
2929484Sgarrett.damore@Sun.COM 		boolean_t	drained = B_FALSE;
2939484Sgarrett.damore@Sun.COM 		audio_client_t	*c = sp->s_client;
2949484Sgarrett.damore@Sun.COM 
2959484Sgarrett.damore@Sun.COM 		/*
2969484Sgarrett.damore@Sun.COM 		 * We need/want a full fragment.  If the client has
2979484Sgarrett.damore@Sun.COM 		 * less than that available, it will cause a client
2989484Sgarrett.damore@Sun.COM 		 * underrun in auimpl_consume_fragment, but in such a
2999484Sgarrett.damore@Sun.COM 		 * case we should get silence bytes.  Assignments done
3009484Sgarrett.damore@Sun.COM 		 * ahead of the lock to minimize lock contention.
3019484Sgarrett.damore@Sun.COM 		 */
3029484Sgarrett.damore@Sun.COM 		need = fragfr;
3039484Sgarrett.damore@Sun.COM 		offset = 0;
3049484Sgarrett.damore@Sun.COM 
3059484Sgarrett.damore@Sun.COM 		mutex_enter(&sp->s_lock);
3069484Sgarrett.damore@Sun.COM 		/* skip over streams not running or paused */
30711936Sgdamore@opensolaris.org 		if ((!sp->s_running) || (sp->s_paused)) {
3089484Sgarrett.damore@Sun.COM 			mutex_exit(&sp->s_lock);
3099484Sgarrett.damore@Sun.COM 			continue;
3109484Sgarrett.damore@Sun.COM 		}
3119484Sgarrett.damore@Sun.COM 
3129484Sgarrett.damore@Sun.COM 		do {
3139484Sgarrett.damore@Sun.COM 			/* make sure we have data to chew on */
3149484Sgarrett.damore@Sun.COM 			if ((avail = sp->s_cnv_cnt) == 0) {
3159484Sgarrett.damore@Sun.COM 				auimpl_consume_fragment(sp);
3169484Sgarrett.damore@Sun.COM 				sp->s_cnv_ptr = sp->s_cnv_src;
3179484Sgarrett.damore@Sun.COM 				avail = sp->s_cnv_cnt;
3189484Sgarrett.damore@Sun.COM 			}
3199484Sgarrett.damore@Sun.COM 
3209484Sgarrett.damore@Sun.COM 			/*
3219484Sgarrett.damore@Sun.COM 			 * We might have got more data than we need
3229484Sgarrett.damore@Sun.COM 			 * right now.  (E.g. 8kHz expanding to 48kHz.)
3239484Sgarrett.damore@Sun.COM 			 * Take only what we need.
3249484Sgarrett.damore@Sun.COM 			 */
3259484Sgarrett.damore@Sun.COM 			used = min(avail, need);
3269484Sgarrett.damore@Sun.COM 
3279484Sgarrett.damore@Sun.COM 			/*
3289484Sgarrett.damore@Sun.COM 			 * Mix the results, as much data as we can use
3299484Sgarrett.damore@Sun.COM 			 * this round.
3309484Sgarrett.damore@Sun.COM 			 */
3319484Sgarrett.damore@Sun.COM 			auimpl_output_mix(sp, offset, used);
3329484Sgarrett.damore@Sun.COM 
3339484Sgarrett.damore@Sun.COM 			/*
3349484Sgarrett.damore@Sun.COM 			 * Save the offset for the next round, so we don't
3359484Sgarrett.damore@Sun.COM 			 * remix into the same location.
3369484Sgarrett.damore@Sun.COM 			 */
3379484Sgarrett.damore@Sun.COM 			offset += used;
3389484Sgarrett.damore@Sun.COM 
3399484Sgarrett.damore@Sun.COM 			/*
3409484Sgarrett.damore@Sun.COM 			 * Okay, we mixed some data, but it might not
3419484Sgarrett.damore@Sun.COM 			 * have been all we need.  This can happen
3429484Sgarrett.damore@Sun.COM 			 * either because we just mixed up some
3439484Sgarrett.damore@Sun.COM 			 * partial/residual data, or because the
3449484Sgarrett.damore@Sun.COM 			 * client has a fragment size which expands to
3459484Sgarrett.damore@Sun.COM 			 * less than a full fragment for us. (Such as
3469484Sgarrett.damore@Sun.COM 			 * a client wanting to operate at a higher
3479484Sgarrett.damore@Sun.COM 			 * data rate than the engine.)
3489484Sgarrett.damore@Sun.COM 			 */
3499484Sgarrett.damore@Sun.COM 			need -= used;
3509484Sgarrett.damore@Sun.COM 
3519484Sgarrett.damore@Sun.COM 		} while (need && avail);
3529484Sgarrett.damore@Sun.COM 
3539484Sgarrett.damore@Sun.COM 		if (avail == 0) {
3549484Sgarrett.damore@Sun.COM 			/* underrun or end of data */
3559484Sgarrett.damore@Sun.COM 			if (sp->s_draining) {
3569484Sgarrett.damore@Sun.COM 				if (sp->s_drain_idx == 0) {
3579484Sgarrett.damore@Sun.COM 					sp->s_drain_idx = eng->e_head;
3589484Sgarrett.damore@Sun.COM 				}
3599484Sgarrett.damore@Sun.COM 				if (eng->e_tail >= sp->s_drain_idx) {
3609484Sgarrett.damore@Sun.COM 					sp->s_drain_idx = 0;
3619484Sgarrett.damore@Sun.COM 					sp->s_draining = B_FALSE;
3629484Sgarrett.damore@Sun.COM 					/*
3639484Sgarrett.damore@Sun.COM 					 * After draining, stop the
3649484Sgarrett.damore@Sun.COM 					 * stream cleanly.  This
3659484Sgarrett.damore@Sun.COM 					 * prevents underrun errors.
3669484Sgarrett.damore@Sun.COM 					 *
3679484Sgarrett.damore@Sun.COM 					 * (Stream will auto-start if
3689484Sgarrett.damore@Sun.COM 					 * client submits more data to
3699484Sgarrett.damore@Sun.COM 					 * it.)
3709484Sgarrett.damore@Sun.COM 					 *
3719484Sgarrett.damore@Sun.COM 					 * AC3: When an AC3 stream
3729484Sgarrett.damore@Sun.COM 					 * drains we should probably
3739484Sgarrett.damore@Sun.COM 					 * stop the actual hardware
3749484Sgarrett.damore@Sun.COM 					 * engine.
3759484Sgarrett.damore@Sun.COM 					 */
3769484Sgarrett.damore@Sun.COM 					ASSERT(mutex_owned(&eng->e_lock));
3779484Sgarrett.damore@Sun.COM 					sp->s_running = B_FALSE;
3789484Sgarrett.damore@Sun.COM 					drained = B_TRUE;
3799484Sgarrett.damore@Sun.COM 				}
3809484Sgarrett.damore@Sun.COM 			} else {
3819484Sgarrett.damore@Sun.COM 				sp->s_errors += need;
38210157Sgdamore@opensolaris.org 				eng->e_stream_underruns++;
3839484Sgarrett.damore@Sun.COM 			}
3849484Sgarrett.damore@Sun.COM 		}
3859484Sgarrett.damore@Sun.COM 
3869484Sgarrett.damore@Sun.COM 		/* wake threads waiting for stream (blocking writes, etc.) */
3879484Sgarrett.damore@Sun.COM 		cv_broadcast(&sp->s_cv);
3889484Sgarrett.damore@Sun.COM 
3899484Sgarrett.damore@Sun.COM 		mutex_exit(&sp->s_lock);
3909484Sgarrett.damore@Sun.COM 
3919484Sgarrett.damore@Sun.COM 
3929484Sgarrett.damore@Sun.COM 		/*
3939484Sgarrett.damore@Sun.COM 		 * Asynchronously notify clients.  We do as much as
3949484Sgarrett.damore@Sun.COM 		 * possible of this outside of the lock, it avoids
3959484Sgarrett.damore@Sun.COM 		 * s_lock and c_lock contention and eliminates any
3969484Sgarrett.damore@Sun.COM 		 * chance of deadlock.
3979484Sgarrett.damore@Sun.COM 		 */
3989484Sgarrett.damore@Sun.COM 
39910632Sgdamore@opensolaris.org 		/*
40010632Sgdamore@opensolaris.org 		 * NB: The only lock we are holding now is the engine
40110632Sgdamore@opensolaris.org 		 * lock.  But the client can't go away because the
40210632Sgdamore@opensolaris.org 		 * closer would have to get the engine lock to remove
40310632Sgdamore@opensolaris.org 		 * the client's stream from engine.  So we're safe.
40410632Sgdamore@opensolaris.org 		 */
4059484Sgarrett.damore@Sun.COM 
40611936Sgdamore@opensolaris.org 		if (output && (c->c_output != NULL) &&
40711936Sgdamore@opensolaris.org 		    (c->c_next_output == NULL)) {
40811936Sgdamore@opensolaris.org 			auclnt_hold(c);
40911936Sgdamore@opensolaris.org 			c->c_next_output = *output;
41011936Sgdamore@opensolaris.org 			*output = c;
41110632Sgdamore@opensolaris.org 		}
4129484Sgarrett.damore@Sun.COM 
41311936Sgdamore@opensolaris.org 		if (drain && drained && (c->c_drain != NULL) &&
41411936Sgdamore@opensolaris.org 		    (c->c_next_drain == NULL)) {
41511936Sgdamore@opensolaris.org 			auclnt_hold(c);
41611936Sgdamore@opensolaris.org 			c->c_next_drain = *drain;
41711936Sgdamore@opensolaris.org 			*drain = c;
41810632Sgdamore@opensolaris.org 		}
4199484Sgarrett.damore@Sun.COM 	}
4209484Sgarrett.damore@Sun.COM 
4219484Sgarrett.damore@Sun.COM 	/*
4229484Sgarrett.damore@Sun.COM 	 * Deal with 24-bit overflows (from mixing) gracefully.
4239484Sgarrett.damore@Sun.COM 	 */
4249484Sgarrett.damore@Sun.COM 	auimpl_output_limiter(eng);
4259484Sgarrett.damore@Sun.COM 
4269484Sgarrett.damore@Sun.COM 	/*
42711936Sgdamore@opensolaris.org 	 * Export the data (a whole fragment) to the device.  Deal
42811936Sgdamore@opensolaris.org 	 * properly with wraps.  Note that the test and subtraction is
42911936Sgdamore@opensolaris.org 	 * faster for dealing with wrap than modulo.
4309484Sgarrett.damore@Sun.COM 	 */
43111936Sgdamore@opensolaris.org 	resid = fragfr;
43211936Sgdamore@opensolaris.org 	do {
43311936Sgdamore@opensolaris.org 		uint_t part = min(resid, eng->e_nframes - eng->e_hidx);
43411936Sgdamore@opensolaris.org 		eng->e_export(eng, part, fragfr - resid);
43511936Sgdamore@opensolaris.org 		eng->e_head += part;
43611936Sgdamore@opensolaris.org 		eng->e_hidx += part;
43711936Sgdamore@opensolaris.org 		if (eng->e_hidx == eng->e_nframes)
43811936Sgdamore@opensolaris.org 			eng->e_hidx = 0;
43911936Sgdamore@opensolaris.org 		resid -= part;
44011936Sgdamore@opensolaris.org 	} while (resid);
4419484Sgarrett.damore@Sun.COM 
4429484Sgarrett.damore@Sun.COM 	/*
4439484Sgarrett.damore@Sun.COM 	 * Consider doing the SYNC outside of the lock.
4449484Sgarrett.damore@Sun.COM 	 */
4459484Sgarrett.damore@Sun.COM 	ENG_SYNC(eng, fragfr);
4469484Sgarrett.damore@Sun.COM }
4479484Sgarrett.damore@Sun.COM 
4489484Sgarrett.damore@Sun.COM /*
4499484Sgarrett.damore@Sun.COM  * Outer loop attempts to keep playing until we hit maximum playahead.
4509484Sgarrett.damore@Sun.COM  */
4519484Sgarrett.damore@Sun.COM 
4529484Sgarrett.damore@Sun.COM void
auimpl_output_callback(void * arg)45311936Sgdamore@opensolaris.org auimpl_output_callback(void *arg)
4549484Sgarrett.damore@Sun.COM {
45511936Sgdamore@opensolaris.org 	audio_engine_t	*e = arg;
45611936Sgdamore@opensolaris.org 	int64_t		cnt;
45711936Sgdamore@opensolaris.org 	audio_client_t	*c;
45811936Sgdamore@opensolaris.org 	audio_client_t	*output = NULL;
45911936Sgdamore@opensolaris.org 	audio_client_t	*drain = NULL;
46011936Sgdamore@opensolaris.org 	uint64_t	t;
46111936Sgdamore@opensolaris.org 
46211936Sgdamore@opensolaris.org 	mutex_enter(&e->e_lock);
46311936Sgdamore@opensolaris.org 
464*12165Sgdamore@opensolaris.org 	if (e->e_suspended || e->e_failed || !e->e_periodic) {
46511936Sgdamore@opensolaris.org 		mutex_exit(&e->e_lock);
46611936Sgdamore@opensolaris.org 		return;
46711936Sgdamore@opensolaris.org 	}
4689484Sgarrett.damore@Sun.COM 
46911936Sgdamore@opensolaris.org 	if (e->e_need_start) {
47011936Sgdamore@opensolaris.org 		int rv;
47111936Sgdamore@opensolaris.org 		if ((rv = ENG_START(e)) != 0) {
47211936Sgdamore@opensolaris.org 			e->e_failed = B_TRUE;
47311936Sgdamore@opensolaris.org 			mutex_exit(&e->e_lock);
47411936Sgdamore@opensolaris.org 			audio_dev_warn(e->e_dev,
47511936Sgdamore@opensolaris.org 			    "failed starting output, rv = %d", rv);
47611936Sgdamore@opensolaris.org 			return;
47711936Sgdamore@opensolaris.org 		}
47811936Sgdamore@opensolaris.org 		e->e_need_start = B_FALSE;
47911936Sgdamore@opensolaris.org 	}
48011936Sgdamore@opensolaris.org 
48111936Sgdamore@opensolaris.org 	t = ENG_COUNT(e);
48211936Sgdamore@opensolaris.org 	if (t < e->e_tail) {
48311936Sgdamore@opensolaris.org 		/*
48411936Sgdamore@opensolaris.org 		 * This is a sign of a serious bug.  We should
48511936Sgdamore@opensolaris.org 		 * probably offline the device via FMA, if we ever
48611936Sgdamore@opensolaris.org 		 * support FMA for audio devices.
48711936Sgdamore@opensolaris.org 		 */
48811936Sgdamore@opensolaris.org 		e->e_failed = B_TRUE;
48911936Sgdamore@opensolaris.org 		ENG_STOP(e);
49011936Sgdamore@opensolaris.org 		mutex_exit(&e->e_lock);
49111936Sgdamore@opensolaris.org 		audio_dev_warn(e->e_dev,
49211936Sgdamore@opensolaris.org 		    "device malfunction: broken play back sample counter");
49311936Sgdamore@opensolaris.org 		return;
49411936Sgdamore@opensolaris.org 
49511936Sgdamore@opensolaris.org 	}
49611936Sgdamore@opensolaris.org 	e->e_tail = t;
49711936Sgdamore@opensolaris.org 
49811936Sgdamore@opensolaris.org 	if (e->e_tail > e->e_head) {
49911936Sgdamore@opensolaris.org 		/* want more than we have */
50011936Sgdamore@opensolaris.org 		e->e_errors++;
50111936Sgdamore@opensolaris.org 		e->e_underruns++;
50211936Sgdamore@opensolaris.org 	}
50311936Sgdamore@opensolaris.org 
50411936Sgdamore@opensolaris.org 	cnt = e->e_head - e->e_tail;
5059484Sgarrett.damore@Sun.COM 
5069484Sgarrett.damore@Sun.COM 	/* stay a bit ahead */
50711936Sgdamore@opensolaris.org 	while (cnt < e->e_playahead) {
50811936Sgdamore@opensolaris.org 		auimpl_output_callback_impl(e, &output, &drain);
50911936Sgdamore@opensolaris.org 		cnt = e->e_head - e->e_tail;
51011936Sgdamore@opensolaris.org 	}
51111936Sgdamore@opensolaris.org 	mutex_exit(&e->e_lock);
51211936Sgdamore@opensolaris.org 
51311936Sgdamore@opensolaris.org 	/*
51411936Sgdamore@opensolaris.org 	 * Notify client personalities.
51511936Sgdamore@opensolaris.org 	 */
51611936Sgdamore@opensolaris.org 	while ((c = output) != NULL) {
51711936Sgdamore@opensolaris.org 
51811936Sgdamore@opensolaris.org 		output = c->c_next_output;
51911936Sgdamore@opensolaris.org 		c->c_next_output = NULL;
52011936Sgdamore@opensolaris.org 		c->c_output(c);
52111936Sgdamore@opensolaris.org 		auclnt_release(c);
52211936Sgdamore@opensolaris.org 	}
52311936Sgdamore@opensolaris.org 
52411936Sgdamore@opensolaris.org 	while ((c = drain) != NULL) {
52511936Sgdamore@opensolaris.org 
52611936Sgdamore@opensolaris.org 		drain = c->c_next_drain;
52711936Sgdamore@opensolaris.org 		c->c_next_drain = NULL;
52811936Sgdamore@opensolaris.org 		c->c_drain(c);
52911936Sgdamore@opensolaris.org 		auclnt_release(c);
53011936Sgdamore@opensolaris.org 	}
53111936Sgdamore@opensolaris.org 
53211936Sgdamore@opensolaris.org }
53311936Sgdamore@opensolaris.org 
53411936Sgdamore@opensolaris.org void
auimpl_output_preload(audio_engine_t * e)53511936Sgdamore@opensolaris.org auimpl_output_preload(audio_engine_t *e)
53611936Sgdamore@opensolaris.org {
53711936Sgdamore@opensolaris.org 	int64_t	cnt;
53811936Sgdamore@opensolaris.org 
53911936Sgdamore@opensolaris.org 	ASSERT(mutex_owned(&e->e_lock));
54011936Sgdamore@opensolaris.org 
54111936Sgdamore@opensolaris.org 	if (e->e_tail > e->e_head) {
54211936Sgdamore@opensolaris.org 		/* want more than we have */
54311936Sgdamore@opensolaris.org 		e->e_errors++;
54411936Sgdamore@opensolaris.org 		e->e_underruns++;
54511936Sgdamore@opensolaris.org 		e->e_tail = e->e_head;
54611936Sgdamore@opensolaris.org 	}
54711936Sgdamore@opensolaris.org 	cnt = e->e_head - e->e_tail;
54811936Sgdamore@opensolaris.org 
54911936Sgdamore@opensolaris.org 	/* stay a bit ahead */
55011936Sgdamore@opensolaris.org 	while (cnt < e->e_playahead) {
55111936Sgdamore@opensolaris.org 		auimpl_output_callback_impl(e, NULL, NULL);
55211936Sgdamore@opensolaris.org 		cnt = e->e_head - e->e_tail;
5539484Sgarrett.damore@Sun.COM 	}
5549484Sgarrett.damore@Sun.COM }
555