diff options
Diffstat (limited to 'kernel/framework/vmix_core')
-rw-r--r-- | kernel/framework/vmix_core/.config | 1 | ||||
-rw-r--r-- | kernel/framework/vmix_core/db_scale.h | 45 | ||||
-rw-r--r-- | kernel/framework/vmix_core/outexport.inc | 83 | ||||
-rw-r--r-- | kernel/framework/vmix_core/outexport_int.inc | 78 | ||||
-rw-r--r-- | kernel/framework/vmix_core/playmix.inc | 115 | ||||
-rw-r--r-- | kernel/framework/vmix_core/playmix_int.inc | 102 | ||||
-rw-r--r-- | kernel/framework/vmix_core/playmix_src.inc | 152 | ||||
-rw-r--r-- | kernel/framework/vmix_core/rec_export.inc | 76 | ||||
-rw-r--r-- | kernel/framework/vmix_core/rec_export_int.inc | 72 | ||||
-rw-r--r-- | kernel/framework/vmix_core/vmix.h | 242 | ||||
-rw-r--r-- | kernel/framework/vmix_core/vmix_core.c | 2352 | ||||
-rw-r--r-- | kernel/framework/vmix_core/vmix_import.inc | 77 | ||||
-rw-r--r-- | kernel/framework/vmix_core/vmix_import_int.inc | 56 | ||||
-rw-r--r-- | kernel/framework/vmix_core/vmix_input.c | 505 | ||||
-rw-r--r-- | kernel/framework/vmix_core/vmix_output.c | 687 |
15 files changed, 4643 insertions, 0 deletions
diff --git a/kernel/framework/vmix_core/.config b/kernel/framework/vmix_core/.config new file mode 100644 index 0000000..f6f2c2e --- /dev/null +++ b/kernel/framework/vmix_core/.config @@ -0,0 +1 @@ +configcheck=VMIX diff --git a/kernel/framework/vmix_core/db_scale.h b/kernel/framework/vmix_core/db_scale.h new file mode 100644 index 0000000..3b23edd --- /dev/null +++ b/kernel/framework/vmix_core/db_scale.h @@ -0,0 +1,45 @@ +/* + * Purpose: dB to linear conversion tables for vmix + */ +/* + * + * 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. + * + */ + +/* + * Attenuation table for dB->linear conversion. Indexed in steps of 0.5 dB. + * Table size is 25 dB (first entry is handled as mute). + */ + +#ifdef CONFIG_OSS_VMIX_FLOAT +const float vmix_db_table[DB_SIZE + 1] = { + 0.0 /* MUTE */ , 0.0035481, 0.0039811, 0.0044668, 0.0050119, + 0.0056234, 0.0063096, 0.0070795, 0.0079433, 0.0089125, + 0.01, 0.01122, 0.012589, 0.014125, 0.015849, + 0.017783, 0.019953, 0.022387, 0.025119, 0.028184, + 0.031623, 0.035481, 0.039811, 0.044668, 0.050119, + 0.056234, 0.063096, 0.070795, 0.079433, 0.089125, + 0.1, 0.1122, 0.12589, 0.14125, 0.15849, + 0.17783, 0.19953, 0.22387, 0.25119, 0.28184, + 0.31623, 0.35481, 0.39811, 0.44668, 0.50119, + 0.56234, 0.63096, 0.70795, 0.79433, 0.89125, + 1.0 /* Full level */ +}; +#else +/* #define VMIX_VOL_SCALE moved to vmix.h */ +const int vmix_db_table[DB_SIZE + 1] = { + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, + 4, 4, 5, 5, 6, 7, 8, 9, 10, 11, + 12, 14, 16, 18, 20, 22, 25, 28, 32, 36, + 40, 45, 50, 57, 64, 71, 80, 90, 101, 114, + 128 +}; +#endif diff --git a/kernel/framework/vmix_core/outexport.inc b/kernel/framework/vmix_core/outexport.inc new file mode 100644 index 0000000..6a28b3c --- /dev/null +++ b/kernel/framework/vmix_core/outexport.inc @@ -0,0 +1,83 @@ +#ifdef CONFIG_OSS_VMIX_FLOAT +/* + * Purpose: Local output buffer to device export routine for vmix (FP version) + */ +/* + * + * 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. + * + */ + +int i, ch, och; +double vol; + +vol = vmix_db_table[eng->outvol / 5]; + +for (ch = 0; ch < channels; ch++) + { + double vu; + float *chbuf; + + och = eng->channel_order[ch]; + op = (SAMPLE_TYPE *) outbuf; + op += och; + + chbuf = chbufs[ch]; + + vu = eng->vu[och % 2]; + vu = vu / 255.0; + + for (i = 0; i < samples; i++) + { + double tmp; + +#if 0 && defined(SINE_DEBUG) + tmp = sine_table[sine_phase[ch]]; + sine_phase[ch] = (sine_phase[ch] + 1 + ch / 2) % SINE_SIZE; +#else + tmp = *chbuf++; +#endif + + tmp *= vol; + +/* + * Check for clipping. Decrease volume if necessary. + */ + if (tmp < -1.0) + { + vol /= -tmp; + eng->outvol--; + tmp = -1.0; + } + else if (tmp > 1.0) + { + vol /= tmp; + eng->outvol--; + tmp = 1.0; + } + + *op = VMIX_BYTESWAP ((SAMPLE_TYPE) (tmp * SAMPLE_RANGE)); + op += channels; + + /* VU meter */ + if (tmp < 0.0) + tmp = -tmp; + if (tmp > vu) + vu = tmp; + } + + if (och < 2) + { + vu = vu * 255.0; + eng->vu[och] = (int)vu; + } + } +#else +#include "outexport_int.inc" +#endif diff --git a/kernel/framework/vmix_core/outexport_int.inc b/kernel/framework/vmix_core/outexport_int.inc new file mode 100644 index 0000000..074705f --- /dev/null +++ b/kernel/framework/vmix_core/outexport_int.inc @@ -0,0 +1,78 @@ +/* + * Purpose: Local output buffer to device export routine for vmix (int) + */ +/* + * + * 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. + * + */ + +int i, ch, och; +int vol; + +#define RANGE_MAX ((1<<24)-1) +#define RANGE_MIN -(1<<24) + +vol = vmix_db_table[eng->outvol / 5]; + +for (ch = 0; ch < channels; ch++) + { + int vu; + int *chbuf; + + och = eng->channel_order[ch]; + op = (SAMPLE_TYPE *) outbuf; + op += och; + + chbuf = chbufs[ch]; + + vu = eng->vu[och % 2]; + vu = vu * 65536; + + for (i = 0; i < samples; i++) + { + int tmp; + + tmp = *chbuf++; + + tmp = (tmp * vol) / VMIX_VOL_SCALE; + +/* + * Check for clipping. Decrease volume if necessary. + */ + if (tmp<RANGE_MIN) + { + tmp=RANGE_MIN; + eng->outvol -= 1; + vol /= 2; + } + else + if (tmp>RANGE_MAX) + { + tmp=RANGE_MAX; + eng->outvol -= 1; + vol /= 2; + } + + *op = VMIX_BYTESWAP(INT_EXPORT(tmp)); + op += channels; + + /* VU meter */ + if (tmp < 0) + tmp = -tmp; + if (tmp > vu) + vu = tmp; + } + + if (och < 2) + { + vu = vu / 65536; + eng->vu[och] = vu; + } + } diff --git a/kernel/framework/vmix_core/playmix.inc b/kernel/framework/vmix_core/playmix.inc new file mode 100644 index 0000000..50d07f5 --- /dev/null +++ b/kernel/framework/vmix_core/playmix.inc @@ -0,0 +1,115 @@ +#ifdef CONFIG_OSS_VMIX_FLOAT +/* + * Purpose: Application to local playback buffer import routine for vmix (FP) + */ +/* + * + * 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. + * + */ + +/* + * Mixing function for virtual devices + */ + +vmix_mixer_t *mixer = portc->mixer; +vmix_engine_t *eng = &mixer->play_engine; + +dmap_t *dmap = audio_engines[portc->audio_dev]->dmap_out; + +int frame_size; + +int inptr, inmax; +int used_channels; +int i, ch, nch; +double vol; + +/* + * Initial setup + */ + +frame_size = sizeof (*inp); + +inmax = dmap->bytes_in_use / frame_size; +inptr = portc->play_dma_pointer / frame_size; + +inp = (BUFFER_TYPE) dmap->dmabuf; + +used_channels = portc->channels; +if (used_channels > eng->channels) + used_channels = eng->channels; + +/* ignored_channels = portc->channels - used_channels; */ + +/* + * Handle mono playback by playing the mono stream twice (for left and right ch) + */ +nch=used_channels; +if (nch<2)nch=2; + +/* + * Do the mixing + */ +for (ch = 0; ch < nch; ch++) + { + int ip = inptr + (ch%used_channels); + double vu = portc->vu[ch % 2]; + float *chbuf = eng->chbufs[ch+portc->play_choffs]; + + i = portc->volume[ch%2]; + vol = vmix_db_table[i / 5]; + + vu = vu / 255.0; + + for (i = 0; i < nsamples; i++) + { + double tmp; + +#if 0 && defined(SINE_DEBUG) + if (ch > 1) + tmp = 0.0; + else + tmp = sine_table[sine_phase[ch]]; + sine_phase[ch] = (sine_phase[ch] + 1) % SINE_SIZE; +#else + /* + * Convert the sample to right endianess. + */ + tmp = VMIX_BYTESWAP (inp[ip]); + tmp = tmp * range; +#endif + tmp = tmp * vol; + + *chbuf++ += tmp; + ip = (ip + portc->channels) % inmax; + + /* VU meter */ + if (tmp < 0.0) + tmp = -tmp; + if (tmp > vu) + vu = tmp; + } + + if (ch < 2) + { /* Save left/right VU meters */ + vu = vu * 255.0; + portc->vu[ch] = (int)vu; + } + } + +/* + * Finally save the state variables + */ + +portc->play_dma_pointer = + (portc->play_dma_pointer + + nsamples * frame_size * portc->channels) % dmap->bytes_in_use; +#else +#include "playmix_int.inc" +#endif diff --git a/kernel/framework/vmix_core/playmix_int.inc b/kernel/framework/vmix_core/playmix_int.inc new file mode 100644 index 0000000..18e4815 --- /dev/null +++ b/kernel/framework/vmix_core/playmix_int.inc @@ -0,0 +1,102 @@ +/* + * Purpose: Application to local playback buffer import routine for vmix (int) + */ +/* + * + * 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. + * + */ + +/* + * Mixing function for virtual devices + */ + +vmix_mixer_t *mixer = portc->mixer; +vmix_engine_t *eng = &mixer->play_engine; + +dmap_t *dmap = audio_engines[portc->audio_dev]->dmap_out; + +int frame_size; + +int inptr, inmax; +int used_channels; +int i, ch, nch; +int vol; + +/* + * Initial setup + */ + +frame_size = sizeof (*inp); + +inmax = dmap->bytes_in_use / frame_size; +inptr = portc->play_dma_pointer / frame_size; + +inp = (BUFFER_TYPE) dmap->dmabuf; + +used_channels = portc->channels; +if (used_channels > eng->channels) + used_channels = eng->channels; + +/* ignored_channels = portc->channels - used_channels; */ + +/* + * Handle mono playback by playing the mono stream twice (for left and right ch) + */ +nch=used_channels; +if (nch<2)nch=2; + +/* + * Do the mixing + */ +for (ch = 0; ch < nch; ch++) + { + int ip = inptr + (ch%used_channels); + int vu = portc->vu[ch % 2]; + int *chbuf = eng->chbufs[ch+portc->play_choffs]; + + i = portc->volume[ch%2]; + vol = vmix_db_table[i / 5]; + + vu = vu * 65536; + + for (i = 0; i < nsamples; i++) + { + int tmp; + + /* + * Convert the sample to right endianess. + */ + tmp = INT_OUTMIX(VMIX_BYTESWAP (inp[ip])); + tmp = (tmp * vol) / VMIX_VOL_SCALE; + + *chbuf++ += tmp; + ip = (ip + portc->channels) % inmax; + + /* VU meter */ + if (tmp < 0) + tmp = -tmp; + if (tmp > vu) + vu = tmp; + } + + if (ch < 2) + { /* Save left/right VU meters */ + vu = vu / 65536; + portc->vu[ch] = vu; + } + } + +/* + * Finally save the state variables + */ + +portc->play_dma_pointer = + (portc->play_dma_pointer + + nsamples * frame_size * portc->channels) % dmap->bytes_in_use; diff --git a/kernel/framework/vmix_core/playmix_src.inc b/kernel/framework/vmix_core/playmix_src.inc new file mode 100644 index 0000000..1877c34 --- /dev/null +++ b/kernel/framework/vmix_core/playmix_src.inc @@ -0,0 +1,152 @@ +/* + * Purpose: Application to local playback buffer resampling routine for vmix + * + * This file is almost the same than playmix.inc but uses simple + * linear interpolation algorithm for sample rate conversion. + */ +/* + * + * 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. + * + */ + +/* + * Mixing function for virtual devices + */ + +vmix_mixer_t *mixer = portc->mixer; +vmix_engine_t *eng = &mixer->play_engine; + +dmap_t *dmap = audio_engines[portc->audio_dev]->dmap_out; + +int frame_size; + +int inptr, inmax; +int used_channels; +int i, ch, nch; +double vol, step; +double tmp; + +/* + * Initial setup + */ +step = (double) portc->rate / (double) mixer->play_engine.rate; + +#if 1 +/* + * TODO: + * For some reason "trivial" conversion rations such as 4:1, 2:1, 1:2 and 1:4 + * don't work properly. As a workaround we add a tiny error to break the + * evil ratio. Need to find out why the algorithm doesn't work with such ratios. + */ + if (step==4.0)step=4.0001; + if (step==2.0)step=2.0001; + if (step==0.5)step=0.5001; + if (step==0.25)step=0.25001; +#endif + +frame_size = sizeof (*inp); + +inmax = dmap->bytes_in_use / frame_size / portc->channels; +inptr = (int)(portc->play_dma_pointer_src / frame_size / portc->channels); + +used_channels = portc->channels; +if (used_channels > eng->channels) + used_channels = eng->channels; + +/* ignored_channels = portc->channels - used_channels; */ + +/* + * Handle mono playback by playing the mono stream twice (for left and right ch) + */ +nch=used_channels; +if (nch<2)nch=2; + +/* + * Do the mixing + */ +for (ch = 0; ch < nch; ch++) + { + double ip = inptr + (ch%used_channels); + double vu = portc->vu[ch % 2]; + float *chbuf = eng->chbufs[ch+portc->play_choffs]; + + inp = (BUFFER_TYPE) dmap->dmabuf; + inp += ch; + + i = portc->volume[ch%2]; + vol = vmix_db_table[i / 5]; + + vu = vu / 255.0; + + for (i = 0; i < nsamples; i++) + { + double frag, tmp_next; + int ip_next; + +#if 0 && defined(SINE_DEBUG) + if (ch > 1) + tmp = 0.0; + else + tmp = sine_table[sine_phase[ch]]; + sine_phase[ch] = (sine_phase[ch] + 1) % SINE_SIZE; +#else + /* + * Convert the sample to right endianess. + */ + tmp = VMIX_BYTESWAP (inp[((int) ip) * portc->channels]); + + /* perform linear interpolation */ + ip_next = (((int) ip) + 1) % inmax; + tmp_next = VMIX_BYTESWAP (inp[ip_next * portc->channels]); + frag = ip - (int) ip; /* Pointer fraction */ + tmp += (tmp_next - tmp) * frag; + tmp /= 2.0; + + tmp = tmp * range; +#endif + tmp = tmp * vol; + + *chbuf++ += tmp; + ip = ip + step; + if (ip > inmax) + ip -= inmax; + + /* VU meter */ + if (tmp < 0.0) + tmp = -tmp; + if (tmp > vu) + vu = tmp; + } + + if (ch < 2) + { /* Save left/right VU meters */ + vu = vu * 255.0; + portc->vu[ch] = (int)vu; + } + } + +/* + * Finally save the state variables + */ +tmp = (nsamples * frame_size * portc->channels); + +tmp *= step; + +portc->play_dma_pointer_src = portc->play_dma_pointer_src + tmp; + +if (portc->play_dma_pointer_src > dmap->bytes_in_use) + portc->play_dma_pointer_src -= dmap->bytes_in_use; + +portc->play_dma_pointer = (int)portc->play_dma_pointer_src; + +frame_size *= portc->channels; + +portc->play_dma_pointer = (portc->play_dma_pointer / frame_size) * frame_size; + diff --git a/kernel/framework/vmix_core/rec_export.inc b/kernel/framework/vmix_core/rec_export.inc new file mode 100644 index 0000000..a8ac880 --- /dev/null +++ b/kernel/framework/vmix_core/rec_export.inc @@ -0,0 +1,76 @@ +#ifdef CONFIG_OSS_VMIX_FLOAT +/* + * Purpose: Local input buffer to application export routine for vmix (FP version) + */ +/* + * + * 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. + * + */ + +vmix_mixer_t *mixer = portc->mixer; +vmix_engine_t *eng = &mixer->record_engine; + +dmap_t *dmap = audio_engines[portc->audio_dev]->dmap_in; + +int frame_size; + +int outptr, outmax; +int used_channels; +int i, ch; + +/* + * Initial setup + */ + +frame_size = sizeof (*outp); + +outmax = dmap->bytes_in_use / frame_size; +outptr = portc->rec_dma_pointer / frame_size; + +outp = (BUFFER_TYPE) dmap->dmabuf; + +used_channels = portc->channels; +if (used_channels > eng->channels) + used_channels = eng->channels; + +/* ignored_channels = portc->channels - used_channels; */ + +/* + * Do the mixing + */ +for (ch = 0; ch < used_channels; ch++) + { + int op = outptr + ch; + float *chbuf = eng->chbufs[ch+portc->rec_choffs]; + + for (i = 0; i < nsamples; i++) + { + float tmp; + + tmp = *chbuf++; + /* + * Convert the sample to right endianess. + */ + outp[op] = VMIX_BYTESWAP ((int)(tmp * range)); + + op = (op + portc->channels) % outmax; + } + } + +/* + * Finally save the state variables + */ + +portc->rec_dma_pointer = + (portc->rec_dma_pointer + + nsamples * frame_size * portc->channels) % dmap->bytes_in_use; +#else +#include "rec_export_int.inc" +#endif diff --git a/kernel/framework/vmix_core/rec_export_int.inc b/kernel/framework/vmix_core/rec_export_int.inc new file mode 100644 index 0000000..3a9953d --- /dev/null +++ b/kernel/framework/vmix_core/rec_export_int.inc @@ -0,0 +1,72 @@ +/* + * Purpose: Local input buffer to application export routine for vmix (int) + */ +/* + * + * 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. + * + */ + +vmix_mixer_t *mixer = portc->mixer; +vmix_engine_t *eng = &mixer->record_engine; + +dmap_t *dmap = audio_engines[portc->audio_dev]->dmap_in; + +int frame_size; + +int outptr, outmax; +int used_channels; +int i, ch; + +/* + * Initial setup + */ + +frame_size = sizeof (*outp); + +outmax = dmap->bytes_in_use / frame_size; +outptr = portc->rec_dma_pointer / frame_size; + +outp = (BUFFER_TYPE) dmap->dmabuf; + +used_channels = portc->channels; +if (used_channels > eng->channels) + used_channels = eng->channels; + +/* ignored_channels = portc->channels - used_channels; */ + +/* + * Do the mixing + */ +for (ch = 0; ch < used_channels; ch++) + { + int op = outptr + ch; + int *chbuf = eng->chbufs[ch+portc->rec_choffs]; + + for (i = 0; i < nsamples; i++) + { + int tmp; + + tmp = *chbuf++; + /* + * Convert the sample to right endianess. + */ + outp[op] = VMIX_BYTESWAP (INT_EXPORT(tmp)); + + op = (op + portc->channels) % outmax; + } + } + +/* + * Finally save the state variables + */ + +portc->rec_dma_pointer = + (portc->rec_dma_pointer + + nsamples * frame_size * portc->channels) % dmap->bytes_in_use; diff --git a/kernel/framework/vmix_core/vmix.h b/kernel/framework/vmix_core/vmix.h new file mode 100644 index 0000000..a01c722 --- /dev/null +++ b/kernel/framework/vmix_core/vmix.h @@ -0,0 +1,242 @@ +/* + * Purpose: Definitions for the vmix driver + */ +/* + * + * 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 SUPPORTED_FORMATS (AFMT_S16_NE | AFMT_S16_OE | AFMT_S32_NE | AFMT_S32_OE) + +/* + * Maximum number of clients per "real" device is defined by MAX_CLIENTS. Limit of 4 would be good for 95% of systems. + * For each client there will be a mixer volume control and peak meter in the mixer interface. Raising the + * client limit will make the mixer interface larger and larger. Something like 8 is probably the practical limit + * for number of clients. + * + * Mixing more than 16 streams together doesn't make much sense since the result is likely to be + * just noise. Mixing a stream will cause some overhead so it's not a good idea to let large number of iddle + * applications running muted or playing silence. + */ + +#define MAX_CLIENTS 9 + +#define MAX_LOOPDEVS 2 /* Maximum number of vmix loopback devices */ + +/* + * 8 play channels and 2 rec channels might be OK for most devices. However envy24 requires 10 play and 12 rec + * channels for the "raw devices". Some professional (ADAT) cards like Digi96 requires 8+8 channels. + */ +#define MAX_PLAY_CHANNELS 12 +/* MAX_REC_CHANNELS must be less or equal than MAX_PLAY_CHANNELS */ +#define MAX_REC_CHANNELS 12 + +#define CHBUF_SAMPLES 2048 /* Max samples (frames) per fragment */ + +typedef struct _vmix_mixer_t vmix_mixer_t; +typedef struct _vmix_portc_t vmix_portc_t; +typedef struct _vmix_engine_t vmix_engine_t; +typedef unsigned char vmix_channel_map_t[MAX_PLAY_CHANNELS]; + +struct _vmix_portc_t /* Audio device specific data */ +{ + int num; + vmix_mixer_t *mixer; + vmix_portc_t *next; /* Linked list for all portc structures */ + int dev_type; +#define DT_IN 1 +#define DT_OUT 2 +#define DT_LOOP 3 + + int disabled_modes; + + int fmt, bits; + int channels; + int rate; + + int audio_dev; + int open_mode; + int trigger_bits; + + int open_pending; /* Set to 1 by vmix_create_client() and cleared by vmix_open() */ + + int play_dma_pointer; + int play_choffs; /* Index of the first channel on multich play engines */ + int rec_choffs; /* Index of the first channel on multich rec engines */ +#ifdef CONFIG_OSS_VMIX_FLOAT + double play_dma_pointer_src; +#endif + int rec_dma_pointer; + int volume[2]; /* Left and right ch volumes */ + int vu[2]; + + void (*play_mixing_func) (vmix_portc_t * portc, int nsamples); + void (*rec_mixing_func) (vmix_portc_t * portc, int nsamples); + int do_src; + vmix_channel_map_t channel_order; +}; + +#ifdef CONFIG_OSS_VMIX_FLOAT + typedef float vmix_sample_t; +#else + typedef int vmix_sample_t; +#endif + +typedef void (*converter_t) (vmix_engine_t * engine, void *outbuf, + vmix_sample_t * chbufs[], int channels, + int samples); + +struct _vmix_engine_t +{ + int rate, channels, fmt, bits; + int max_playahead; + int fragsize; + int samples_per_frag; + + converter_t converter; + vmix_sample_t *chbufs[MAX_PLAY_CHANNELS]; + unsigned int limiter_statevar; + +/* + * Mixer volumes, etc. + */ + int outvol; + int vu[2]; + int num_active_outputs; + vmix_channel_map_t channel_order; +}; + +struct _vmix_mixer_t /* Instance specific data */ +{ + vmix_mixer_t *next; /* Pointer to the next vmix instance */ + int instance_num; + int disabled; + oss_device_t *osdev; + oss_device_t *master_osdev; + oss_mutex_t mutex; + unsigned int attach_flags; + + int installed_ok; + + int open_devices, open_inputs; + struct fileinfo master_finfo, input_finfo; + int masterdev_opened; + + int vmix_flags; /* Copy of adev[master]->vmix_flags */ + +/* + * Config options for this instance + */ + int masterdev; + int inputdev; + int rate; + + int src_quality; /* Control panel setting */ + int multich_enable; /* Enable multi channel mode */ + int max_channels; + + vmix_engine_t play_engine, record_engine; + + vmix_portc_t *client_portc[MAX_CLIENTS]; + vmix_portc_t *loop_portc[MAX_LOOPDEVS]; + int num_clientdevs, num_loopdevs; + +/* + * Mixer interface + * + * Mixer device numbers for the master audio devices + */ + int output_mixer_dev; + int input_mixer_dev; + int first_input_mixext; + int first_output_mixext; + int client_mixer_group; /* Create the client controls under this mixer group */ +}; + +extern void vmix_setup_play_engine (vmix_mixer_t * mixer, adev_t * adev, + dmap_t * dmap); +extern void vmix_setup_record_engine (vmix_mixer_t * mixer, adev_t * adev, + dmap_t * dmap); +extern void finalize_record_engine (vmix_mixer_t * mixer, int fmt, + adev_t * adev, dmap_p dmap); + +extern void vmix_outmix_16ne (vmix_portc_t * portc, int nsamples); +extern void vmix_outmix_16oe (vmix_portc_t * portc, int nsamples); +extern void vmix_outmix_32ne (vmix_portc_t * portc, int nsamples); +extern void vmix_outmix_32oe (vmix_portc_t * portc, int nsamples); +#ifdef CONFIG_OSS_VMIX_FLOAT +extern void vmix_outmix_float (vmix_portc_t * portc, int nsamples); +#endif + +#ifdef CONFIG_OSS_VMIX_FLOAT +/* + * For the time being these routines will only work in floating point. + */ +extern void vmix_outmix_16ne_src (vmix_portc_t * portc, int nsamples); +extern void vmix_outmix_16oe_src (vmix_portc_t * portc, int nsamples); +extern void vmix_outmix_32ne_src (vmix_portc_t * portc, int nsamples); +extern void vmix_outmix_32oe_src (vmix_portc_t * portc, int nsamples); +extern void vmix_outmix_float_src (vmix_portc_t * portc, int nsamples); +#endif + +extern void vmix_rec_export_16ne (vmix_portc_t * portc, int nsamples); +extern void vmix_rec_export_16oe (vmix_portc_t * portc, int nsamples); +extern void vmix_rec_export_32ne (vmix_portc_t * portc, int nsamples); +extern void vmix_rec_export_32oe (vmix_portc_t * portc, int nsamples); +#ifdef CONFIG_OSS_VMIX_FLOAT +extern void vmix_rec_export_float (vmix_portc_t * portc, int nsamples); +#endif + +#define DB_SIZE 50 +#define VMIX_VOL_SCALE 127 + +#ifdef CONFIG_OSS_VMIX_FLOAT + extern const float vmix_db_table[DB_SIZE + 1]; +#else + extern const int vmix_db_table[DB_SIZE + 1]; +#endif + +#ifdef VMIX_MAIN +#include "db_scale.h" +#endif + +#ifdef SWAP_SUPPORT +/* + * Endianess swapping functions + */ +static __inline__ short +bswap16 (short x) +{ + short y = 0; + unsigned char *a = ((unsigned char *) &x) + 1; + unsigned char *b = (unsigned char *) &y; + + *b++ = *a--; + *b++ = *a--; + + return y; +} + +static __inline__ int +bswap32 (int x) +{ + + int y = 0; + unsigned char *a = ((unsigned char *) &x) + 3; + unsigned char *b = (unsigned char *) &y; + + *b++ = *a--; + *b++ = *a--; + *b++ = *a--; + *b++ = *a--; + + return y; +} +#endif diff --git a/kernel/framework/vmix_core/vmix_core.c b/kernel/framework/vmix_core/vmix_core.c new file mode 100644 index 0000000..d74499f --- /dev/null +++ b/kernel/framework/vmix_core/vmix_core.c @@ -0,0 +1,2352 @@ +/* + * Purpose: Viartual audio mixing framework + * + * This subsystem makes it possible to share one physical audio between + * multiple applications at the same time. + * + */ +/* + * + * 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 VMIX_MAIN +#include <oss_config.h> +#include "vmix.h" + +extern int vmix_disabled; /* Configuration option (osscore.conf) */ +extern int vmix_loopdevs; /* Configuration option (osscore.conf) */ +extern int flat_device_model; +extern int vmix_no_autoattach; +static vmix_mixer_t *mixer_list = NULL; /* List of all currently installed mixer instances */ +static int num_instances = 0; + +static const unsigned char peak_cnv[256] = { + 0, 18, 29, 36, 42, 47, 51, 54, 57, 60, 62, 65, 67, 69, 71, 72, + 74, 75, 77, 78, 79, 81, 82, 83, 84, 85, 86, 87, 88, 89, 89, 90, + 91, 92, 93, 93, 94, 95, 95, 96, 97, 97, 98, 99, 99, 100, 100, 101, + 101, 102, 102, 103, 103, 104, 104, 105, 105, 106, 106, 107, 107, 108, 108, + 108, + 109, 109, 110, 110, 110, 111, 111, 111, 112, 112, 113, 113, 113, 114, 114, + 114, + 115, 115, 115, 115, 116, 116, 116, 117, 117, 117, 118, 118, 118, 118, 119, + 119, + 119, 119, 120, 120, 120, 121, 121, 121, 121, 122, 122, 122, 122, 122, 123, + 123, + 123, 123, 124, 124, 124, 124, 125, 125, 125, 125, 125, 126, 126, 126, 126, + 126, + 127, 127, 127, 127, 127, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, + 130, + 130, 130, 130, 130, 130, 131, 131, 131, 131, 131, 131, 132, 132, 132, 132, + 132, + 132, 133, 133, 133, 133, 133, 133, 134, 134, 134, 134, 134, 134, 134, 135, + 135, + 135, 135, 135, 135, 135, 136, 136, 136, 136, 136, 136, 136, 137, 137, 137, + 137, + 137, 137, 137, 138, 138, 138, 138, 138, 138, 138, 138, 139, 139, 139, 139, + 139, + 139, 139, 139, 140, 140, 140, 140, 140, 140, 140, 140, 141, 141, 141, 141, + 141, + 141, 141, 141, 141, 142, 142, 142, 142, 142, 142, 142, 142, 142, 143, 143, + 143, + 143, 143, 143, 143, 143, 143, 144, 144, 144, 144, 144, 144, 144, 144, 144, + 144, +}; + +/* + * Mixer/control panel interface + */ +static int +vmix_outvol (int dev, int ctrl, unsigned int cmd, int value) +{ + vmix_mixer_t *mixer = mixer_devs[dev]->vmix_devc; + int vol; + + if (mixer == NULL) + return OSS_ENXIO; + + if (cmd == SNDCTL_MIX_READ) + { + switch (ctrl) + { + case 0: /* Main output volume */ + return mixer->play_engine.outvol | (mixer->play_engine. + outvol << 16); + break; + + case 1: /* Peak meter */ + vol = + peak_cnv[mixer->play_engine. + vu[0]] | (peak_cnv[mixer->play_engine.vu[1]] << 8); + mixer->play_engine.vu[0] = 0; + mixer->play_engine.vu[1] = 0; + return vol; + break; + + case 509: /* Curren sample rate */ + return mixer->rate; + break; + + case 510: /* Enable/disable */ + return !mixer->disabled; + break; + + case 511: /* Multi channel enable */ + return mixer->multich_enable; + break; + + case 512: /* grc3<->interpolation selector */ + return mixer->src_quality; + break; + + default: + return OSS_EINVAL; + } + } + + if (cmd == SNDCTL_MIX_WRITE) + { + vol = value & 0xffff; /* Left/mono channel volume */ + if (vol > DB_SIZE * 5) + vol = DB_SIZE * 5; + + switch (ctrl) + { + case 0: + mixer->play_engine.outvol = vol; + + mixer_devs[dev]->modify_counter++; + return vol | (vol << 16); + break; + + case 510: /* Enable/disable */ + mixer->disabled = !value; + return !!value; + break; + + case 511: /* Multich enable */ + mixer->multich_enable = !!value; + mixer_devs[dev]->modify_counter++; + return mixer->multich_enable; + break; + + case 512: /* grc3<->interpolation selector */ + mixer->src_quality = value & 0xff; + mixer_devs[dev]->modify_counter++; + return mixer->src_quality; + break; + + default: + return OSS_EINVAL; + } + } + + return OSS_EINVAL; +} + +static int +vmix_invol (int dev, int ctrl, unsigned int cmd, int value) +{ + vmix_mixer_t *mixer = mixer_devs[dev]->vmix_devc; + int vol; + + if (mixer == NULL) + return OSS_ENXIO; + + if (cmd == SNDCTL_MIX_READ) + { + switch (ctrl) + { + case 0: /* Main input volume */ + return mixer->record_engine.outvol | (mixer->record_engine. + outvol << 16); + break; + + case 1: /* recording peak meter */ + vol = + peak_cnv[mixer->record_engine. + vu[0]] | (peak_cnv[mixer->record_engine.vu[1]] << 8); + mixer->record_engine.vu[0] = 0; + mixer->record_engine.vu[1] = 0; + return vol & 0x7fffffff; + break; + + default: + return OSS_EINVAL; + } + } + + if (cmd == SNDCTL_MIX_WRITE) + { + vol = value & 0xffff; /* Left/mono channel volume */ + if (vol > DB_SIZE * 5) + vol = DB_SIZE * 5; + + switch (ctrl) + { + case 0: + mixer->record_engine.outvol = vol; + mixer_devs[mixer->input_mixer_dev]->modify_counter++; + return vol | (vol << 16); + break; + + default: + return OSS_EINVAL; + } + } + + return OSS_EINVAL; +} + +static int +vmix_outportc_vol (int dev, int ctrl, unsigned int cmd, int value) +{ + vmix_mixer_t *mixer = mixer_devs[dev]->vmix_devc; + vmix_portc_t *portc; + int vol, rvol; + + if (mixer == NULL) + return OSS_ENXIO; + + if (ctrl < 0) + return OSS_ENXIO; + + if (ctrl >= mixer->num_clientdevs) /* Client engine not created yet */ + return (DB_SIZE * 5) | ((DB_SIZE * 5) << 16); /* Force to maximum level */ + + portc = mixer->client_portc[ctrl]; + + if (cmd == SNDCTL_MIX_READ) + { + return portc->volume[0] | (portc->volume[1] << 16); + } + + if (cmd == SNDCTL_MIX_WRITE) + { + vol = (value) & 0xffff; /* Left/mono channel volume */ + rvol = (value >> 16) & 0xffff; /* Right channel volume */ + if (vol > DB_SIZE * 5) + vol = DB_SIZE * 5; + if (rvol > DB_SIZE * 5) + rvol = DB_SIZE * 5; + portc->volume[0] = vol; + portc->volume[1] = rvol; + return vol | (rvol << 16); + } + + return OSS_EINVAL; +} + +/*ARGSUSED*/ +static int +vmix_outportc_vu (int dev, int ctrl, unsigned int cmd, int value) +{ + vmix_mixer_t *mixer = mixer_devs[dev]->vmix_devc; + vmix_portc_t *portc; + int val; + + if (mixer == NULL) + return OSS_ENXIO; + + if (cmd != SNDCTL_MIX_READ) + return OSS_EINVAL; + + if (ctrl < 0 || ctrl >= mixer->num_clientdevs) + return OSS_ENXIO; + + portc = mixer->client_portc[ctrl]; + + val = peak_cnv[portc->vu[0]] | (peak_cnv[portc->vu[1]] << 8); + portc->vu[0] = portc->vu[1] = 0; + + return val; +} + +static void +create_client_controls (void *vmix_mixer, int client_num) +{ + /* + * Create the pcmN sliders + */ + int group, err; + vmix_mixer_t *mixer = vmix_mixer; + int mixer_dev; + char name[32]; + + mixer_dev = mixer->output_mixer_dev; + group = mixer->client_mixer_group; + + if (mixer_dev < 0 || mixer_dev >= num_mixers) + return; + + sprintf (name, "@pcm%d", mixer->client_portc[client_num]->audio_dev); + if ((err = + mixer_ext_create_control (mixer_dev, group, client_num, vmix_outportc_vol, + MIXT_STEREOSLIDER16, name, DB_SIZE * 5, + MIXF_READABLE | MIXF_WRITEABLE | + MIXF_CENTIBEL | MIXF_PCMVOL)) < 0) + return; + + if ((err = + mixer_ext_create_control (mixer_dev, group, client_num, vmix_outportc_vu, + MIXT_STEREOPEAK, "", 144, + MIXF_READABLE | MIXF_DECIBEL)) < 0) + return; +} + +static int +create_output_controls (int mixer_dev) +{ + int ctl; + oss_mixext *ext; + vmix_mixer_t *mixer = mixer_devs[mixer_dev]->vmix_devc; + char tmp[32]; + + /* + * Misc vmix related mixer settings. + */ + sprintf (tmp, "vmix%d-enable", mixer->instance_num); + if ((ctl = mixer_ext_create_control (mixer_dev, 0, 510, vmix_outvol, + MIXT_ONOFF, + tmp, 2, + MIXF_READABLE | MIXF_WRITEABLE)) < + 0) + return ctl; + + sprintf (tmp, "vmix%d-rate", mixer->instance_num); + if ((ctl = mixer_ext_create_control (mixer_dev, 0, 509, vmix_outvol, + MIXT_VALUE, + tmp, 500000, + MIXF_READABLE | MIXF_HZ)) < + 0) + return ctl; + mixer_ext_set_description(mixer_dev, ctl, "Sample rate currently used by virtual mixer on this device.\n" + "Use vmixctl(1) command to change the rate."); + + if (mixer->max_channels>2) + { + sprintf (tmp, "vmix%d-channels", mixer->instance_num); + if ((ctl = mixer_ext_create_control (mixer_dev, 0, 511, vmix_outvol, + MIXT_ENUM, + tmp, 2, + MIXF_READABLE | MIXF_WRITEABLE)) < + 0) + return ctl; + + mixer_ext_set_strings (mixer_dev, ctl, + "Stereo Multich", 0); + } + + sprintf (tmp, "vmix%d-src", mixer->instance_num); + if ((ctl = mixer_ext_create_control (mixer_dev, 0, 512, vmix_outvol, + MIXT_ENUM, + tmp, 7, + MIXF_READABLE | MIXF_WRITEABLE)) < + 0) + return ctl; + + mixer_ext_set_strings (mixer_dev, ctl, + "Fast Low Medium High High+ Production OFF", 0); + mixer_ext_set_description(mixer_dev, ctl, "Sample rate conversion quality used by the virtual mixer.\n" + "\n" + "Virtual mixer uses internally a fixed sampling rate that can be set\n" + "using the 'vmixctl rate' command (usually 48 kHz by default). Applications\n" + "that want to use different rates will be handled by performing automatic\n" + "sample rate conversions (SRC) in software. This operation will consume\n" + "some additional CPU time depending on the quality. The following\n" + "alternatives are availabe:\n" + "\n" + "Fast: Use fast linear interpolation algorithm (low quality).\n" + "Low: Use slightly better linear interpolation\n" + "Medium: Use an algorithm that provides good quality with moderate CPU load.\n" + "High/High+/Production: Higher quality algorithms that consume more CPU resources.\n" + "OFF: No sample rate conversions. Sample rate locked to the master rate.\n" + "\n" + "'Fast' will work best in most cases. Only users with high end audio\n" + "cards and speakers should use the other settings.\n" + ); + ext = mixer_find_ext (mixer_dev, ctl); + if (ext != NULL) + { + int i; + + memset (ext->enum_present, 0, sizeof (ext->enum_present)); +#ifdef CONFIG_OSS_VMIX_FLOAT + ext->enum_present[0] |= 0x01; // "Fast" is always present +#endif + ext->enum_present[0] |= 0x040; // As well as "OFF" +#if CONFIG_OSS_GRC_MAX_QUALITY > 7 +#error CONFIG_OSS_GRC_MAX_QUALITY is out of range +#endif + + for (i=CONFIG_OSS_GRC_MIN_QUALITY; i <= CONFIG_OSS_GRC_MAX_QUALITY; i++) + ext->enum_present[0] |= (1 << i); + } + + /* + * Create the vmix volume slider and peak meter to the top panel. + */ + if (!(mixer->vmix_flags & VMIX_NOMAINVOL)) + { + sprintf (tmp, "vmix%d-outvol", mixer->instance_num); + if ((ctl = mixer_ext_create_control (mixer_dev, 0, 0, vmix_outvol, + MIXT_MONOSLIDER16, + tmp, DB_SIZE * 5, + MIXF_READABLE | MIXF_WRITEABLE | + MIXF_CENTIBEL | MIXF_PCMVOL)) < 0) + return ctl; + + if (mixer->first_output_mixext == -1) + mixer->first_output_mixext = ctl; + } + else + mixer->play_engine.outvol = DB_SIZE * 5; + + sprintf (tmp, "vmix%d-outvu", mixer->instance_num); + if ((ctl = mixer_ext_create_control (mixer_dev, 0, 1, vmix_outvol, + MIXT_STEREOPEAK, + tmp, 144, + MIXF_READABLE | MIXF_DECIBEL)) < 0) + return ctl; + + if (mixer->first_output_mixext == -1) + mixer->first_output_mixext = ctl; + + sprintf (tmp, "vmix%d", mixer->instance_num); + if ((mixer->client_mixer_group = mixer_ext_create_group (mixer_dev, 0, tmp)) < 0) + return mixer->client_mixer_group; + + return 0; +} + +static int +create_input_controls (int mixer_dev) +{ + int err; + vmix_mixer_t *mixer = mixer_devs[mixer_dev]->vmix_devc; + char name[32]; + + if (!(mixer->vmix_flags & VMIX_NOMAINVOL)) + { + sprintf (name, "vmix%d-invol", mixer->instance_num); + + if ((err = mixer_ext_create_control (mixer_dev, 0, 0, vmix_invol, + MIXT_MONOSLIDER16, + name, DB_SIZE * 5, + MIXF_READABLE | MIXF_WRITEABLE | + MIXF_CENTIBEL | MIXF_PCMVOL)) < 0) + return err; + + sprintf (name, "vmix%d-invu", mixer->instance_num); + if ((err = mixer_ext_create_control (mixer_dev, 0, 1, vmix_invol, + MIXT_STEREOPEAK, + name, 144, + MIXF_READABLE | MIXF_DECIBEL)) < 0) + return err; + + if (mixer->first_input_mixext == -1) + mixer->first_input_mixext = err; + } + + return 0; +} + +static int +create_duplex_controls (int mixer_dev) +{ + int err; + + if ((err=create_output_controls (mixer_dev)) < 0) + return err; + + return create_input_controls (mixer_dev); +} + +static int +vmix_process_chninfo (vmix_channel_map_t * output, oss_chninfo * input, + int maxchan) +{ + int i, val; + + for (i=0; i < sizeof (vmix_channel_map_t); i++) + { + val = (*input)[i]; + if ((val <= 0) || (val > maxchan) || (val == i+1)) + { + (*input)[i] = 0; + (*output)[i] = i; + } + else + { + (*output)[i] = val-1; + } + } + return 0; +} + +/* + * Audio virtual device routines + */ + +static int +vmix_set_rate (int dev, int arg) +{ + vmix_mixer_t *mixer = audio_engines[dev]->devc; + vmix_portc_t *portc = audio_engines[dev]->portc; + + if (arg == 0) + return portc->rate; + +#ifdef CONFIG_OSS_VMIX_FLOAT + if (portc->do_src) + { + /* + * Stick with the master device rate if the requested rate is + * not inside the range supported by the simple interpolation algorithm. + * In that way the rate conversion will be handled by the audio core + * with better quality. + */ + if (arg > (mixer->play_engine.rate * 5) / 4) /* At most 1.25*master_rate */ + arg = mixer->play_engine.rate; + +/* + * The simple linear interpolation algorithm cannot handle more than 2x rate + * boosts reliably so don't permit them. + */ + if (arg < mixer->play_engine.rate / 2) + arg = mixer->play_engine.rate; + + return portc->rate = arg; + } +#endif + + return portc->rate = mixer->play_engine.rate; +} + +static short +vmix_set_channels (int dev, short arg) +{ + vmix_portc_t *portc = audio_engines[dev]->portc; + + if (portc->dev_type == DT_LOOP) + { + return portc->mixer->play_engine.channels; + } + + if (arg == 0) + { + return portc->channels; + } + + if (portc->open_mode & OPEN_WRITE) + { + if (arg > portc->mixer->play_engine.channels) + arg = portc->mixer->play_engine.channels; + } + + if (portc->open_mode & OPEN_READ) + { + if (arg > portc->mixer->record_engine.channels) + arg = portc->mixer->record_engine.channels; + } + return portc->channels = arg; +} + +/*ARGSUSED*/ +static unsigned int +vmix_set_format (int dev, unsigned int arg) +{ + vmix_portc_t *portc = audio_engines[dev]->portc; + + if (portc->dev_type == DT_LOOP) + return portc->mixer->play_engine.fmt; + + return portc->fmt = AFMT_S16_NE; +} + +static int +export_names (oss_mixer_enuminfo * ei, char *names) +{ + int n = 1, p = 0; + + strcpy (ei->strings, names); + names = ei->strings; + + ei->strindex[0] = 0; + + while (names[p] != 0) + { + if (names[p] == ' ') + { + names[p] = 0; + ei->strindex[n++] = p + 1; + } + p++; + } + + ei->nvalues = n; + + return 0; +} + +static int +vmix_ioctl_override (int dev, unsigned int cmd, ioctl_arg arg) +{ +/* + * Redirect all mixer (also legacy) ioctl calls to the master device + * if the master device driver has exported its ioctl override method. + * + * All other ioctl calls will be processed in normal way (by oss_audio_core.c) + * because we return OSS_EAGAIN. + */ + + if ((((cmd >> 8) & 0xff) == 'M') || (((cmd >> 8) & 0xff) == 'X')) + { + vmix_mixer_t *mixer = audio_engines[dev]->devc; + adev_t *adev; + + adev=audio_engines[mixer->masterdev]; + + if (adev->d->adrv_ioctl_override == NULL) + return OSS_EAGAIN; + + return adev->d->adrv_ioctl_override(adev->engine_num, cmd, arg); + } + + return OSS_EAGAIN; +} + +static int +vmix_ioctl (int dev, unsigned int cmd, ioctl_arg arg) +{ + vmix_portc_t *portc = audio_engines[dev]->portc; + vmix_mixer_t *mixer = audio_engines[dev]->devc; + int val, left, right; + + switch (cmd) + { + case SNDCTL_DSP_SETPLAYVOL: + left = (*arg) & 0xff; + if (left > 100) + left = 100; + left = (left * DB_SIZE * 5) / 100; + portc->volume[0] = left; + + right = (*arg >> 8) & 0xff; + if (right > 100) + right = 100; + right = (right * DB_SIZE * 5) / 100; + portc->volume[1] = right; + + if (mixer->output_mixer_dev >= 0 + && mixer->output_mixer_dev < num_mixers) + mixer_devs[mixer->output_mixer_dev]->modify_counter++; + return 0; + break; + + case SNDCTL_DSP_GETPLAYVOL: + left = (portc->volume[0] * 100) / (DB_SIZE * 5); + right = (portc->volume[1] * 100) / (DB_SIZE * 5); + *arg = left | (right << 8); + return 0; + break; + + case SNDCTL_DSP_GET_PLAYTGT: + return *arg = portc->play_choffs / 2; + break; + + case SNDCTL_DSP_SET_PLAYTGT: + val = (*arg) * 2; + if (val < 0) + return OSS_EIO; + if (val >= mixer->play_engine.channels) + return OSS_EIO; + portc->play_choffs = val; + return *arg = val / 2; + break; + + case SNDCTL_DSP_GET_PLAYTGT_NAMES: + { + oss_mixer_enuminfo *ei = (oss_mixer_enuminfo *) arg; + int n, p, i; + char *names = NULL; + + names = audio_engines[mixer->masterdev]->outch_names; + + memset (ei, 0, sizeof (*ei)); + + n = mixer->play_engine.channels / 2; + if (n < 1) + n = 1; + ei->nvalues = n; + p = 0; + + if (n <= 1) /* Only one alternative */ + { + ei->nvalues = 1; + ei->strindex[0] = 0; + sprintf (ei->strings, "default"); + + } + else + { + /* Multiple alternatives */ + + if (names != NULL) + return export_names (ei, names); + + for (i = 0; i < n; i++) + { + ei->strindex[i] = p; + + sprintf (&ei->strings[p], "CH%d/%d", i * 2 + 1, i * 2 + 2); + + p += strlen (&ei->strings[p]) + 1; + } + } + + return 0; + } + break; + +/* + * Bypass the recording source and level calls to the master device if only one + * recording client is active and if the master device is not a multi channel + * one (probably professional device). + */ + case SNDCTL_DSP_GETRECVOL: + case SNDCTL_DSP_SETRECVOL: + + if (mixer->inputdev == -1) /* No input device */ + return OSS_EINVAL; + if (mixer->open_inputs < 2 && mixer->record_engine.channels <= 2) + return oss_audio_ioctl (mixer->inputdev, NULL, cmd, arg); + + return OSS_EINVAL; + break; + +/* + * Recording source selection. This can be done in two different ways depending + * on the hardware capabilities: + * + * 1) If the input master device has multiple channels then select one of + * the stereo pairs. It is likely that such device is a professional + * audio card that uses fixed inputs anyway. + * 2) For stereo input only devices bypass the RECSRC ioctl calls to the + * actual hardware driver. However this cannot be done when multiple + * client applications have the same device opened for recording. In + * such situation switching the recording source would disturb other + * applications that already have recording going on. + */ + + case SNDCTL_DSP_GET_RECSRC: + + if (mixer->inputdev == -1) /* No input device */ + return OSS_EINVAL; + if (mixer->open_inputs < 2 && mixer->record_engine.channels <= 2) + return oss_audio_ioctl (mixer->inputdev, NULL, cmd, arg); + + return *arg = portc->rec_choffs / 2; + break; + + case SNDCTL_DSP_SET_RECSRC: + + if (mixer->inputdev == -1) /* No input device */ + return OSS_EINVAL; + + if (mixer->open_inputs < 2 && mixer->record_engine.channels <= 2) + return oss_audio_ioctl (mixer->inputdev, NULL, cmd, arg); + + val = (*arg) * 2; + if (val < 0) + return OSS_EIO; + if (val >= mixer->record_engine.channels) + return OSS_EIO; + portc->rec_choffs = val; + return *arg = val / 2; + break; + + case SNDCTL_DSP_GET_RECSRC_NAMES: + { + oss_mixer_enuminfo *ei = (oss_mixer_enuminfo *) arg; + int n, p, i; + char *names = NULL; + + if (mixer->inputdev == -1) + return OSS_EINVAL; + + if (mixer->open_inputs < 2 && mixer->record_engine.channels <= 2) + return oss_audio_ioctl (mixer->inputdev, NULL, cmd, arg); + + names = audio_engines[mixer->inputdev]->inch_names; + + memset (ei, 0, sizeof (*ei)); + + n = mixer->record_engine.channels / 2; + if (n < 1) + n = 1; + ei->nvalues = n; + p = 0; + + if (n <= 1) /* Only one alternative */ + { + /* + * It might be better to get the name of the current recording + * source from the master device. However for the time being + * we will return just "default". + */ + ei->nvalues = 1; + ei->strindex[0] = 0; + sprintf (ei->strings, "default"); + } + else + { + /* Multiple alternatives */ + + if (names != NULL) + return export_names (ei, names); + + for (i = 0; i < n; i++) + { + ei->strindex[i] = p; + + sprintf (&ei->strings[p], "CH%d/%d", i * 2 + 1, i * 2 + 2); + + p += strlen (&ei->strings[p]) + 1; + } + } + + return 0; + } + break; + } + return OSS_EINVAL; +} + +static void +vmix_halt_input (int dev) +{ + vmix_portc_t *portc = audio_engines[dev]->portc; + vmix_mixer_t *mixer = audio_engines[dev]->devc; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (mixer->mutex, flags); + portc->trigger_bits &= PCM_ENABLE_INPUT; + MUTEX_EXIT_IRQRESTORE (mixer->mutex, flags); +} + +static void +vmix_halt_output (int dev) +{ + vmix_portc_t *portc = audio_engines[dev]->portc; + vmix_mixer_t *mixer = audio_engines[dev]->devc; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (mixer->mutex, flags); + portc->trigger_bits &= PCM_ENABLE_OUTPUT; + MUTEX_EXIT_IRQRESTORE (mixer->mutex, flags); +} + +static void +vmix_reset (int dev) +{ + vmix_halt_input (dev); + vmix_halt_output (dev); +} + +static int +start_engines (vmix_mixer_t * mixer) +{ + int err; + int trig; + + adev_t *adev_in, *adev_out; + dmap_t *dmap_in, *dmap_out; + + if (mixer->masterdev_opened) + { + return 0; + } + + mixer->master_finfo.mode = OPEN_WRITE; + if (mixer->inputdev == mixer->masterdev) + mixer->master_finfo.mode |= OPEN_READ; + mixer->master_finfo.acc_flags = 0; + + adev_out = adev_in = audio_engines[mixer->masterdev]; + + if (mixer->inputdev > -1) + adev_in = audio_engines[mixer->inputdev]; + + adev_out->cooked_enable = 0; + + if ((err = + oss_audio_open_engine (mixer->masterdev, OSS_DEV_DSP, + &mixer->master_finfo, 1, OF_SMALLBUF, + NULL)) < 0) + { + return err; + } + + dmap_out = adev_out->dmap_out; + dmap_in = adev_in->dmap_in; + adev_out->cooked_enable = 0; + + if (mixer->inputdev > -1 && mixer->inputdev != mixer->masterdev) + { + /* + * Open input device + */ + adev_in->cooked_enable = 0; + mixer->input_finfo.mode = OPEN_READ; + mixer->input_finfo.acc_flags = 0; + if ((err = + oss_audio_open_engine (mixer->inputdev, OSS_DEV_DSP, + &mixer->input_finfo, 1, OF_SMALLBUF, + NULL)) < 0) + { + oss_audio_release (mixer->masterdev, &mixer->master_finfo); + return err; + } + strcpy (adev_in->cmd, "VMIX_IN"); + strcpy (adev_in->label, "VMIX_IN"); + dmap_in = adev_in->dmap_in; + adev_out->pid = 0; + adev_out->cooked_enable = 0; + + vmix_setup_record_engine (mixer, adev_in, dmap_in); + + dmap_in->dma_mode = PCM_ENABLE_INPUT; + trig = 0; + oss_audio_ioctl (mixer->inputdev, NULL, SNDCTL_DSP_SETTRIGGER, + (ioctl_arg) & trig); + } + + mixer->masterdev_opened = 1; + strcpy (adev_out->cmd, "VMIX"); + strcpy (adev_out->label, "VMIX"); + adev_out->pid = 0; + adev_out->cooked_enable = 0; + + vmix_setup_play_engine (mixer, adev_out, dmap_out); + + trig = 0; + dmap_out->dma_mode = PCM_ENABLE_OUTPUT; + + oss_audio_ioctl (mixer->masterdev, NULL, SNDCTL_DSP_SETTRIGGER, + (ioctl_arg) & trig); + trig = PCM_ENABLE_OUTPUT; + if (mixer->masterdev == mixer->inputdev) + trig |= PCM_ENABLE_INPUT; + else + { + int trig2 = PCM_ENABLE_INPUT; + if (mixer->inputdev > -1) + if (oss_audio_ioctl (mixer->inputdev, NULL, SNDCTL_DSP_SETTRIGGER, + (ioctl_arg) & trig2) < 0) + { + cmn_err (CE_WARN, "Trigger (input) failed\n"); + } + } + + if (oss_audio_ioctl (mixer->masterdev, NULL, SNDCTL_DSP_SETTRIGGER, + (ioctl_arg) & trig) < 0) + { + cmn_err (CE_WARN, "Trigger failed\n"); + } + + return 0; +} + +static void +stop_engines (vmix_mixer_t * mixer) +{ + oss_native_word flags; + + if (mixer->masterdev_opened) + { + adev_t *adev; + dmap_t *dmap; + + adev = audio_engines[mixer->masterdev]; + dmap = adev->dmap_out; + + oss_audio_ioctl (mixer->masterdev, NULL, SNDCTL_DSP_HALT, 0); + + MUTEX_ENTER_IRQDISABLE (mixer->mutex, flags); + dmap->audio_callback = NULL; + + dmap = adev->dmap_in; + if (dmap != NULL) + { + dmap->audio_callback = NULL; + } + MUTEX_EXIT_IRQRESTORE (mixer->mutex, flags); + + oss_audio_release (mixer->masterdev, &mixer->master_finfo); + + if (mixer->inputdev > -1 && mixer->inputdev != mixer->masterdev) + { + adev = audio_engines[mixer->inputdev]; + dmap = adev->dmap_in; + + oss_audio_ioctl (mixer->inputdev, NULL, SNDCTL_DSP_HALT, 0); + MUTEX_ENTER_IRQDISABLE (mixer->mutex, flags); + dmap->audio_callback = NULL; + MUTEX_EXIT_IRQRESTORE (mixer->mutex, flags); + oss_audio_release (mixer->inputdev, &mixer->input_finfo); + } + + mixer->masterdev_opened = 0; + } +} + +/*ARGSUSED*/ +static int +vmix_open (int dev, int mode, int open_flags) +{ + adev_t *adev = audio_engines[dev]; + vmix_portc_t *portc = audio_engines[dev]->portc; + vmix_mixer_t *mixer = portc->mixer; + oss_native_word flags; + int start = 0; + + if (mode & portc->disabled_modes) + return OSS_EACCES; + + MUTEX_ENTER_IRQDISABLE (mixer->mutex, flags); + + portc->open_pending = 0; /* Was set to 1 by vmix_create_client */ + + if (portc->open_mode != 0) + { + MUTEX_EXIT_IRQRESTORE (mixer->mutex, flags); + return OSS_EBUSY; + } + + portc->open_mode = mode; + portc->trigger_bits = 0; + portc->play_mixing_func = NULL; + portc->rec_mixing_func = NULL; + portc->do_src = 0; + portc->play_choffs = 0; /* Left align */ + portc->rec_choffs = 0; /* Left align */ + +#ifdef CONFIG_OSS_VMIX_FLOAT + /* + * For the time being always enable local linear interpolation to make + * vmix devices to work faster. + */ + if (mixer->src_quality == 0) + portc->do_src = 1; + else +#endif + { + adev->src_quality = mixer->src_quality; + if (mixer->src_quality == 6) /* SRC=OFF */ + adev->cooked_enable = 0; + else + { + if (adev->src_quality < 1) + adev->src_quality = 1; + else if (adev->src_quality > 5) + adev->src_quality = 5; + } + } +#ifdef CONFIG_OSS_VMIX_FLOAT +/* + * Enable local src (linear interpolation) for mmap applications and SADA + * support (sadasupport.c). + */ + if (open_flags & (OF_DEVAUDIO | OF_MMAP)) + portc->do_src = 1; +#endif + + /* + * However SRC is not supported for input + */ + if (mode == PCM_ENABLE_INPUT) + portc->do_src = 0; + + if (mixer->open_devices++ == 0) + start = 1; + + if (mode & PCM_ENABLE_INPUT) + mixer->open_inputs++; + + MUTEX_EXIT_IRQRESTORE (mixer->mutex, flags); + + if (start) + { + int err; + + if ((err = start_engines (mixer)) < 0) + { + MUTEX_ENTER_IRQDISABLE (mixer->mutex, flags); + mixer->open_devices = 0; + mixer->open_inputs=0; + portc->open_mode = 0; + MUTEX_EXIT_IRQRESTORE (mixer->mutex, flags); + return err; + } + } + + return 0; +} + +/*ARGSUSED*/ +static void +vmix_close (int dev, int mode) +{ + vmix_portc_t *portc = audio_engines[dev]->portc; + vmix_mixer_t *mixer = portc->mixer; + oss_native_word flags; + int stop = 0; + + MUTEX_ENTER_IRQDISABLE (mixer->mutex, flags); + if (mixer->open_devices-- == 1) + stop = 1; + + if (mode & PCM_ENABLE_INPUT) + mixer->open_inputs--; + + portc->open_mode = 0; + portc->trigger_bits = 0; + portc->play_mixing_func = NULL; + portc->rec_mixing_func = NULL; + MUTEX_EXIT_IRQRESTORE (mixer->mutex, flags); + + if (stop) + { + stop_engines (mixer); + } + +} + +/*ARGSUSED*/ +static void +vmix_output_block (int dev, oss_native_word buf, int count, + int fragsize, int intrflag) +{ +} + +/*ARGSUSED*/ +static void +vmix_start_input (int dev, oss_native_word buf, int count, int fragsize, + int intrflag) +{ +} + +static void +vmix_trigger (int dev, int state) +{ + vmix_portc_t *portc = audio_engines[dev]->portc; + vmix_mixer_t *mixer = audio_engines[dev]->devc; + + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (mixer->mutex, flags); + if (portc->open_mode & OPEN_WRITE) + { + portc->trigger_bits &= ~PCM_ENABLE_OUTPUT; + portc->trigger_bits |= state & PCM_ENABLE_OUTPUT; + } + + if (portc->open_mode & OPEN_READ) + { + portc->trigger_bits &= ~PCM_ENABLE_INPUT; + portc->trigger_bits |= state & PCM_ENABLE_INPUT; + } + MUTEX_EXIT_IRQRESTORE (mixer->mutex, flags); +} + +/*ARGSUSED*/ +static int +vmix_prepare_for_input (int dev, int bsize, int bcount) +{ + vmix_portc_t *portc = audio_engines[dev]->portc; + /* int bytes; */ + + switch (portc->fmt) + { + case AFMT_S16_NE: + portc->rec_mixing_func = vmix_rec_export_16ne; + /* bytes = 2; */ + break; + + case AFMT_S16_OE: + portc->rec_mixing_func = vmix_rec_export_16oe; + /* bytes = 2; */ + break; + + case AFMT_S32_NE: + portc->rec_mixing_func = vmix_rec_export_32ne; + /* bytes = 4; */ + break; + + case AFMT_S32_OE: + portc->rec_mixing_func = vmix_rec_export_32oe; + /* bytes = 4; */ + break; + +#ifdef CONFIG_OSS_VMIX_FLOAT + case AFMT_FLOAT: + portc->rec_mixing_func = vmix_rec_export_float; + /* bytes = 4; */ + break; +#endif + } + + portc->rec_dma_pointer = 0; + return 0; +} + +/*ARGSUSED*/ +static int +vmix_prepare_for_output (int dev, int bsize, int bcount) +{ + vmix_portc_t *portc = audio_engines[dev]->portc; + /* int bytes; */ +#ifdef CONFIG_OSS_VMIX_FLOAT + vmix_mixer_t *mixer = audio_engines[dev]->devc; + + memset (&portc->play_dma_pointer_src, 0, sizeof (portc->play_dma_pointer_src)); /* 0.0 */ + + if (portc->rate != mixer->play_engine.rate) /* Sample rate conversions needed */ + { + switch (portc->fmt) + { + case AFMT_S16_NE: + portc->play_mixing_func = vmix_outmix_16ne_src; + /* bytes = 2; */ + break; + + case AFMT_S16_OE: + portc->play_mixing_func = vmix_outmix_16oe_src; + /* bytes = 2; */ + break; + + case AFMT_S32_NE: + portc->play_mixing_func = vmix_outmix_32ne_src; + /* bytes = 4; */ + break; + + case AFMT_S32_OE: + portc->play_mixing_func = vmix_outmix_32oe_src; + /* bytes = 4; */ + break; + + case AFMT_FLOAT: + portc->play_mixing_func = vmix_outmix_float_src; + /* bytes = 4; */ + break; + } + } + else +#endif + { + switch (portc->fmt) + { + case AFMT_S16_NE: + portc->play_mixing_func = vmix_outmix_16ne; + /* bytes = 2; */ + break; + + case AFMT_S16_OE: + portc->play_mixing_func = vmix_outmix_16oe; + /* bytes = 2; */ + break; + + case AFMT_S32_NE: + portc->play_mixing_func = vmix_outmix_32ne; + /* bytes = 4; */ + break; + + case AFMT_S32_OE: + portc->play_mixing_func = vmix_outmix_32oe; + /* bytes = 4; */ + break; + +#ifdef CONFIG_OSS_VMIX_FLOAT + case AFMT_FLOAT: + portc->play_mixing_func = vmix_outmix_float; + /* bytes = 4; */ + break; +#endif + } + } + + portc->play_dma_pointer = 0; + return 0; +} + +/*ARGSUSED*/ +static int +vmix_alloc_buffer (int dev, dmap_t * dmap, int direction) +{ + vmix_portc_t *portc = audio_engines[dev]->portc; + + /* Loopback devices share the DMA buffer of the master device */ + if (portc->dev_type == DT_LOOP) + { + adev_t *adev; + adev = audio_engines[portc->mixer->masterdev]; + + dmap->dmabuf_phys = 0; + dmap->dmabuf = adev->dmap_out->dmabuf; + dmap->buffsize = adev->dmap_out->buffsize; + dmap->buffer_protected = 1; /* Write protect flag for audio core */ + + return 0; + } + + if (dmap->dmabuf != NULL) + return 0; + +#ifdef ALLOW_BUFFER_MAPPING + /* + * Apps may use mmap() so allocate a buffer that is + * suitable for it. + */ + { + int err; + + if ((err = oss_alloc_dmabuf (dev, dmap, direction)) < 0) + return err; + } +#else +#define MY_BUFFSIZE (64*1024) + dmap->dmabuf_phys = 0; + dmap->dmabuf = KERNEL_MALLOC (MY_BUFFSIZE); + if (dmap->dmabuf == NULL) + return OSS_ENOSPC; + dmap->buffsize = MY_BUFFSIZE; +#endif + + return 0; +} + +/*ARGSUSED*/ +static int +vmix_free_buffer (int dev, dmap_t * dmap, int direction) +{ + vmix_portc_t *portc = audio_engines[dev]->portc; + + /* Loopback devices share the DMA buffer of the master device so don't free it */ + if (portc->dev_type == DT_LOOP) + { + dmap->dmabuf_phys = 0; + dmap->dmabuf = NULL; + dmap->buffsize = 0; + return 0; + } + + if (dmap->dmabuf == NULL) + return 0; +#ifdef ALLOW_BUFFER_MAPPING + oss_free_dmabuf (dev, dmap); +#else + KERNEL_FREE (dmap->dmabuf); +#endif + + dmap->dmabuf = NULL; + dmap->dmabuf_phys = 0; + return 0; +} + +/*ARGSUSED*/ +static int +vmix_get_output_buffer_pointer (int dev, dmap_t * dmap, int direction) +{ + vmix_portc_t *portc = audio_engines[dev]->portc; + + return portc->play_dma_pointer; +} + +/*ARGSUSED*/ +static int +vmix_get_input_buffer_pointer (int dev, dmap_t * dmap, int direction) +{ + vmix_portc_t *portc = audio_engines[dev]->portc; + + if (portc->dev_type == DT_LOOP) + { + int p; + /* Return the write pointer of the master side */ + adev_t *adev; + adev = audio_engines[portc->mixer->masterdev]; + + p = (int) (adev->dmap_out->user_counter % adev->dmap_out->bytes_in_use); + + return p; + } + + return portc->rec_dma_pointer; +} + +static int +vmix_local_qlen (int dev) +{ + vmix_portc_t *portc; + vmix_mixer_t *mixer; + int samplesize; + int len = 0; + + portc = audio_engines[dev]->portc; + mixer = audio_engines[dev]->devc; + + samplesize = 1; + if (portc->bits == AFMT_S16_NE) + samplesize *= 2; + samplesize *= portc->channels; + + /* Compute the number of samples in the physical device */ + oss_audio_ioctl (mixer->masterdev, NULL, SNDCTL_DSP_GETODELAY, + (ioctl_arg) & len); + + if (mixer->play_engine.bits == 16) + len /= 2; /* 16 bit samples */ + else + len /= 4; /* 32 bit samples */ + len /= mixer->play_engine.channels; + + /* Convert # of samples to local bytes */ + + len *= portc->channels; + + if (portc->bits == AFMT_S16_NE) + len *= 2; + + return len; +} + +/*ARGSUSED*/ +static void +vmix_setup_fragments (int dev, dmap_t * dmap, int direction) +{ +/* + * vmix_setup_fragments is used to force fragment/buffer parameters of + * loopback devices to match the DMA buffer of the physical device. + */ + vmix_portc_t *portc = audio_engines[dev]->portc; + adev_t *adev; + + if (portc->dev_type != DT_LOOP) + return; + + adev = audio_engines[portc->mixer->masterdev]; + + /* Copy the buffering parameters */ + dmap->fragment_size = adev->dmap_out->fragment_size; + dmap->nfrags = adev->dmap_out->nfrags; + dmap->bytes_in_use = adev->dmap_out->bytes_in_use; +} + +static audiodrv_t vmix_driver = { + vmix_open, + vmix_close, + vmix_output_block, + vmix_start_input, + vmix_ioctl, + vmix_prepare_for_input, + vmix_prepare_for_output, + vmix_reset, + vmix_local_qlen, + NULL, + vmix_halt_input, + vmix_halt_output, + vmix_trigger, + vmix_set_rate, + vmix_set_format, + vmix_set_channels, + NULL, + NULL, + NULL, + NULL, + vmix_alloc_buffer, + vmix_free_buffer, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + vmix_get_input_buffer_pointer, + vmix_get_output_buffer_pointer, + NULL, + vmix_setup_fragments +}; + +/* + * Initialization support + */ + +static void +relink_masterdev (vmix_mixer_t * mixer) +{ +/* + * Insert the VMIX devices to the engine search list of the master device. + */ + adev_t *first_adev, *last_adev, *master_adev; + int i, n = mixer->num_clientdevs; + + n = n - 1; + if (n < 1) + return; + + first_adev = audio_engines[mixer->client_portc[0]->audio_dev]; + last_adev = audio_engines[mixer->client_portc[n]->audio_dev]; + master_adev = audio_engines[mixer->masterdev]; + +/* + * Relink client devices in the proper way. + */ + + for (i=0;i<mixer->num_clientdevs;i++) + { + adev_t *adev = audio_engines[mixer->client_portc[i]->audio_dev]; + + if (i==mixer->num_clientdevs-1) + adev->next_out = NULL; + else + adev->next_out = audio_engines[mixer->client_portc[i+1]->audio_dev]; + } + + if (master_adev == NULL || master_adev->unloaded || !master_adev->enabled) + { + cmn_err (CE_WARN, "vmix: master_adev %d is not available\n", mixer->masterdev); + cmn_err (CE_CONT, "master_adev=%p, unloaded=%d, enabled=%d\n", master_adev, master_adev->unloaded, master_adev->enabled); + return; + } + + last_adev->next_out = NULL; + master_adev->next_out = first_adev; +} + +static void +unlink_masterdev (vmix_mixer_t * mixer) +{ +/* + * Remove the VMIX devices from the engine search list of the master device. + */ + adev_t *last_adev, *master_adev; + int i, n = mixer->num_clientdevs; + + if (n < 1) + return; + + if (n > MAX_CLIENTS) + n = MAX_CLIENTS; + + for (i=0;i<n;i++) + { + /* + * Mark all client engines as unloaded + */ + adev_t *adev = audio_engines[mixer->client_portc[i]->audio_dev]; + + adev->unloaded = 1; + } + + last_adev = audio_engines[mixer->client_portc[n-1]->audio_dev]; + master_adev = audio_engines[mixer->masterdev]; + + if (master_adev == NULL) + { + cmn_err (CE_WARN, "master_adev == NULL\n"); + return; + } + + master_adev->vmix_mixer=NULL; + + master_adev->next_out = last_adev->next_out; + last_adev->next_out = NULL; +} + +static void +relink_inputdev (vmix_mixer_t * mixer) +{ +/* + * Insert the VMIX devices to the engine search list of the input device. + */ + adev_t *first_adev, *last_adev, *input_adev; + int i, n = mixer->num_clientdevs; + +/* + * Relink client devices in the proper way. + */ + + for (i=0;i<mixer->num_clientdevs;i++) + { + adev_t *adev = audio_engines[mixer->client_portc[i]->audio_dev]; + + if (i==mixer->num_clientdevs-1) + adev->next_in = NULL; + else + adev->next_in = audio_engines[mixer->client_portc[i+1]->audio_dev]; + } + + if (n < 1) + return; + + n = n - 1; + + first_adev = audio_engines[mixer->client_portc[0]->audio_dev]; + last_adev = audio_engines[mixer->client_portc[n]->audio_dev]; + input_adev = audio_engines[mixer->inputdev]; + + last_adev->next_in = NULL; + input_adev->next_in = first_adev; +} + +static void +unlink_inputdev (vmix_mixer_t * mixer) +{ +/* + * Remove the VMIX devices from the engine search list of the input device. + */ + adev_t *last_adev, *input_adev; + int n = mixer->num_clientdevs; + + if (n < 1) + return; + + if (n > MAX_CLIENTS) + n = MAX_CLIENTS; + + n = n - 1; + + last_adev = audio_engines[mixer->client_portc[n]->audio_dev]; + input_adev = audio_engines[mixer->inputdev]; + input_adev->vmix_mixer=NULL; + + input_adev->next_in = last_adev->next_in; + last_adev->next_in = NULL; +} + +static int +create_vmix_engine (vmix_mixer_t * mixer) +{ + vmix_portc_t *portc; + int n; + char tmp[128]; + adev_t *adev, *master_adev; + int opts = ADEV_VIRTUAL | ADEV_DEFAULT | ADEV_VMIX; + + n = mixer->num_clientdevs; + + /* + * ADEV_HIDDEN is used for the VMIX devices because they should not be + * made visible to applications. The audio core will automatically + * redirect applications opening the master device to use the + * VMIX devices. + * + * However make the client devices visible if requested by vmixctl + */ + if (!(mixer->attach_flags & VMIX_INSTALL_VISIBLE)) + { + opts |= ADEV_HIDDEN; + + if (n > 0) + opts |= ADEV_SHADOW; + } + + if (mixer->masterdev == -1) + return OSS_ENXIO; + + if (n + 1 >= MAX_CLIENTS) /* Cannot create more client engines */ + return OSS_EBUSY; + + /* + * Other than the first instance are unlikely to be default the default + * audio device of the system. + */ + if (mixer->instance_num > 0) + opts |= ADEV_SPECIAL; + + if ((portc = PMALLOC (mixer->osdev, sizeof (*portc))) == NULL) + { + cmn_err (CE_WARN, "Cannot allocate portc structure\n"); + return OSS_ENOMEM; + } + memset (portc, 0, sizeof (*portc)); + portc->open_pending = 1; /* Reserve this engine to the client it was created for */ + + mixer->num_clientdevs++; + + portc->num = n; + + mixer->client_portc[n] = portc; + + if (mixer->inputdev == -1) + opts |= ADEV_NOINPUT; + else + opts |= ADEV_DUPLEX; + + master_adev = audio_engines[mixer->masterdev]; + + sprintf (tmp, "%s (vmix)", master_adev->name); + + if ((portc->audio_dev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION, + mixer->osdev, + mixer->master_osdev, + tmp, + &vmix_driver, + sizeof (audiodrv_t), + opts, + AFMT_S16_NE | AFMT_S32_NE | + AFMT_FLOAT, mixer, -1)) < 0) + { + return portc->audio_dev; + } + + adev = audio_engines[portc->audio_dev]; + + if (master_adev->d->adrv_ioctl_override != NULL) + adev->d->adrv_ioctl_override = vmix_ioctl_override; + + adev->mixer_dev = mixer->output_mixer_dev; + adev->portc = portc; + adev->min_rate = mixer->play_engine.rate; + adev->max_rate = mixer->play_engine.rate; + adev->caps |= PCM_CAP_FREERATE | PCM_CAP_BATCH | PCM_CAP_MULTI; + adev->min_channels = 2; + adev->max_channels = mixer->play_engine.channels; + + adev->rate_source = audio_engines[mixer->masterdev]->rate_source; + portc->dev_type = DT_OUT; + portc->mixer = mixer; + portc->volume[0] = DB_SIZE * 5; + portc->volume[1] = DB_SIZE * 5; + if (mixer->inputdev == -1) + portc->disabled_modes = OPEN_READ; + + portc->fmt = AFMT_S16_NE; + portc->bits = 16; + portc->channels = 2; + +#ifdef VDEV_SUPPORT + /* Report device node of the master device */ + if (opts & ADEV_HIDDEN) + strcpy (adev->devnode, master_adev->devnode); + oss_add_audio_devlist (OPEN_READ | OPEN_WRITE, master_adev->audio_devfile); +#endif + + if (n == 0 && mixer->masterdev < num_audio_engines + && audio_engines[mixer->masterdev] != NULL) + { + audio_engines[mixer->masterdev]->redirect_out = portc->audio_dev; + + if (mixer->inputdev > -1) + { + audio_engines[mixer->inputdev]->redirect_in = portc->audio_dev; + audio_engines[mixer->masterdev]->redirect_in = portc->audio_dev; + } + } + + relink_masterdev (mixer); + + if (mixer->inputdev > -1) + relink_inputdev (mixer); + + + return portc->audio_dev; +} + +static void +create_loopdev (vmix_mixer_t * mixer) +{ + vmix_portc_t *portc; + int n; + char tmp[128], nick[16]; + adev_t *adev; + int opts = ADEV_VIRTUAL | ADEV_NOOUTPUT; + + if (mixer->masterdev == -1) + return; + + if ((portc = PMALLOC (mixer->osdev, sizeof (*portc))) == NULL) + { + cmn_err (CE_WARN, "Cannot allocate portc structure\n"); + return; + } + memset (portc, 0, sizeof (*portc)); + + n = mixer->num_loopdevs++; + portc->num = n; + mixer->loop_portc[n] = portc; + + if (n > 0) + opts |= ADEV_SHADOW; + + adev = audio_engines[mixer->masterdev]; + + sprintf (tmp, "%s (vmix) loopback record", adev->name); + sprintf (nick, "loop%d", n); + + if ((portc->audio_dev = oss_install_audiodev_with_devname (OSS_AUDIO_DRIVER_VERSION, + mixer->osdev, + mixer->master_osdev, + tmp, + &vmix_driver, + sizeof (audiodrv_t), + opts, + mixer->play_engine.fmt, + mixer, -1, + nick)) < 0) + { + return; + } + + adev = audio_engines[portc->audio_dev]; + + adev->mixer_dev = mixer->output_mixer_dev; + adev->portc = portc; + adev->min_rate = mixer->play_engine.rate; + adev->max_rate = mixer->play_engine.rate; + adev->min_channels = mixer->play_engine.channels; + adev->max_channels = mixer->play_engine.channels; + adev->caps |= PCM_CAP_FREERATE | PCM_CAP_HIDDEN | PCM_CAP_BATCH | PCM_CAP_MULTI; + portc->mixer = mixer; + portc->dev_type = DT_LOOP; + portc->disabled_modes = OPEN_WRITE; + portc->fmt = AFMT_S16_NE; + portc->bits = 16; + portc->channels = 2; + portc->volume[0] = DB_SIZE * 5; + portc->volume[1] = DB_SIZE * 5; +} + +static int +uninit_vmix_instance(vmix_mixer_t *mixer) +{ + if (mixer->master_osdev != NULL) + { + MUTEX_CLEANUP (mixer->mutex); + } + + if (mixer->masterdev < 0 || mixer->masterdev >= num_audio_engines) + return OSS_ENXIO; + + if (!mixer->installed_ok) + { + return OSS_EIO; + } + + if (audio_engines[mixer->masterdev] == NULL) + return OSS_ENXIO; + +/* + * Cleanup the engine redirection links + */ + + audio_engines[mixer->masterdev]->redirect_out = -1; + + if (mixer->inputdev > -1) + { + audio_engines[mixer->inputdev]->redirect_in = -1; + audio_engines[mixer->masterdev]->redirect_in = -1; + } + unlink_masterdev (mixer); + + if (mixer->inputdev > -1 && mixer->inputdev != mixer->masterdev) + { + unlink_inputdev (mixer); + } + + return 0; +} + +static int +check_masterdev (void *mx, int reattach) +{ + vmix_mixer_t *mixer = mx; + adev_t *adev; + + if (mixer->masterdev < 0 || mixer->masterdev >= num_audio_engines) + return OSS_ENXIO; + + adev = audio_engines[mixer->masterdev]; + DDB (cmn_err + (CE_CONT, "Check masterdev eng=%d/%s\n", adev->engine_num, + adev->name)); + + /* Don't accept virtual devices other than loopback ones */ + if (adev->flags & ADEV_VIRTUAL && !(adev->flags & ADEV_LOOP)) + return OSS_EIO; + + if (adev->vmix_mixer != NULL) /* Already attached */ + { + cmn_err(CE_CONT, "Vmix already attached to %s\n", adev->devnode); + return OSS_EBUSY; + } + + if (adev->flags & ADEV_NOOUTPUT) + return OSS_EIO; + + if (adev->flags & ADEV_DISABLE_VIRTUAL) /* Not compatible */ + return OSS_EIO; + + if (adev->vmix_flags & VMIX_DISABLED) /* Not compatible */ + return OSS_EIO; + + if (adev->max_channels < 2) + return OSS_EIO; + + if (!(adev->oformat_mask & SUPPORTED_FORMATS)) + return OSS_EIO; + + if (mixer->inputdev != -1 && mixer->inputdev != mixer->masterdev) + if (audio_engines[mixer->inputdev]->vmix_mixer != NULL) /* Input master already driven */ + { + cmn_err(CE_CONT, "Vmix already attached to %s\n", audio_engines[mixer->inputdev]->devnode); + return OSS_EBUSY; + } + +/* + * Good. Initialize all per-mixer variables + */ + mixer->master_osdev = adev->master_osdev; + adev->vmix_mixer = mixer; + adev->prev_vmix_mixer = mixer; +//cmn_err(CE_CONT, "Calling MUTEX_INIT mast=%p, mixer=%p, mutex=%p\n", mixer->master_osdev, mixer,mixer->mutex); + MUTEX_INIT (mixer->master_osdev, mixer->mutex, MH_DRV + 4); +//cmn_err(CE_CONT, "OK\n"); + + DDB (cmn_err (CE_CONT, "Vmix masterdev=%d\n", mixer->masterdev)); + +/* + * The device is OK. Next check for the input/duplex capability. + */ + + mixer->vmix_flags = adev->vmix_flags; + mixer->max_channels = adev->max_channels; + + if (mixer->attach_flags & VMIX_INSTALL_NOINPUT) + mixer->vmix_flags |= VMIX_NOINPUT; + + if (mixer->vmix_flags & VMIX_NOINPUT) + mixer->inputdev = -1; + else if (!(adev->flags & ADEV_NOINPUT) && (adev->flags & ADEV_DUPLEX)) + { + mixer->inputdev = mixer->masterdev; + DDB (cmn_err + (CE_CONT, "Vmix masterdev=%d shared for input\n", + mixer->masterdev)); + } + +/* + * At this point we know the input and output master devices so it's the time + * to create the virtual audio devices. + */ + + adev = audio_engines[mixer->masterdev]; + + if (mixer->osdev->first_mixer >= 0) + { + mixer->output_mixer_dev = mixer->osdev->first_mixer; + mixer->input_mixer_dev = mixer->osdev->first_mixer; + } + + if (mixer->inputdev != -1 && mixer->inputdev != mixer->masterdev) + { + adev = audio_engines[mixer->inputdev]; + + adev->vmix_mixer = mixer; + adev->prev_vmix_mixer = mixer; + + if (adev->mixer_dev != -1) + { + mixer->input_mixer_dev = adev->mixer_dev; + } + } + + /* + * Warm up the engines so that client device creation knows the defaults + */ + start_engines (mixer); + stop_engines (mixer); + + mixer->installed_ok = 1; + + + if (!reattach) + { + if (mixer->output_mixer_dev > -1) + { + if (mixer->output_mixer_dev == mixer->input_mixer_dev) + { + mixer_ext_set_vmix_init_fn (mixer->output_mixer_dev, + create_duplex_controls, 20, mixer); + } + else + { + mixer_ext_set_vmix_init_fn (mixer->output_mixer_dev, + create_output_controls, 20, mixer); + } + } + + if (mixer->output_mixer_dev != mixer->input_mixer_dev) + if (mixer->inputdev >= 0 && + mixer->input_mixer_dev > -1) + { + mixer_ext_set_vmix_init_fn (mixer->input_mixer_dev, + create_input_controls, 10, mixer); + } + } +/* + * Crate one client in advance so that that SNDCTL_AUDIOINFO can provide proper info. + */ + if (!(mixer->attach_flags & VMIX_INSTALL_NOPREALLOC)) + { + int cl, n=4; + + if ((mixer->attach_flags & 0xff) != 0) /* Number of clients given */ + { + n = mixer->attach_flags & 0xff; + if (n<1) n=1; + if (n>=MAX_CLIENTS) n=MAX_CLIENTS-1; /* TODO: Why n=MAX_CLIENTS doesn't work? */ + } + + for (cl=0;cl<n;cl++) + vmix_create_client (mixer); + + /* + * Mark the engines to be free for use + */ + for (cl=0;cl<mixer->num_clientdevs;cl++) + mixer->client_portc[cl]->open_pending = 0; + } + + if (vmix_loopdevs>0) + { + int i; + + for (i=0;i<vmix_loopdevs;i++) + create_loopdev (mixer); + } + + DDB (cmn_err (CE_CONT, "Master dev %d is OK\n", adev->engine_num)); + + return 0; +} + +int +vmix_attach_audiodev(oss_device_t *osdev, int masterdev, int inputdev, unsigned int attach_flags) +{ +/* + * Purpose: Create a vmix instance for an audio device. + * + * This function will be called by OSS drivers after installing the audio devices. It will create + * an vmix instance for the device. + * + * Parameters: + * + * osdev: The osdev structure of the actual hardware device. + * masterdev: The audio engine number of the master (playback or duplex) device. + * inputdev: Input master device (if different than masterdev). Value of -1 means that the + * masterdev device should also be used as the input master device (if it supports + * input). + * attach_flags: Flags like VMIX_INSTALL_NOPREALLOC. + */ + + vmix_mixer_t *mixer=NULL; + int reattach=0; + int err; + int i; + + if (vmix_disabled) /* Vmix not available in the system */ + return OSS_EIO; + + /* + * If the vmix_no_autoattach option is set in osscore.conf then attach + * vmix only when 'vmixctl attach' is executed manually (VMIX_INSTALL_MANUAL). + */ + if (vmix_no_autoattach && !(attach_flags & VMIX_INSTALL_MANUAL)) + return 0; + + if (flat_device_model) + { + attach_flags |= VMIX_INSTALL_VISIBLE; + attach_flags = (attach_flags & ~0xff) | 8; /* Precreate 8 client engines */ + } + + if (audio_engines[masterdev]->prev_vmix_mixer != NULL) + { + mixer = audio_engines[masterdev]->prev_vmix_mixer; + + if (mixer->attach_flags != attach_flags) /* Not compatible */ + mixer=NULL; + } + + if (mixer == NULL) + { + if ((mixer = PMALLOC (osdev, sizeof (*mixer))) == NULL) + { + cmn_err (CE_CONT, "Cannot allocate memory for instance descriptor\n"); + return OSS_ENOMEM; + } + + memset (mixer, 0, sizeof (*mixer)); + mixer->instance_num = num_instances++; + if (mixer->instance_num > 0) + mixer->osdev = osdev_clone (osdev, mixer->instance_num); + } + else + { + relink_masterdev (mixer); + + if (mixer->inputdev > -1) + relink_inputdev (mixer); + + reattach=1; + } + + mixer->osdev = osdev; + mixer->first_input_mixext = -1; + mixer->first_output_mixext = -1; + mixer->src_quality = 0; + + mixer->output_mixer_dev = -1; + mixer->input_mixer_dev = -1; + + /* + * Mixer default levels + */ + mixer->play_engine.outvol = DB_SIZE * 5; + mixer->record_engine.outvol = DB_SIZE * 5; +#ifndef CONFIG_OSS_VMIX_FLOAT + mixer->play_engine.outvol -= 3; /* For overflow protection */ +#endif + for (i = 0; i < sizeof (vmix_channel_map_t); i++) + mixer->play_engine.channel_order[i] = i; + + mixer->masterdev = masterdev; + mixer->inputdev = inputdev; + mixer->rate = 48000; + mixer->attach_flags = attach_flags; + + DDB (cmn_err (CE_CONT, "Create instance %d\n", num_instances)); + DDB (cmn_err (CE_CONT, "vmix_masterdev=%d\n", masterdev)); + DDB (cmn_err (CE_CONT, "vmix_inputdev=%d\n", inputdev)); + DDB (cmn_err (CE_CONT, "vmix_rate=%d\n", mixer->rate)); + DDB (cmn_err (CE_CONT, "\n")); + + /* + * Insert the newly created mixer to the mixer list. + */ + mixer->next = mixer_list; + mixer_list = mixer; + + if (masterdev >= 0) + { + if (masterdev >= num_audio_engines) + { + return OSS_ENXIO; + } + + masterdev = mixer->masterdev; + + if (mixer->inputdev >= 0) + { + if (inputdev >= num_audio_engines) + inputdev = mixer->inputdev = -1; + } + + if ((err=check_masterdev (mixer, reattach))<0) + { + cmn_err (CE_CONT, "Vmix instance %d: Master device %d is not compatible with vmix (error %d)\n", + mixer->instance_num + 1, mixer->masterdev, err); + return err; + } + + return 0; + } + + return OSS_EIO; +} + +int +vmix_detach_audiodev(int masterdev) +{ +/* + * Purpose: Detach the vmix subsystem from the audio device. + * + * Most drivers don't call this since vmix instances will be automatically detached when the + * master device is removed. However drivers that support dynamically created/deleted devices must + * call this when a device is deleted. + * + * Paramaters: + * + * masterdev: The audio engine number of the master device (same as in vmix_attach_audiodev). + */ + + vmix_mixer_t *mixer; + + if (masterdev<0 || masterdev>=num_audio_engines) + return OSS_ENXIO; + + mixer = audio_engines[masterdev]->vmix_mixer; + + if (mixer==NULL) /* Not attached */ + return 0; + + return uninit_vmix_instance(mixer); +} + +int +vmix_set_master_rate(int masterdev, int rate) +{ +/* + * Purpose: Set the master sampling rate of given vmix instance. + * + * Paramaters: + * + * masterdev: The audio engine number of the master device (same as in vmix_attach_audiodev). + * rate: The requested new sampling rate. + */ + + vmix_mixer_t *mixer; + + if (rate < 4000 || rate > 200000) + return OSS_EDOM; + + if (masterdev<0 || masterdev>=num_audio_engines) + return OSS_ENXIO; + + mixer = audio_engines[masterdev]->vmix_mixer; + + if (mixer==NULL) + return OSS_ENXIO; + + mixer->rate = rate; + + return 0; +} + +int +vmix_set_channel_map (int masterdev, void * map) +{ + vmix_mixer_t *mixer; + + if (masterdev < 0 || masterdev >= num_audio_engines) + return OSS_ENXIO; + + mixer = audio_engines[masterdev]->vmix_mixer; + + if (mixer == NULL) + return OSS_EPERM; + + return vmix_process_chninfo (&mixer->play_engine.channel_order, map, + mixer->play_engine.channels); +} + +int +vmix_create_client(void *mixer_) +{ + int engine_num=-1, i; + oss_native_word flags; + vmix_portc_t *portc; + vmix_mixer_t *mixer = mixer_; + + if (mixer->disabled) /* Vmix is disabled for the time being */ + return OSS_ENXIO; + +/* + * First check if any of the already created engines is free and available for use. + */ + + MUTEX_ENTER_IRQDISABLE (mixer->mutex, flags); + for (i=0;i<mixer->num_clientdevs;i++) + { + portc = mixer->client_portc[i]; + + if (portc->open_mode != 0 || portc->open_pending) + continue; + + portc->open_pending = 1; + engine_num = portc->audio_dev; + break; + } + MUTEX_EXIT_IRQRESTORE (mixer->mutex, flags); + +/* + * Create a new engine and use it + */ + + if (engine_num < 0) /* Engine not allocated yet */ + { + if ((engine_num = create_vmix_engine (mixer))<0) + { + cmn_err(CE_WARN, "Failed to create a vmix engine, error=%d\n", engine_num); + return engine_num; + } + + portc = audio_engines[engine_num]->portc; + create_client_controls (mixer, portc->num); + } + + portc = audio_engines[engine_num]->portc; + + /* portc->open_pending = 1; // This was done already by create_vmix_engine() */ + return engine_num; +} + +void +vmix_core_init (oss_device_t *osdev) +{ +#ifdef CONFIG_OSS_VMIX_FLOAT + int check; +/* + * Check that the processor is compatible with vmix (has proper FP support). + */ + + if ((check = oss_fp_check ()) <= 0) + { + vmix_disabled = 1; + cmn_err (CE_WARN, + "This processor architecture is not compatible with vmix (info=%d) - Not enabled.\n", + check); + return; + } +#endif +} + +void +vmix_core_uninit (void) +{ + vmix_mixer_t *mixer = mixer_list; + int n = 0; + + while (mixer != NULL && n++ < num_instances) + { + uninit_vmix_instance(mixer); + mixer = mixer->next; + } + + mixer_list = NULL; /* Everything removed */ +} + +void +vmix_change_devnames(void *vmix_mixer, const char *name) +{ +/* + * Change audio device names of all client engines. + */ + vmix_mixer_t *mixer = vmix_mixer; + int i; + + for (i=0;i<mixer->num_clientdevs;i++) + { + adev_t *adev = audio_engines[mixer->client_portc[i]->audio_dev]; + + sprintf(adev->name, "%s (vmix)", name); + } +} diff --git a/kernel/framework/vmix_core/vmix_import.inc b/kernel/framework/vmix_core/vmix_import.inc new file mode 100644 index 0000000..dc4a965 --- /dev/null +++ b/kernel/framework/vmix_core/vmix_import.inc @@ -0,0 +1,77 @@ +#ifdef CONFIG_OSS_VMIX_FLOAT +/* + * Purpose: Recording device to local input buffer import routine for vmix + */ +/* + * + * 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. + * + */ + +int i, ch; +float vol; + +vol = vmix_db_table[eng->outvol / 5]; + +for (ch = 0; ch < channels; ch++) + { + float vu; + float *chbuf; + + vu = eng->vu[ch % 2]; + vu = vu / 255.0; + + op = (SAMPLE_TYPE *) inbuf; + op += ch; + + chbuf = chbufs[ch]; + + for (i = 0; i < samples; i++) + { + float tmp; + +#if 0 && defined(SINE_DEBUG) + /* Generate internal sine wave test signal */ + tmp = 0; + if (ch < 2) + { + tmp = sine_table[sine_phase[ch]]; + sine_phase[ch] = (sine_phase[ch] + 1) % SINE_SIZE; + } +#else + tmp = VMIX_BYTESWAP (*op); + tmp /= SAMPLE_RANGE; + tmp *= vol; + + if (tmp < -1.0) + tmp = -1.0; + else if (tmp > 1.0) + tmp = 1.0; + +#endif + op += channels; + + *chbuf++ = tmp; + + /* VU meter */ + if (tmp < 0.0) + tmp = -tmp; + if (tmp > vu) + vu = tmp; + } + + if (ch < 2) + { + vu = vu * 255.0; + eng->vu[ch] = (int)vu; + } + } +#else +#include "vmix_import_int.inc" +#endif diff --git a/kernel/framework/vmix_core/vmix_import_int.inc b/kernel/framework/vmix_core/vmix_import_int.inc new file mode 100644 index 0000000..338078d --- /dev/null +++ b/kernel/framework/vmix_core/vmix_import_int.inc @@ -0,0 +1,56 @@ +/* + * Purpose: Recording device to local input buffer import routine for vmix (int) + */ +/* + * + * 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. + * + */ + +int i, ch; +int vol; + +vol = vmix_db_table[eng->outvol / 5]; + +for (ch = 0; ch < channels; ch++) + { + int vu; + int *chbuf; + + vu = eng->vu[ch % 2] * 65536; + + op = (SAMPLE_TYPE *) inbuf; + op += ch; + + chbuf = chbufs[ch]; + + for (i = 0; i < samples; i++) + { + int tmp; + + tmp = INT_IMPORT (VMIX_BYTESWAP (*op)); + tmp = (tmp * vol) / VMIX_VOL_SCALE; + + op += channels; + + *chbuf++ = tmp; + + /* VU meter */ + if (tmp < 0) + tmp = -tmp; + if (tmp > vu) + vu = tmp; + } + + if (ch < 2) + { + vu = vu / 65536; + eng->vu[ch] = vu; + } + } diff --git a/kernel/framework/vmix_core/vmix_input.c b/kernel/framework/vmix_core/vmix_input.c new file mode 100644 index 0000000..0e90b48 --- /dev/null +++ b/kernel/framework/vmix_core/vmix_input.c @@ -0,0 +1,505 @@ +/* + * Purpose: Virtual mixing audio driver recording routines + */ +/* + * + * 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[2] = { 0 }; +#endif + +/* + * Recording import functions (from the physical devices) + */ +#undef INT_IMPORT +#define INT_IMPORT(x) (x * 256) + +static void +import16ne (vmix_engine_t * eng, void *inbuf, 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 "vmix_import.inc" +} + +static void +import16oe (vmix_engine_t * eng, void *inbuf, 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 "vmix_import.inc" +} + +#undef INT_IMPORT +#define INT_IMPORT(x) (x / 256) + +static void +import32ne (vmix_engine_t * eng, void *inbuf, 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 "vmix_import.inc" +} + +static void +import32oe (vmix_engine_t * eng, void *inbuf, 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 "vmix_import.inc" +} + +/* + * recording export functions to virtual devices + */ +#undef BUFFER_TYPE +#define BUFFER_TYPE short * + +#undef INT_EXPORT +#define INT_EXPORT(x) (x / 256) + +void +vmix_rec_export_16ne (vmix_portc_t * portc, int nsamples) +{ + short *outp; +#undef VMIX_BYTESWAP +#define VMIX_BYTESWAP(x) x +#ifdef CONFIG_OSS_VMIX_FLOAT + double range = 32767.0; +#endif +#include "rec_export.inc" +} + +void +vmix_rec_export_16oe (vmix_portc_t * portc, int nsamples) +{ + short *outp; +#undef VMIX_BYTESWAP +#define VMIX_BYTESWAP(x) bswap16(x) +#ifdef CONFIG_OSS_VMIX_FLOAT + double range = 32767.0; +#endif +#include "rec_export.inc" +} + +#undef BUFFER_TYPE +#define BUFFER_TYPE int * +#undef INT_EXPORT +#define INT_EXPORT(x) (x * 256) + +void +vmix_rec_export_32ne (vmix_portc_t * portc, int nsamples) +{ + int *outp; +#undef VMIX_BYTESWAP +#define VMIX_BYTESWAP(x) x +#ifdef CONFIG_OSS_VMIX_FLOAT + double range = 2147483647.0; +#endif +#include "rec_export.inc" +} + +void +vmix_rec_export_32oe (vmix_portc_t * portc, int nsamples) +{ + int *outp; +#undef VMIX_BYTESWAP +#define VMIX_BYTESWAP(x) bswap32(x) +#ifdef CONFIG_OSS_VMIX_FLOAT + double range = 2147483647.0; +#endif +#include "rec_export.inc" +} + +#ifdef CONFIG_OSS_VMIX_FLOAT +void +vmix_rec_export_float (vmix_portc_t * portc, int nsamples) +{ + float *outp; +#undef BUFFER_TYPE +#define BUFFER_TYPE float * +#undef VMIX_BYTESWAP +#define VMIX_BYTESWAP(x) x + double range = 1.0; +#include "rec_export.inc" +} +#endif + +static void +vmix_record_callback (int dev, int parm) +{ + int i, n; + int do_input = 0; + + adev_t *adev = audio_engines[dev]; + dmap_t *dmap = adev->dmap_in; + oss_native_word flags; + + vmix_mixer_t *mixer = adev->vmix_mixer; + vmix_engine_t *eng = &mixer->record_engine; + +#ifdef CONFIG_OSS_VMIX_FLOAT + fp_env_t fp_buf; + short *fp_env = fp_buf; + fp_flags_t fp_flags; +#endif + + if (mixer == NULL) /* Houston, we have a problem. */ + return; + + /* + * Check if any input applications are active. Skip input processing + * if it's not needed (to save CPU cycles). + */ + + for (i = 0; i < mixer->num_clientdevs; i++) + if (mixer->client_portc[i]->trigger_bits & PCM_ENABLE_INPUT) + do_input = 1; + + if (!do_input) /* Skip all input processing */ + { + n = 0; + while (n++ < dmap->nfrags + && (int) (dmap->byte_counter - dmap->user_counter) >= + dmap->fragment_size) + { + dmap->user_counter += dmap->fragment_size; + } + return; + } + + UP_STATUS (0x02); + MUTEX_ENTER_IRQDISABLE (mixer->mutex, flags); +#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++ < dmap->nfrags + && (int) (dmap->byte_counter - dmap->user_counter) >= + dmap->fragment_size) + { + int i, p; + unsigned char *inbuf; + + if (!do_input) + { + /* + * Just skip the recorded data becaus nobody needs it. + */ + dmap->user_counter += dmap->fragment_size; + continue; + } + + for (i = 0; i < eng->channels; i++) + { + memset (eng->chbufs[i], 0, CHBUF_SAMPLES * sizeof (vmix_sample_t)); + } + + p = (int) (dmap->user_counter % dmap->bytes_in_use); + inbuf = dmap->dmabuf + p; + + eng->converter (eng, inbuf, eng->chbufs, eng->channels, + eng->samples_per_frag); + + for (i = 0; i < mixer->num_clientdevs; i++) + { + vmix_portc_t *portc = mixer->client_portc[i]; + + if (portc->trigger_bits & PCM_ENABLE_INPUT) + { + if (portc->rec_mixing_func == NULL) + continue; + if (portc->rec_choffs + portc->channels > + mixer->record_engine.channels) + portc->rec_choffs = 0; + portc->rec_mixing_func (portc, + mixer->record_engine.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_inputintr 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_INPUT) + { + vmix_portc_t *portc = mixer->client_portc[i]; + oss_audio_inputintr (portc->audio_dev, 0); + } + DOWN_STATUS (0x02); +} + +void +finalize_record_engine (vmix_mixer_t * mixer, int fmt, adev_t * adev, + dmap_p dmap) +{ + int i; + + switch (fmt) + { + case AFMT_S16_NE: + mixer->record_engine.bits = 16; + mixer->record_engine.converter = import16ne; + break; + + case AFMT_S16_OE: + mixer->record_engine.bits = 16; + mixer->record_engine.converter = import16oe; + break; + + case AFMT_S32_NE: + mixer->record_engine.bits = 32; + mixer->record_engine.converter = import32ne; + break; + + case AFMT_S32_OE: + mixer->record_engine.bits = 32; + mixer->record_engine.converter = import32oe; + break; + + default: + cmn_err (CE_CONT, "Unrecognized recording sample format %x\n", fmt); + return; + } + + mixer->record_engine.fragsize = dmap->fragment_size; + + mixer->record_engine.samples_per_frag = + mixer->record_engine.fragsize / mixer->record_engine.channels / + (mixer->record_engine.bits / 8); + + if (mixer->record_engine.samples_per_frag > CHBUF_SAMPLES) + { + cmn_err (CE_WARN, "Too many samples per fragment (%d,%d)\n", + mixer->record_engine.samples_per_frag, CHBUF_SAMPLES); + return; + } + + for (i = 0; i < mixer->record_engine.channels; i++) + if (mixer->record_engine.chbufs[i] == NULL) /* Not allocated yet */ + { + mixer->record_engine.chbufs[i] = + PMALLOC (mixer->master_osdev, + CHBUF_SAMPLES * sizeof (vmix_sample_t)); + if (mixer->record_engine.chbufs[i] == NULL) + { + cmn_err (CE_WARN, "Out of memory\n"); + return; + } + } + + dmap->audio_callback = vmix_record_callback; /* Enable conversions */ + dmap->callback_parm = mixer->instance_num; + dmap->dma_mode = PCM_ENABLE_INPUT; + + if (mixer->num_clientdevs > 1) + { + adev->redirect_out = mixer->client_portc[0]->audio_dev; + adev->vmix_mixer = mixer; + } + vmix_record_callback (mixer->inputdev, mixer->instance_num); +} + +void +vmix_setup_record_engine (vmix_mixer_t * mixer, adev_t * adev, dmap_t * dmap) +{ + int fmt; + int old_min; + int frags = 0x7fff0007; /* fragment size of 128 bytes */ + +/* + * Sample format (and endianess) setup + * + */ + + // First make sure a sane format is selected before starting to probe + fmt = adev->d->adrv_set_format (mixer->inputdev, AFMT_S16_LE); + fmt = adev->d->adrv_set_format (mixer->inputdev, AFMT_S16_NE); + + // Find out the "best" sample format supported by the device + + if (adev->iformat_mask & AFMT_S16_OE) + fmt = AFMT_S16_OE; + if (adev->iformat_mask & AFMT_S16_NE) + fmt = AFMT_S16_NE; + if (mixer->multich_enable) + { + if (adev->iformat_mask & AFMT_S32_OE) + fmt = AFMT_S32_OE; + if (adev->iformat_mask & AFMT_S32_NE) + fmt = AFMT_S32_NE; + } + + fmt = adev->d->adrv_set_format (mixer->inputdev, fmt); + mixer->record_engine.fmt = fmt; + +/* + * Number of channels + */ + mixer->record_engine.channels = mixer->max_channels; + + if (mixer->record_engine.channels > MAX_REC_CHANNELS) + mixer->record_engine.channels = MAX_REC_CHANNELS; + + if (!mixer->multich_enable) + mixer->record_engine.channels = 2; + + /* Force the device to stereo before trying with (possibly) imultiple channels */ + adev->d->adrv_set_channels (mixer->inputdev, 2); + + mixer->record_engine.channels = + adev->d->adrv_set_channels (mixer->inputdev, + mixer->record_engine.channels); + + if (mixer->record_engine.channels > MAX_REC_CHANNELS) + { + cmn_err (CE_WARN, + "Number of channels (%d) is larger than maximum (%d)\n", + mixer->record_engine.channels, MAX_REC_CHANNELS); + return; + } + + /* + * Try to set the same rate than for playback. + */ + mixer->record_engine.rate = + oss_audio_set_rate (mixer->inputdev, mixer->play_engine.rate); + + if (mixer->record_engine.rate <= 22050) + frags = 0x7fff0004; /* Use smaller fragments */ + + audio_engines[mixer->inputdev]->hw_parms.channels = + mixer->record_engine.channels; + audio_engines[mixer->inputdev]->hw_parms.rate = mixer->record_engine.rate; + audio_engines[mixer->inputdev]->dmap_in->data_rate = + mixer->record_engine.rate * mixer->record_engine.channels * + mixer->record_engine.bits / 8; + audio_engines[mixer->inputdev]->dmap_in->frame_size = + mixer->record_engine.channels * mixer->record_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->inputdev, NULL, SNDCTL_DSP_SETFRAGMENT, + (ioctl_arg) & frags); + oss_audio_ioctl (mixer->inputdev, NULL, SNDCTL_DSP_GETBLKSIZE, + (ioctl_arg) & mixer->record_engine.fragsize); + + dmap->bytes_in_use = dmap->fragment_size * dmap->nfrags; + + oss_audio_ioctl (mixer->inputdev, NULL, SNDCTL_DSP_GETBLKSIZE, + (ioctl_arg) & mixer->record_engine.fragsize); + mixer->record_engine.fragsize = dmap->fragment_size; + adev->min_fragments = old_min; + + if (mixer->record_engine.channels > 2) + { + DDB (cmn_err + (CE_CONT, "Enabling multi channel rec mode, %d hw channels\n", + mixer->record_engine.channels)); + } + else if (mixer->record_engine.channels != 2) + { + cmn_err (CE_WARN, + "Master device doesn't support suitable channel configuration\n"); + + return; + } + + finalize_record_engine (mixer, fmt, adev, dmap); +} 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); +} |