summaryrefslogtreecommitdiff
path: root/kernel/framework/vmix_core/vmix_output.c
diff options
context:
space:
mode:
authorIgor Pashev <pashev.igor@gmail.com>2013-05-03 21:08:42 +0400
committerIgor Pashev <pashev.igor@gmail.com>2013-05-03 21:08:42 +0400
commit1058def8e7827e56ce4a70afb4aeacb5dc44148f (patch)
tree4495d23e7b54ab5700e3839081e797c1eafe0db9 /kernel/framework/vmix_core/vmix_output.c
downloadoss4-upstream.tar.gz
Imported Upstream version 4.2-build2006upstream/4.2-build2006upstream
Diffstat (limited to 'kernel/framework/vmix_core/vmix_output.c')
-rw-r--r--kernel/framework/vmix_core/vmix_output.c687
1 files changed, 687 insertions, 0 deletions
diff --git a/kernel/framework/vmix_core/vmix_output.c b/kernel/framework/vmix_core/vmix_output.c
new file mode 100644
index 0000000..47e10ab
--- /dev/null
+++ b/kernel/framework/vmix_core/vmix_output.c
@@ -0,0 +1,687 @@
+/*
+ * Purpose: Virtual mixing audio driver output mixing routines
+ *
+ * This file contains the actual mixing and resampling engine for output.
+ * The actual algorithms are implemented in outexport.inc and playmix.inc.
+ */
+/*
+ *
+ * This file is part of Open Sound System.
+ *
+ * Copyright (C) 4Front Technologies 1996-2008.
+ *
+ * This this source file is released under GPL v2 license (no other versions).
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ *
+ */
+
+#define SWAP_SUPPORT
+#include <oss_config.h>
+#include "vmix.h"
+
+#if 0
+/* Debugging macros*/
+extern unsigned char tmp_status;
+# define UP_STATUS(v) OUTB(NULL, (tmp_status=tmp_status|(v)), 0x378)
+# define DOWN_STATUS(v) OUTB(NULL, (tmp_status=tmp_status&~(v)), 0x378)
+#else
+# define UP_STATUS(v)
+# define DOWN_STATUS(v)
+#endif
+
+#undef SINE_DEBUG
+
+#ifndef CONFIG_OSS_VMIX_FLOAT
+#undef SINE_DEBUG
+#endif
+
+#ifdef SINE_DEBUG
+#define SINE_SIZE 48
+static const float sine_table[SINE_SIZE] = {
+ 0.000000, 0.130526, 0.258819, 0.382683,
+ 0.500000, 0.608761, 0.707107, 0.793353,
+ 0.866025, 0.923880, 0.965926, 0.991445,
+ 1.000000, 0.991445, 0.965926, 0.923880,
+ 0.866025, 0.793353, 0.707107, 0.608761,
+ 0.500000, 0.382683, 0.258819, 0.130526,
+ 0.000000, -0.130526, -0.258819, -0.382683,
+ -0.500000, -0.608761, -0.707107, -0.793353,
+ -0.866025, -0.923880, -0.965926, -0.991445,
+ -1.000000, -0.991445, -0.965926, -0.923880,
+ -0.866025, -0.793353, -0.707107, -0.608761,
+ -0.500000, -0.382683, -0.258819, -0.130526
+};
+static int sine_phase[MAX_PLAY_CHANNELS] = { 0 };
+#endif
+
+#ifndef CONFIG_OSS_VMIX_FLOAT
+/*
+ * Simple limiter to prevent overflows when using fixed point computations
+ */
+void
+process_limiter (unsigned int *statevar, int *chbufs[], int nchannels,
+ int nsamples)
+{
+#define Abs(x) ((x) < 0 ? -(x) : (x))
+
+ int k, t;
+ unsigned int q, amp, amp2;
+
+ for (t = 0; t < nsamples; t++)
+ {
+ amp = (unsigned) Abs (chbufs[0][t]);
+
+ for (k = 1; k < nchannels; k++)
+ {
+ amp2 = (unsigned) 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 < nchannels; k++)
+ {
+ int in = chbufs[k][t];
+ int out = 0;
+ unsigned int 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;
+ }
+ }
+}
+#endif
+
+/*
+ * Output export functions
+ */
+
+#undef INT_EXPORT
+#define INT_EXPORT(x) (x / 256)
+
+static void
+export16ne (vmix_engine_t * eng, void *outbuf, vmix_sample_t * chbufs[],
+ int channels, int samples)
+{
+ short *op;
+#define SAMPLE_TYPE short
+#define SAMPLE_RANGE 32768.0
+#undef VMIX_BYTESWAP
+#define VMIX_BYTESWAP(x) x
+
+#include "outexport.inc"
+}
+
+static void
+export16oe (vmix_engine_t * eng, void *outbuf, vmix_sample_t * chbufs[],
+ int channels, int samples)
+{
+ short *op;
+#undef SAMPLE_TYPE
+#undef SAMPLE_RANGE
+#define SAMPLE_TYPE short
+#define SAMPLE_RANGE 32768.0
+#undef VMIX_BYTESWAP
+#define VMIX_BYTESWAP(x) bswap16(x)
+
+#include "outexport.inc"
+}
+
+#undef INT_EXPORT
+#define INT_EXPORT(x) (x * 256)
+
+static void
+export32ne (vmix_engine_t * eng, void *outbuf, vmix_sample_t * chbufs[],
+ int channels, int samples)
+{
+ int *op;
+#undef SAMPLE_TYPE
+#undef SAMPLE_RANGE
+#define SAMPLE_TYPE int
+#define SAMPLE_RANGE 2147483648.0
+#undef VMIX_BYTESWAP
+#define VMIX_BYTESWAP(x) x
+
+#include "outexport.inc"
+}
+
+static void
+export32oe (vmix_engine_t * eng, void *outbuf, vmix_sample_t * chbufs[],
+ int channels, int samples)
+{
+ int *op;
+#define SAMPLE_TYPE int
+#define SAMPLE_RANGE 2147483648.0
+#undef VMIX_BYTESWAP
+#define VMIX_BYTESWAP(x) bswap32(x)
+
+#include "outexport.inc"
+}
+
+/*
+ * Mixing functions
+ */
+#undef BUFFER_TYPE
+#define BUFFER_TYPE short *
+
+#undef INT_OUTMIX
+#define INT_OUTMIX(x) (x * 256)
+
+void
+vmix_outmix_16ne (vmix_portc_t * portc, int nsamples)
+{
+ short *inp;
+#ifdef CONFIG_OSS_VMIX_FLOAT
+ double range = 3.0517578125e-5; /* 1.0 / 32768.0 */
+#endif
+#undef VMIX_BYTESWAP
+#define VMIX_BYTESWAP(x) x
+#include "playmix.inc"
+}
+
+void
+vmix_outmix_16oe (vmix_portc_t * portc, int nsamples)
+{
+ short *inp;
+#ifdef CONFIG_OSS_VMIX_FLOAT
+ double range = 3.0517578125e-5; /* 1.0 / 32768.0 */
+#endif
+#undef VMIX_BYTESWAP
+#define VMIX_BYTESWAP(x) bswap16(x)
+#include "playmix.inc"
+}
+
+#undef BUFFER_TYPE
+#define BUFFER_TYPE int *
+#undef INT_OUTMIX
+#define INT_OUTMIX(x) (x / 256)
+
+void
+vmix_outmix_32ne (vmix_portc_t * portc, int nsamples)
+{
+ int *inp;
+#undef VMIX_BYTESWAP
+#define VMIX_BYTESWAP(x) x
+#ifdef CONFIG_OSS_VMIX_FLOAT
+ double range = 4.65661287308e-10; /* 1.0 / 2147483648.0 */
+#endif
+#include "playmix.inc"
+}
+
+void
+vmix_outmix_32oe (vmix_portc_t * portc, int nsamples)
+{
+ int *inp;
+#undef VMIX_BYTESWAP
+#define VMIX_BYTESWAP(x) bswap32(x)
+#ifdef CONFIG_OSS_VMIX_FLOAT
+ double range = 4.65661287308e-10; /* 1.0 / 2147483648.0 */
+#endif
+#include "playmix.inc"
+}
+
+#ifdef CONFIG_OSS_VMIX_FLOAT
+void
+vmix_outmix_float (vmix_portc_t * portc, int nsamples)
+{
+ float *inp;
+ double range = 1.0;
+#undef BUFFER_TYPE
+#define BUFFER_TYPE float *
+#undef VMIX_BYTESWAP
+#define VMIX_BYTESWAP(x) x
+#include "playmix.inc"
+}
+#endif
+
+#ifdef CONFIG_OSS_VMIX_FLOAT
+/*
+ * Mixing functions (with sample rate conversions)
+ */
+#undef BUFFER_TYPE
+#define BUFFER_TYPE short *
+
+#undef INT_OUTMIX
+#define INT_OUTMIX(x) (x * 256)
+
+void
+vmix_outmix_16ne_src (vmix_portc_t * portc, int nsamples)
+{
+ short *inp;
+ double range = 3.0517578125e-5; /* 1.0 / 32768.0 */
+#undef VMIX_BYTESWAP
+#define VMIX_BYTESWAP(x) x
+#include "playmix_src.inc"
+}
+
+void
+vmix_outmix_16oe_src (vmix_portc_t * portc, int nsamples)
+{
+ short *inp;
+ double range = 3.0517578125e-5; /* 1.0 / 32768.0 */
+#undef VMIX_BYTESWAP
+#define VMIX_BYTESWAP(x) bswap16(x)
+#include "playmix_src.inc"
+}
+
+#undef BUFFER_TYPE
+#define BUFFER_TYPE int *
+
+#undef INT_OUTMIX
+#define INT_OUTMIX(x) (x / 256)
+
+void
+vmix_outmix_32ne_src (vmix_portc_t * portc, int nsamples)
+{
+ int *inp;
+ double range = 4.65661287308e-10; /* 1.0 / 2147483648.0 */
+#undef VMIX_BYTESWAP
+#define VMIX_BYTESWAP(x) x
+#include "playmix_src.inc"
+}
+
+void
+vmix_outmix_32oe_src (vmix_portc_t * portc, int nsamples)
+{
+ int *inp;
+ double range = 4.65661287308e-10; /* 1.0 / 2147483648.0 */
+#undef VMIX_BYTESWAP
+#define VMIX_BYTESWAP(x) bswap32(x)
+#include "playmix_src.inc"
+}
+
+void
+vmix_outmix_float_src (vmix_portc_t * portc, int nsamples)
+{
+ float *inp;
+ double range = 1.0;
+#undef BUFFER_TYPE
+#define BUFFER_TYPE float *
+#undef VMIX_BYTESWAP
+#define VMIX_BYTESWAP(x) x
+#include "playmix_src.inc"
+}
+#endif
+
+static void
+vmix_play_callback (int dev, int parm)
+{
+ int n;
+
+ adev_t *adev = audio_engines[dev];
+ dmap_t *dmap = adev->dmap_out;
+ oss_native_word flags;
+ int i;
+
+ vmix_mixer_t *mixer = adev->vmix_mixer;
+ vmix_engine_t *eng = &mixer->play_engine;
+
+#ifdef CONFIG_OSS_VMIX_FLOAT
+ fp_env_t fp_buf;
+ short *fp_env = fp_buf;
+ fp_flags_t fp_flags;
+#endif
+
+ UP_STATUS (0x04);
+ if (dmap == NULL || dmap->dmabuf == NULL)
+ return;
+
+ if (dmap->bytes_in_use == 0)
+ {
+ cmn_err (CE_WARN, "Bytes in use=0, eng=%d\n", adev->engine_num);
+ return;
+ }
+
+ MUTEX_ENTER_IRQDISABLE (mixer->mutex, flags);
+
+ if (dmap->dmabuf == NULL)
+ {
+ MUTEX_EXIT_IRQRESTORE (mixer->mutex, flags);
+ return;
+ }
+
+#ifdef CONFIG_OSS_VMIX_FLOAT
+
+ {
+ /*
+ * Align the FP save buffer to 16 byte boundary
+ */
+ oss_native_word p;
+ p = (oss_native_word) fp_buf;
+
+ p = ((p + 15ULL) / 16) * 16;
+ fp_env = (short *) p;
+ }
+ FP_SAVE (fp_env, fp_flags);
+#endif
+
+ n = 0;
+ while (n++ < eng->max_playahead
+ && dmap_get_qlen (dmap) < eng->max_playahead)
+ {
+ int p;
+ unsigned char *outbuf;
+ int nstreams = 0;
+
+ for (i = 0; i < eng->channels; i++)
+ memset (eng->chbufs[i], 0, CHBUF_SAMPLES * sizeof (vmix_sample_t));
+ for (i = 0; i < mixer->num_clientdevs; i++)
+ if (mixer->client_portc[i]->trigger_bits & PCM_ENABLE_OUTPUT)
+ {
+ vmix_portc_t *portc = mixer->client_portc[i];
+
+ if (portc->play_mixing_func == NULL)
+ continue;
+ if (portc->play_choffs + portc->channels >
+ mixer->play_engine.channels)
+ portc->play_choffs = 0;
+ portc->play_mixing_func (portc,
+ mixer->play_engine.samples_per_frag);
+ nstreams++; /* Count the number of active output streams */
+ }
+
+ eng->num_active_outputs = (nstreams > 0) ? nstreams : 1;
+
+ /* Export the output mix to the device */
+ p = (int) (dmap->user_counter % dmap->bytes_in_use);
+ outbuf = dmap->dmabuf + p;
+ if (dmap->dmabuf != NULL)
+ {
+#ifndef CONFIG_OSS_VMIX_FLOAT
+ process_limiter (&eng->limiter_statevar, eng->chbufs, eng->channels,
+ eng->samples_per_frag);
+#endif
+ eng->converter (eng, outbuf, eng->chbufs, eng->channels,
+ eng->samples_per_frag);
+ }
+
+ dmap->user_counter += dmap->fragment_size;
+
+ }
+
+#ifdef CONFIG_OSS_VMIX_FLOAT
+ FP_RESTORE (fp_env, fp_flags);
+#endif
+ MUTEX_EXIT_IRQRESTORE (mixer->mutex, flags);
+/*
+ * Call oss_audio_outputintr outside FP mode because it may
+ * cause a task switch (under Solaris). Task switch may turn on CR0.TS under
+ * x86 which in turn will cause #nm exception.
+ */
+ for (i = 0; i < mixer->num_clientdevs; i++)
+ if (mixer->client_portc[i]->trigger_bits & PCM_ENABLE_OUTPUT)
+ {
+ vmix_portc_t *portc = mixer->client_portc[i];
+ oss_audio_outputintr (portc->audio_dev, 1);
+ }
+
+ for (i = 0; i < mixer->num_loopdevs; i++)
+ {
+ if (mixer->loop_portc[i]->trigger_bits & PCM_ENABLE_INPUT)
+ {
+ oss_audio_inputintr (mixer->loop_portc[i]->audio_dev, 0);
+ }
+ }
+ DOWN_STATUS (0x04);
+}
+
+void
+vmix_setup_play_engine (vmix_mixer_t * mixer, adev_t * adev, dmap_t * dmap)
+{
+ int fmt;
+ int frags = 0x7fff0007; /* fragment size of 128 bytes */
+ int i;
+ int old_min;
+
+/*
+ * Sample format (and endianess) setup
+ *
+ */
+
+ /* First make sure a sane format is selected before starting to probe */
+ fmt = adev->d->adrv_set_format (mixer->masterdev, AFMT_S16_LE);
+ fmt = adev->d->adrv_set_format (mixer->masterdev, AFMT_S16_NE);
+
+ /* Find out the "best" sample format supported by the device */
+
+ if (adev->oformat_mask & AFMT_S16_OE)
+ fmt = AFMT_S16_OE;
+ if (adev->oformat_mask & AFMT_S16_NE)
+ fmt = AFMT_S16_NE;
+
+ if (mixer->multich_enable) /* Better quality enabled */
+ {
+ if (adev->oformat_mask & AFMT_S32_OE)
+ fmt = AFMT_S32_OE;
+ if (adev->oformat_mask & AFMT_S32_NE)
+ fmt = AFMT_S32_NE;
+ }
+
+ fmt = adev->d->adrv_set_format (mixer->masterdev, fmt);
+ mixer->play_engine.fmt = fmt;
+
+ switch (fmt)
+ {
+ case AFMT_S16_NE:
+ mixer->play_engine.bits = 16;
+ mixer->play_engine.converter = export16ne;
+ break;
+
+ case AFMT_S16_OE:
+ mixer->play_engine.bits = 16;
+ mixer->play_engine.converter = export16oe;
+ break;
+
+ case AFMT_S32_NE:
+ mixer->play_engine.bits = 32;
+ mixer->play_engine.converter = export32ne;
+ break;
+
+ case AFMT_S32_OE:
+ mixer->play_engine.bits = 32;
+ mixer->play_engine.converter = export32oe;
+ break;
+
+ default:
+ cmn_err (CE_CONT, "Unrecognized sample format %x\n", fmt);
+ return;
+ }
+/*
+ * Number of channels
+ */
+ mixer->play_engine.channels = mixer->max_channels;
+
+ if (mixer->play_engine.channels > MAX_PLAY_CHANNELS)
+ mixer->play_engine.channels = MAX_PLAY_CHANNELS;
+
+ if (!mixer->multich_enable)
+ mixer->play_engine.channels = 2;
+
+ /* Force the device to stereo before trying with (possibly) multiple channels */
+ adev->d->adrv_set_channels (mixer->masterdev, 2);
+
+ mixer->play_engine.channels =
+ adev->d->adrv_set_channels (mixer->masterdev,
+ mixer->play_engine.channels);
+
+ if (mixer->play_engine.channels > MAX_PLAY_CHANNELS)
+ {
+ cmn_err (CE_WARN,
+ "Number of channels (%d) is larger than maximum (%d)\n",
+ mixer->play_engine.channels, MAX_PLAY_CHANNELS);
+ return;
+ }
+
+ if (mixer->play_engine.channels > 2)
+ {
+ DDB (cmn_err
+ (CE_CONT, "Enabling multi channel play mode, %d hw channels\n",
+ mixer->play_engine.channels));
+ }
+ else if (mixer->play_engine.channels != 2)
+ {
+ cmn_err (CE_WARN,
+ "Master device doesn't support suitable channel configuration\n");
+
+ return;
+ }
+
+ mixer->play_engine.rate =
+ oss_audio_set_rate (mixer->masterdev, mixer->rate);
+ mixer->rate = mixer->play_engine.rate;
+
+ if (mixer->play_engine.rate <= 22050)
+ frags = 0x7fff0004; /* Use smaller fragments */
+
+ audio_engines[mixer->masterdev]->hw_parms.channels =
+ mixer->play_engine.channels;
+ audio_engines[mixer->masterdev]->hw_parms.rate = mixer->play_engine.rate;
+ audio_engines[mixer->masterdev]->dmap_out->data_rate =
+ mixer->play_engine.rate * mixer->play_engine.channels *
+ mixer->play_engine.bits / 8;
+ audio_engines[mixer->masterdev]->dmap_out->frame_size =
+ mixer->play_engine.channels * mixer->play_engine.bits / 8;
+
+ old_min = adev->min_fragments;
+#if 0
+ if ((adev->max_fragments == 0 || adev->max_fragments >= 4)
+ && adev->min_block == 0)
+ adev->min_fragments = 4;
+#endif
+
+ oss_audio_ioctl (mixer->masterdev, NULL, SNDCTL_DSP_SETFRAGMENT,
+ (ioctl_arg) & frags);
+ oss_audio_ioctl (mixer->masterdev, NULL, SNDCTL_DSP_GETBLKSIZE,
+ (ioctl_arg) & mixer->play_engine.fragsize);
+
+ dmap->bytes_in_use = dmap->fragment_size * dmap->nfrags;
+
+ oss_audio_ioctl (mixer->masterdev, NULL, SNDCTL_DSP_GETBLKSIZE,
+ (ioctl_arg) & mixer->play_engine.fragsize);
+ adev->min_fragments = old_min;
+
+ mixer->play_engine.fragsize = dmap->fragment_size;
+
+/*
+ * Determine how many fragments we need to keep filled.
+ */
+ if (adev->vmix_flags & VMIX_MULTIFRAG)
+ mixer->play_engine.max_playahead = 32;
+ else
+ mixer->play_engine.max_playahead = 4;
+
+ if (mixer->play_engine.max_playahead >
+ audio_engines[mixer->masterdev]->dmap_out->nfrags)
+ mixer->play_engine.max_playahead =
+ audio_engines[mixer->masterdev]->dmap_out->nfrags;
+
+/*
+ * Try to keep one empty fragment after the one currently being played
+ * by the device. Writing too close to the playback point may cause
+ * massive clicking with some devices.
+ */
+ if (dmap->nfrags > 2 && mixer->play_engine.max_playahead == dmap->nfrags)
+ mixer->play_engine.max_playahead--;
+
+ mixer->play_engine.samples_per_frag =
+ mixer->play_engine.fragsize / mixer->play_engine.channels /
+ (mixer->play_engine.bits / 8);
+
+ if (mixer->play_engine.samples_per_frag > CHBUF_SAMPLES)
+ {
+ cmn_err (CE_WARN, "Too many samples per fragment (%d,%d)\n",
+ mixer->play_engine.samples_per_frag, CHBUF_SAMPLES);
+ return;
+ }
+
+ for (i = 0; i < mixer->play_engine.channels; i++)
+ {
+ if (mixer->play_engine.chbufs[i] == NULL) /* Not allocated yet */
+ {
+ mixer->play_engine.chbufs[i] =
+ PMALLOC (mixer->master_portc,
+ CHBUF_SAMPLES * sizeof (vmix_sample_t));
+ if (mixer->play_engine.chbufs[i] == NULL)
+ {
+ cmn_err (CE_WARN, "Out of memory\n");
+ return;
+ }
+ }
+
+ if (mixer->play_engine.channel_order[i] >= mixer->play_engine.channels)
+ mixer->play_engine.channel_order[i] = i;
+ }
+
+ dmap->audio_callback = vmix_play_callback; /* Enable conversions */
+ dmap->callback_parm = mixer->instance_num;
+ dmap->dma_mode = PCM_ENABLE_OUTPUT;
+
+ if (mixer->inputdev == mixer->masterdev)
+ {
+ mixer->record_engine.rate = mixer->play_engine.rate;
+ mixer->record_engine.bits = mixer->play_engine.bits;
+ mixer->record_engine.fragsize = mixer->play_engine.fragsize;
+ mixer->record_engine.channels = mixer->play_engine.channels;
+ mixer->record_engine.samples_per_frag =
+ mixer->play_engine.samples_per_frag;
+ }
+
+ if (mixer->num_clientdevs > 1)
+ {
+ adev->redirect_out = mixer->client_portc[0]->audio_dev;
+ }
+
+ /*
+ * Fill in the initial playback data (silence) to avoid underruns
+ */
+ vmix_play_callback (mixer->masterdev, mixer->instance_num);
+
+ if (mixer->masterdev == mixer->inputdev)
+ finalize_record_engine (mixer, fmt, audio_engines[mixer->inputdev],
+ audio_engines[mixer->inputdev]->dmap_in);
+}