root/usr/src/uts/common/io/audio/impl/audio_output.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright (C) 4Front Technologies 1996-2008.
 *
 * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
 */

/*
 * Purpose: Virtual mixing audio output routines
 *
 * This file contains the actual mixing and resampling engine for output.
 */

#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/sysmacros.h>
#include "audio_impl.h"

#define DECL_AUDIO_EXPORT(NAME, TYPE, SAMPLE)                           \
void                                                                    \
auimpl_export_##NAME(audio_engine_t *eng, uint_t nfr, uint_t froff)     \
{                                                                       \
        int             nch = eng->e_nchan;                             \
        uint_t          hidx = eng->e_hidx;                             \
        TYPE            *out = (void *)eng->e_data;                     \
        int             ch = 0;                                         \
                                                                        \
        do {    /* for each channel */                                  \
                int32_t *ip;                                            \
                TYPE    *op;                                            \
                int     i;                                              \
                int     incr = eng->e_chincr[ch];                       \
                                                                        \
                /* get value and adjust next channel offset */          \
                op = out + eng->e_choffs[ch] + (hidx * incr);           \
                ip = eng->e_chbufs[ch];                                 \
                ip += froff;                                            \
                                                                        \
                i = nfr;                                                \
                                                                        \
                do {    /* for each frame */                            \
                        int32_t sample = *ip;                           \
                                                                        \
                        *op = SAMPLE;                                   \
                        op += incr;                                     \
                        ip++;                                           \
                                                                        \
                } while (--i);                                          \
                                                                        \
                ch++;                                                   \
        } while (ch < nch);                                             \
}

DECL_AUDIO_EXPORT(16ne, int16_t, sample >> 8)
DECL_AUDIO_EXPORT(16oe, int16_t, ddi_swap16(sample >> 8))
DECL_AUDIO_EXPORT(32ne, int32_t, sample << 8)
DECL_AUDIO_EXPORT(32oe, int32_t, ddi_swap32(sample << 8))
DECL_AUDIO_EXPORT(24ne, int32_t, sample)
DECL_AUDIO_EXPORT(24oe, int32_t, ddi_swap32(sample))

/*
 * Simple limiter to prevent overflows when using fixed point computations
 */
static void
auimpl_output_limiter(audio_engine_t *eng)
{
        int k, t;
        uint_t q, amp, amp2;
        int nchan = eng->e_nchan;
        uint_t fragfr = eng->e_fragfr;
        int32_t **chbufs = eng->e_chbufs;
        uint_t statevar = eng->e_limiter_state;

        for (t = 0; t < fragfr; t++) {

                amp = (uint_t)ABS(chbufs[0][t]);

                for (k = 1; k < nchan; k++)     {
                        amp2 = (uint_t)ABS(chbufs[k][t]);
                        if (amp2 > amp)
                                amp = amp2;
                }

                amp >>= 8;
                q = 0x10000;

                if (amp > 0x7FFF)
                        q = 0x7FFF0000 / amp;

                if (statevar > q) {
                        statevar = q;
                } else {
                        q = statevar;

                        /*
                         * Simplier (linear) tracking algo
                         * (gives less distortion, but more pumping)
                         */
                        statevar += 2;
                        if (statevar > 0x10000)
                                statevar = 0x10000;

                        /*
                         * Classic tracking algo
                         * gives more distortion with no-lookahead
                         * statevar=0x10000-((0x10000-statevar)*0xFFF4>>16);
                         */
                }

                for (k = 0; k < nchan; k++) {
                        int32_t in = chbufs[k][t];
                        int32_t out = 0;
                        uint_t p;

                        if (in >= 0) {
                                p = in;
                                p = ((p & 0xFFFF) * (q >> 4) >> 12) +
                                    (p >> 16) * q;
                                out = p;
                        } else {
                                p = -in;
                                p = ((p & 0xFFFF) * (q >> 4) >> 12) +
                                    (p >> 16) * q;
                                out = -p;
                        }
                        /* safety code */
                        /*
                         * if output after limiter is clamped, then it
                         * can be dropped
                         */
                        if (out > 0x7FFFFF)
                                out = 0x7FFFFF;
                        else if (out < -0x7FFFFF)
                                out = -0x7FFFFF;

                        chbufs[k][t] = out;
                }
        }

        eng->e_limiter_state = statevar;
}

/*
 * Output mixing function.  Assumption: all work is done in 24-bit native PCM.
 */
static void
auimpl_output_mix(audio_stream_t *sp, int offset, int nfr)
{
        audio_engine_t *eng = sp->s_engine;
        const int32_t *src;
        int choffs;
        int nch;
        int vol;

        /*
         * Initial setup.
         */

        src = sp->s_cnv_ptr;
        choffs = sp->s_choffs;
        nch = sp->s_cnv_dst_nchan;
        vol = sp->s_gain_eff;

        /*
         * Do the mixing.  We de-interleave the source stream at the
         * same time.
         */
        for (int ch = 0; ch < nch; ch++) {
                int32_t *op;
                const int32_t *ip;


                ip = src + ch;
                op = eng->e_chbufs[ch + choffs];
                op += offset;

                for (int i = nfr; i; i--) {

                        int64_t samp;

                        samp = *ip;
                        samp *= vol;
                        samp /= AUDIO_VOL_SCALE;

                        ip += nch;
                        *op += (int32_t)samp;
                        op++;
                }
        }

        sp->s_cnv_cnt -= nfr;
        sp->s_cnv_ptr += (nch * nfr);
}

