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