/* * 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 #include #include #include #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); } }