/*
 * Consume a fragment's worth of data.  This is called when the data in
 * the conversion buffer is exhausted, and we need to refill it from the
 * source buffer.  We always consume data from the client in quantities of
 * a fragment at a time (assuming that a fragment is available.)
 */
static void
auimpl_consume_fragment(audio_stream_t *sp)
{
        uint_t  count;
        uint_t  avail;
        uint_t  nframes;
        uint_t  fragfr;
        uint_t  framesz;
        caddr_t cnvbuf;

        sp->s_cnv_src = sp->s_cnv_buf0;
        sp->s_cnv_dst = sp->s_cnv_buf1;

        fragfr = sp->s_fragfr;
        nframes = sp->s_nframes;
        framesz = sp->s_framesz;

        ASSERT(sp->s_head >= sp->s_tail);

        avail = sp->s_head - sp->s_tail;
        cnvbuf = sp->s_cnv_src;

        count = min(avail, fragfr);

        /*
         * Copy data.  We deal properly with wraps.  Done as a
         * do...while to minimize the number of tests.
         */
        do {
                uint_t n;
                uint_t nbytes;

                n = min(nframes - sp->s_tidx, count);
                nbytes = framesz * n;
                bcopy(sp->s_data + (sp->s_tidx * framesz), cnvbuf, nbytes);
                cnvbuf += nbytes;
                count -= n;
                sp->s_samples += n;
                sp->s_tail += n;
                sp->s_tidx += n;
                if (sp->s_tidx >= nframes)
                        sp->s_tidx -= nframes;
        } while (count);

        /* Note: data conversion is optional! */
        count = min(avail, fragfr);
        if (sp->s_converter != NULL) {
                sp->s_cnv_cnt = sp->s_converter(sp, count);
        } else {
                sp->s_cnv_cnt = count;
        }
}

static void
auimpl_output_callback_impl(audio_engine_t *eng, audio_client_t **output,
    audio_client_t **drain)
{
        uint_t  fragfr = eng->e_fragfr;
        uint_t  resid;

        /* clear any preexisting mix results */
        for (int i = 0; i < eng->e_nchan; i++)
                bzero(eng->e_chbufs[i], AUDIO_CHBUFS * sizeof (int32_t));

        for (audio_stream_t *sp = list_head(&eng->e_streams);
            sp != NULL;
            sp = list_next(&eng->e_streams, sp)) {

                int             need;
                int             avail;
                int             used;
                int             offset;
                boolean_t       drained = B_FALSE;
                audio_client_t  *c = sp->s_client;

                /*
                 * We need/want a full fragment.  If the client has
                 * less than that available, it will cause a client
                 * underrun in auimpl_consume_fragment, but in such a
                 * case we should get silence bytes.  Assignments done
                 * ahead of the lock to minimize lock contention.
                 */
                need = fragfr;
                offset = 0;

                mutex_enter(&sp->s_lock);
                /* skip over streams not running or paused */
                if ((!sp->s_running) || (sp->s_paused)) {
                        mutex_exit(&sp->s_lock);
                        continue;
                }

                do {
                        /* make sure we have data to chew on */
                        if ((avail = sp->s_cnv_cnt) == 0) {
                                auimpl_consume_fragment(sp);
                                sp->s_cnv_ptr = sp->s_cnv_src;
                                avail = sp->s_cnv_cnt;
                        }

                        /*
                         * We might have got more data than we need
                         * right now.  (E.g. 8kHz expanding to 48kHz.)
                         * Take only what we need.
                         */
                        used = min(avail, need);

                        /*
                         * Mix the results, as much data as we can use
                         * this round.
                         */
                        auimpl_output_mix(sp, offset, used);

                        /*
                         * Save the offset for the next round, so we don't
                         * remix into the same location.
                         */
                        offset += used;

                        /*
                         * Okay, we mixed some data, but it might not
                         * have been all we need.  This can happen
                         * either because we just mixed up some
                         * partial/residual data, or because the
                         * client has a fragment size which expands to
                         * less than a full fragment for us. (Such as
                         * a client wanting to operate at a higher
                         * data rate than the engine.)
                         */
                        need -= used;

                } while (need && avail);

                if (avail == 0) {
                        /* underrun or end of data */
                        if (sp->s_draining) {
                                if (sp->s_drain_idx == 0) {
                                        sp->s_drain_idx = eng->e_head;
                                }
                                if (eng->e_tail >= sp->s_drain_idx) {
                                        sp->s_drain_idx = 0;
                                        sp->s_draining = B_FALSE;
                                        /*
                                         * After draining, stop the
                                         * stream cleanly.  This
                                         * prevents underrun errors.
                                         *
                                         * (Stream will auto-start if
                                         * client submits more data to
                                         * it.)
                                         *
                                         * AC3: When an AC3 stream
                                         * drains we should probably
                                         * stop the actual hardware
                                         * engine.
                                         */
                                        ASSERT(mutex_owned(&eng->e_lock));
                                        sp->s_running = B_FALSE;
                                        drained = B_TRUE;
                                }
                        } else {
                                sp->s_errors += need;
                                eng->e_stream_underruns++;
                        }
                }

                /* wake threads waiting for stream (blocking writes, etc.) */
                cv_broadcast(&sp->s_cv);

                mutex_exit(&sp->s_lock);


                /*
                 * Asynchronously notify clients.  We do as much as
                 * possible of this outside of the lock, it avoids
                 * s_lock and c_lock contention and eliminates any
                 * chance of deadlock.
                 */

                /*
                 * NB: The only lock we are holding now is the engine
                 * lock.  But the client can't go away because the
                 * closer would have to get the engine lock to remove
                 * the client's stream from engine.  So we're safe.
                 */

                if (output && (c->c_output != NULL) &&
                    (c->c_next_output == NULL)) {
                        auclnt_hold(c);
                        c->c_next_output = *output;
                        *output = c;
                }

                if (drain && drained && (c->c_drain != NULL) &&
                    (c->c_next_drain == NULL)) {
                        auclnt_hold(c);
                        c->c_next_drain = *drain;
                        *drain = c;
                }
        }

        /*
         * Deal with 24-bit overflows (from mixing) gracefully.
         */
        auimpl_output_limiter(eng);

        /*
         * Export the data (a whole fragment) to the device.  Deal
         * properly with wraps.  Note that the test and subtraction is
         * faster for dealing with wrap than modulo.
         */
        resid = fragfr;
        do {
                uint_t part = min(resid, eng->e_nframes - eng->e_hidx);
                eng->e_export(eng, part, fragfr - resid);
                eng->e_head += part;
                eng->e_hidx += part;
                if (eng->e_hidx == eng->e_nframes)
                        eng->e_hidx = 0;
                resid -= part;
        } while (resid);

        /*
         * Consider doing the SYNC outside of the lock.
         */
        ENG_SYNC(eng, fragfr);
}

