root/usr/src/uts/common/io/audio/impl/audio_input.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 input routines
 *
 * This file contains the actual mixing and resampling engine for input.
 */

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

#define DECL_AUDIO_IMPORT(NAME, TYPE, SWAP, SHIFT)                      \
void                                                                    \
auimpl_import_##NAME(audio_engine_t *e, uint_t nfr, audio_stream_t *sp) \
{                                                                       \
        int             nch = e->e_nchan;                               \
        int32_t         *out = (void *)sp->s_cnv_src;                   \
        TYPE            *in = (void *)e->e_data;                        \
        int             ch = 0;                                         \
        int             vol = sp->s_gain_eff;                           \
                                                                        \
        do {    /* for each channel */                                  \
                TYPE    *ip;                                            \
                int32_t *op;                                            \
                int     i;                                              \
                int     incr = e->e_chincr[ch];                         \
                uint_t  tidx = e->e_tidx;                               \
                                                                        \
                /* get value and adjust next channel offset */          \
                op = out++;                                             \
                ip = in + e->e_choffs[ch] + (tidx * incr);              \
                                                                        \
                i = nfr;                                                \
                                                                        \
                do {    /* for each frame */                            \
                        int32_t sample = (TYPE)SWAP(*ip);               \
                        int32_t scaled = sample SHIFT;                  \
                                                                        \
                        scaled *= vol;                                  \
                        scaled /= AUDIO_VOL_SCALE;                      \
                                                                        \
                        *op = scaled;                                   \
                        op += nch;                                      \
                                                                        \
                        ip += incr;                                     \
                        if (++tidx == e->e_nframes) {                   \
                                tidx = 0;                               \
                                ip = in + e->e_choffs[ch];              \
                        }                                               \
                } while (--i);                                          \
                ch++;                                                   \
        } while (ch < nch);                                             \
}

DECL_AUDIO_IMPORT(16ne, int16_t, /* nop */, << 8)
DECL_AUDIO_IMPORT(16oe, int16_t, ddi_swap16, << 8)
DECL_AUDIO_IMPORT(32ne, int32_t, /* nop */, >> 8)
DECL_AUDIO_IMPORT(32oe, int32_t, ddi_swap32, >> 8)
DECL_AUDIO_IMPORT(24ne, int32_t, /* nop */, /* nop */)
DECL_AUDIO_IMPORT(24oe, int32_t, ddi_swap32, /* nop */)

/*
 * Produce capture data.  This takes data from the conversion buffer
 * and copies it into the stream data buffer.
 */
static void
auimpl_produce_data(audio_stream_t *sp, uint_t count)
{
        uint_t  nframes;
        uint_t  framesz;
        caddr_t cnvsrc;
        caddr_t data;

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

        ASSERT(sp->s_head >= sp->s_tail);
        ASSERT(sp->s_hidx < nframes);
        ASSERT(sp->s_tidx < nframes);

        /*
         * Copy data.  We deal properly with wraps.  Done as a
         * do...while to minimize the number of tests.
         */
        cnvsrc = sp->s_cnv_src;
        data = sp->s_data + (sp->s_hidx * framesz);
        do {
                unsigned nf;
                unsigned nb;

                nf = min(nframes - sp->s_hidx, count);
                nb = nf * framesz;

                bcopy(cnvsrc, data, nb);
                data += nb;
                cnvsrc += nb;
                sp->s_hidx += nf;
                sp->s_head += nf;
                count -= nf;
                sp->s_samples += nf;
                if (sp->s_hidx == nframes) {
                        sp->s_hidx = 0;
                        data = sp->s_data;
                }
        } while (count);

        ASSERT(sp->s_tail <= sp->s_head);
        ASSERT(sp->s_hidx < nframes);
}

void
auimpl_input_callback(void *arg)
{
        audio_engine_t  *e = arg;
        uint_t          fragfr = e->e_fragfr;
        audio_stream_t  *sp;
        audio_client_t  *c;
        audio_client_t  *clist = NULL;
        list_t          *l = &e->e_streams;
        uint64_t        h;

        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 input, rv = %d", rv);
                        return;
                }
                e->e_need_start = B_FALSE;
        }

        h = ENG_COUNT(e);
        ASSERT(h >= e->e_head);
        if (h < e->e_head) {
                /*
                 * 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 capture sample counter");
                return;
        }
        e->e_head = h;
        ASSERT(e->e_head >= e->e_tail);

        if ((e->e_head - e->e_tail) > e->e_nframes) {
                /* no room for data, not much we can do */
                e->e_errors++;
                e->e_overruns++;
        }

        /* consume all fragments in the buffer */
        while ((e->e_head - e->e_tail) > fragfr) {

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

                for (sp = list_head(l); sp != NULL; sp = list_next(l, sp)) {
                        int space;
                        int count;

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

                        e->e_import(e, fragfr, sp);

                        /*
                         * Optionally convert fragment to requested sample
                         * format and rate.
                         */
                        if (sp->s_converter != NULL) {
                                count = sp->s_converter(sp, fragfr);
                        } else {
                                count = fragfr;
                        }

                        ASSERT(sp->s_head >= sp->s_tail);
                        space = sp->s_nframes - (sp->s_head - sp->s_tail);
                        if (count > space) {
                                e->e_stream_overruns++;
                                e->e_errors++;
                                sp->s_errors += count - space;
                                count = space;
                        }

                        auimpl_produce_data(sp, count);

                        /* wake blocked threads (blocking reads, etc.) */
                        cv_broadcast(&sp->s_cv);

                        mutex_exit(&sp->s_lock);

                        /*
                         * Add client to notification list.  We'll
                         * process it after dropping the lock.
                         */
                        c = sp->s_client;

                        if ((c->c_input != NULL) &&
                            (c->c_next_input == NULL)) {
                                auclnt_hold(c);
                                c->c_next_input = clist;
                                clist = c;
                        }
                }

                /*
                 * Update the tail pointer, and the data pointer.
                 */
                e->e_tail += fragfr;
                e->e_tidx += fragfr;
                if (e->e_tidx >= e->e_nframes) {
                        e->e_tidx -= e->e_nframes;
                }
        }

        mutex_exit(&e->e_lock);

        /*
         * Notify client personalities.
         */

        while ((c = clist) != NULL) {
                clist = c->c_next_input;
                c->c_next_input = NULL;
                c->c_input(c);
                auclnt_release(c);
        }
}