/*
 * Outer loop attempts to keep playing until we hit maximum playahead.
 */

void
auimpl_output_callback(void *arg)
{
        audio_engine_t  *e = arg;
        int64_t         cnt;
        audio_client_t  *c;
        audio_client_t  *output = NULL;
        audio_client_t  *drain = NULL;
        uint64_t        t;

        mutex_enter(&e->e_lock);

        if (e->e_suspended || e->e_failed || !e->e_periodic) {
                mutex_exit(&e->e_lock);
                return;
        }

        if (e->e_need_start) {
                int rv;
                if ((rv = ENG_START(e)) != 0) {
                        e->e_failed = B_TRUE;
                        mutex_exit(&e->e_lock);
                        audio_dev_warn(e->e_dev,
                            "failed starting output, rv = %d", rv);
                        return;
                }
                e->e_need_start = B_FALSE;
        }

        t = ENG_COUNT(e);
        if (t < e->e_tail) {
                /*
                 * This is a sign of a serious bug.  We should
                 * probably offline the device via FMA, if we ever
                 * support FMA for audio devices.
                 */
                e->e_failed = B_TRUE;
                ENG_STOP(e);
                mutex_exit(&e->e_lock);
                audio_dev_warn(e->e_dev,
                    "device malfunction: broken play back sample counter");
                return;

        }
        e->e_tail = t;

        if (e->e_tail > e->e_head) {
                /* want more than we have */
                e->e_errors++;
                e->e_underruns++;
        }

        cnt = e->e_head - e->e_tail;

        /* stay a bit ahead */
        while (cnt < e->e_playahead) {
                auimpl_output_callback_impl(e, &output, &drain);
                cnt = e->e_head - e->e_tail;
        }
        mutex_exit(&e->e_lock);

        /*
         * Notify client personalities.
         */
        while ((c = output) != NULL) {

                output = c->c_next_output;
                c->c_next_output = NULL;
                c->c_output(c);
                auclnt_release(c);
        }

        while ((c = drain) != NULL) {

                drain = c->c_next_drain;
                c->c_next_drain = NULL;
                c->c_drain(c);
                auclnt_release(c);
        }

}

void
auimpl_output_preload(audio_engine_t *e)
{
        int64_t cnt;

        ASSERT(mutex_owned(&e->e_lock));

        if (e->e_tail > e->e_head) {
                /* want more than we have */
                e->e_errors++;
                e->e_underruns++;
                e->e_tail = e->e_head;
        }
        cnt = e->e_head - e->e_tail;

        /* stay a bit ahead */
        while (cnt < e->e_playahead) {
                auimpl_output_callback_impl(e, NULL, NULL);
                cnt = e->e_head - e->e_tail;
        }
}