90da2b2859
For a slightly thorough explaination, please refer to [1] http://people.freebsd.org/~ariff/SOUND_4.TXT.html . Summary of changes includes: 1 Volume Per-Channel (vpc). Provides private / standalone volume control unique per-stream pcm channel without touching master volume / pcm. Applications can directly use SNDCTL_DSP_[GET|SET][PLAY|REC]VOL, or for backwards compatibility, SOUND_MIXER_PCM through the opened dsp device instead of /dev/mixer. Special "bypass" mode is enabled through /dev/mixer which will automatically detect if the adjustment is made through /dev/mixer and forward its request to this private volume controller. Changes to this volume object will not interfere with other channels. Requirements: - SNDCTL_DSP_[GET|SET][PLAY|REC]_VOL are newer ioctls (OSSv4) which require specific application modifications (preferred). - No modifications required for using bypass mode, so applications like mplayer or xmms should work out of the box. Kernel hints: - hint.pcm.%d.vpc (0 = disable vpc). Kernel sysctls: - hw.snd.vpc_mixer_bypass (default: 1). Enable or disable /dev/mixer bypass mode. - hw.snd.vpc_autoreset (default: 1). By default, closing/opening /dev/dsp will reset the volume back to 0 db gain/attenuation. Setting this to 0 will preserve its settings across device closing/opening. - hw.snd.vpc_reset (default: 0). Panic/reset button to reset all volume settings back to 0 db. - hw.snd.vpc_0db (default: 45). 0 db relative to linear mixer value. 2 High quality fixed-point Bandlimited SINC sampling rate converter, based on Julius O'Smith's Digital Audio Resampling - http://ccrma.stanford.edu/~jos/resample/. It includes a filter design script written in awk (the clumsiest joke I've ever written) - 100% 32bit fixed-point, 64bit accumulator. - Possibly among the fastest (if not fastest) of its kind. - Resampling quality is tunable, either runtime or during kernel compilation (FEEDER_RATE_PRESETS). - Quality can be further customized during kernel compilation by defining FEEDER_RATE_PRESETS in /etc/make.conf. Kernel sysctls: - hw.snd.feeder_rate_quality. 0 - Zero-order Hold (ZOH). Fastest, bad quality. 1 - Linear Interpolation (LINEAR). Slightly slower than ZOH, better quality but still does not eliminate aliasing. 2 - (and above) - Sinc Interpolation(SINC). Best quality. SINC quality always start from 2 and above. Rough quality comparisons: - http://people.freebsd.org/~ariff/z_comparison/ 3 Bit-perfect mode. Bypasses all feeder/dsp effects. Pure sound will be directly fed into the hardware. 4 Parametric (compile time) Software Equalizer (Bass/Treble mixer). Can be customized by defining FEEDER_EQ_PRESETS in /etc/make.conf. 5 Transparent/Adaptive Virtual Channel. Now you don't have to disable vchans in order to make digital format pass through. It also makes vchans more dynamic by choosing a better format/rate among all the concurrent streams, which means that dev.pcm.X.play.vchanformat/rate becomes sort of optional. 6 Exclusive Stream, with special open() mode O_EXCL. This will "mute" other concurrent vchan streams and only allow a single channel with O_EXCL set to keep producing sound. Other Changes: * most feeder_* stuffs are compilable in userland. Let's not speculate whether we should go all out for it (save that for FreeBSD 16.0-RELEASE). * kobj signature fixups, thanks to Andriy Gapon <avg@freebsd.org> * pull out channel mixing logic out of vchan.c and create its own feeder_mixer for world justice. * various refactoring here and there, for good or bad. * activation of few more OSSv4 ioctls() (see [1] above). * opt_snd.h for possible compile time configuration: (mostly for debugging purposes, don't try these at home) SND_DEBUG SND_DIAGNOSTIC SND_FEEDER_MULTIFORMAT SND_FEEDER_FULL_MULTIFORMAT SND_FEEDER_RATE_HP SND_PCM_64 SND_OLDSTEREO Manual page updates are on the way. Tested by: joel, Olivier SMEDTS <olivier at gid0 d org>, too many unsung / unnamed heroes.
844 lines
22 KiB
C
844 lines
22 KiB
C
/*-
|
|
* Copyright (c) 2008-2009 Ariff Abdullah <ariff@FreeBSD.org>
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#ifdef HAVE_KERNEL_OPTION_HEADERS
|
|
#include "opt_snd.h"
|
|
#endif
|
|
|
|
#include <dev/sound/pcm/sound.h>
|
|
|
|
#include "feeder_if.h"
|
|
|
|
SND_DECLARE_FILE("$FreeBSD$");
|
|
|
|
/* chain state */
|
|
struct feeder_chain_state {
|
|
uint32_t afmt; /* audio format */
|
|
uint32_t rate; /* sampling rate */
|
|
struct pcmchan_matrix *matrix; /* matrix map */
|
|
};
|
|
|
|
/*
|
|
* chain descriptor that will be passed around from the beginning until the
|
|
* end of chain process.
|
|
*/
|
|
struct feeder_chain_desc {
|
|
struct feeder_chain_state origin; /* original state */
|
|
struct feeder_chain_state current; /* current state */
|
|
struct feeder_chain_state target; /* target state */
|
|
struct pcm_feederdesc desc; /* feeder descriptor */
|
|
uint32_t afmt_ne; /* prefered native endian */
|
|
int mode; /* chain mode */
|
|
int use_eq; /* need EQ? */
|
|
int use_matrix; /* need channel matrixing? */
|
|
int use_volume; /* need softpcmvol? */
|
|
int dummy; /* dummy passthrough */
|
|
int expensive; /* possibly expensive */
|
|
};
|
|
|
|
#define FEEDER_CHAIN_LEAN 0
|
|
#define FEEDER_CHAIN_16 1
|
|
#define FEEDER_CHAIN_32 2
|
|
#define FEEDER_CHAIN_MULTI 3
|
|
#define FEEDER_CHAIN_FULLMULTI 4
|
|
#define FEEDER_CHAIN_LAST 5
|
|
|
|
#if defined(SND_FEEDER_FULL_MULTIFORMAT)
|
|
#define FEEDER_CHAIN_DEFAULT FEEDER_CHAIN_FULLMULTI
|
|
#elif defined(SND_FEEDER_MULTIFORMAT)
|
|
#define FEEDER_CHAIN_DEFAULT FEEDER_CHAIN_MULTI
|
|
#else
|
|
#define FEEDER_CHAIN_DEFAULT FEEDER_CHAIN_LEAN
|
|
#endif
|
|
|
|
/*
|
|
* List of prefered formats that might be required during
|
|
* processing. It will be decided through snd_fmtbest().
|
|
*/
|
|
|
|
/* 'Lean' mode, signed 16 or 32 bit native endian. */
|
|
static uint32_t feeder_chain_formats_lean[] = {
|
|
AFMT_S16_NE, AFMT_S32_NE,
|
|
0
|
|
};
|
|
|
|
/* Force everything to signed 16 bit native endian. */
|
|
static uint32_t feeder_chain_formats_16[] = {
|
|
AFMT_S16_NE,
|
|
0
|
|
};
|
|
|
|
/* Force everything to signed 32 bit native endian. */
|
|
static uint32_t feeder_chain_formats_32[] = {
|
|
AFMT_S32_NE,
|
|
0
|
|
};
|
|
|
|
/* Multiple choices, all except 8 bit. */
|
|
static uint32_t feeder_chain_formats_multi[] = {
|
|
AFMT_S16_LE, AFMT_S16_BE, AFMT_U16_LE, AFMT_U16_BE,
|
|
AFMT_S24_LE, AFMT_S24_BE, AFMT_U24_LE, AFMT_U24_BE,
|
|
AFMT_S32_LE, AFMT_S32_BE, AFMT_U32_LE, AFMT_U32_BE,
|
|
0
|
|
};
|
|
|
|
/* Everything that is convertible. */
|
|
static uint32_t feeder_chain_formats_fullmulti[] = {
|
|
AFMT_S8, AFMT_U8,
|
|
AFMT_S16_LE, AFMT_S16_BE, AFMT_U16_LE, AFMT_U16_BE,
|
|
AFMT_S24_LE, AFMT_S24_BE, AFMT_U24_LE, AFMT_U24_BE,
|
|
AFMT_S32_LE, AFMT_S32_BE, AFMT_U32_LE, AFMT_U32_BE,
|
|
0
|
|
};
|
|
|
|
static uint32_t *feeder_chain_formats[FEEDER_CHAIN_LAST] = {
|
|
[FEEDER_CHAIN_LEAN] = feeder_chain_formats_lean,
|
|
[FEEDER_CHAIN_16] = feeder_chain_formats_16,
|
|
[FEEDER_CHAIN_32] = feeder_chain_formats_32,
|
|
[FEEDER_CHAIN_MULTI] = feeder_chain_formats_multi,
|
|
[FEEDER_CHAIN_FULLMULTI] = feeder_chain_formats_fullmulti
|
|
};
|
|
|
|
static int feeder_chain_mode = FEEDER_CHAIN_DEFAULT;
|
|
|
|
#if defined(_KERNEL) && defined(SND_DEBUG) && defined(SND_FEEDER_FULL_MULTIFORMAT)
|
|
TUNABLE_INT("hw.snd.feeder_chain_mode", &feeder_chain_mode);
|
|
SYSCTL_INT(_hw_snd, OID_AUTO, feeder_chain_mode, CTLFLAG_RW,
|
|
&feeder_chain_mode, 0,
|
|
"feeder chain mode "
|
|
"(0=lean, 1=16bit, 2=32bit, 3=multiformat, 4=fullmultiformat)");
|
|
#endif
|
|
|
|
/*
|
|
* feeder_build_format(): Chain any format converter.
|
|
*/
|
|
static int
|
|
feeder_build_format(struct pcm_channel *c, struct feeder_chain_desc *cdesc)
|
|
{
|
|
struct feeder_class *fc;
|
|
struct pcm_feederdesc *desc;
|
|
int ret;
|
|
|
|
desc = &(cdesc->desc);
|
|
desc->type = FEEDER_FORMAT;
|
|
desc->in = 0;
|
|
desc->out = 0;
|
|
desc->flags = 0;
|
|
|
|
fc = feeder_getclass(desc);
|
|
if (fc == NULL) {
|
|
device_printf(c->dev,
|
|
"%s(): can't find feeder_format\n", __func__);
|
|
return (ENOTSUP);
|
|
}
|
|
|
|
desc->in = cdesc->current.afmt;
|
|
desc->out = cdesc->target.afmt;
|
|
|
|
ret = chn_addfeeder(c, fc, desc);
|
|
if (ret != 0) {
|
|
device_printf(c->dev,
|
|
"%s(): can't add feeder_format\n", __func__);
|
|
return (ret);
|
|
}
|
|
|
|
c->feederflags |= 1 << FEEDER_FORMAT;
|
|
|
|
cdesc->current.afmt = cdesc->target.afmt;
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* feeder_build_formatne(): Chain format converter that suite best for native
|
|
* endian format.
|
|
*/
|
|
static int
|
|
feeder_build_formatne(struct pcm_channel *c, struct feeder_chain_desc *cdesc)
|
|
{
|
|
struct feeder_chain_state otarget;
|
|
int ret;
|
|
|
|
if (cdesc->afmt_ne == 0 ||
|
|
AFMT_ENCODING(cdesc->current.afmt) == cdesc->afmt_ne)
|
|
return (0);
|
|
|
|
otarget = cdesc->target;
|
|
cdesc->target = cdesc->current;
|
|
cdesc->target.afmt = SND_FORMAT(cdesc->afmt_ne,
|
|
cdesc->current.matrix->channels, cdesc->current.matrix->ext);
|
|
|
|
ret = feeder_build_format(c, cdesc);
|
|
if (ret != 0)
|
|
return (ret);
|
|
|
|
cdesc->target = otarget;
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* feeder_build_rate(): Chain sample rate converter.
|
|
*/
|
|
static int
|
|
feeder_build_rate(struct pcm_channel *c, struct feeder_chain_desc *cdesc)
|
|
{
|
|
struct feeder_class *fc;
|
|
struct pcm_feeder *f;
|
|
struct pcm_feederdesc *desc;
|
|
int ret;
|
|
|
|
ret = feeder_build_formatne(c, cdesc);
|
|
if (ret != 0)
|
|
return (ret);
|
|
|
|
desc = &(cdesc->desc);
|
|
desc->type = FEEDER_RATE;
|
|
desc->in = 0;
|
|
desc->out = 0;
|
|
desc->flags = 0;
|
|
|
|
fc = feeder_getclass(desc);
|
|
if (fc == NULL) {
|
|
device_printf(c->dev,
|
|
"%s(): can't find feeder_rate\n", __func__);
|
|
return (ENOTSUP);
|
|
}
|
|
|
|
desc->in = cdesc->current.afmt;
|
|
desc->out = desc->in;
|
|
|
|
ret = chn_addfeeder(c, fc, desc);
|
|
if (ret != 0) {
|
|
device_printf(c->dev,
|
|
"%s(): can't add feeder_rate\n", __func__);
|
|
return (ret);
|
|
}
|
|
|
|
f = c->feeder;
|
|
|
|
/*
|
|
* If in 'dummy' mode (possibly due to passthrough mode), set the
|
|
* conversion quality to the lowest possible (should be fastest) since
|
|
* listener won't be hearing anything. Theoretically we can just
|
|
* disable it, but that will cause weird runtime behaviour:
|
|
* application appear to play something that is either too fast or too
|
|
* slow.
|
|
*/
|
|
if (cdesc->dummy != 0) {
|
|
ret = FEEDER_SET(f, FEEDRATE_QUALITY, 0);
|
|
if (ret != 0) {
|
|
device_printf(c->dev,
|
|
"%s(): can't set resampling quality\n", __func__);
|
|
return (ret);
|
|
}
|
|
}
|
|
|
|
ret = FEEDER_SET(f, FEEDRATE_SRC, cdesc->current.rate);
|
|
if (ret != 0) {
|
|
device_printf(c->dev,
|
|
"%s(): can't set source rate\n", __func__);
|
|
return (ret);
|
|
}
|
|
|
|
ret = FEEDER_SET(f, FEEDRATE_DST, cdesc->target.rate);
|
|
if (ret != 0) {
|
|
device_printf(c->dev,
|
|
"%s(): can't set destination rate\n", __func__);
|
|
return (ret);
|
|
}
|
|
|
|
c->feederflags |= 1 << FEEDER_RATE;
|
|
|
|
cdesc->current.rate = cdesc->target.rate;
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* feeder_build_matrix(): Chain channel matrixing converter.
|
|
*/
|
|
static int
|
|
feeder_build_matrix(struct pcm_channel *c, struct feeder_chain_desc *cdesc)
|
|
{
|
|
struct feeder_class *fc;
|
|
struct pcm_feeder *f;
|
|
struct pcm_feederdesc *desc;
|
|
int ret;
|
|
|
|
ret = feeder_build_formatne(c, cdesc);
|
|
if (ret != 0)
|
|
return (ret);
|
|
|
|
desc = &(cdesc->desc);
|
|
desc->type = FEEDER_MATRIX;
|
|
desc->in = 0;
|
|
desc->out = 0;
|
|
desc->flags = 0;
|
|
|
|
fc = feeder_getclass(desc);
|
|
if (fc == NULL) {
|
|
device_printf(c->dev,
|
|
"%s(): can't find feeder_matrix\n", __func__);
|
|
return (ENOTSUP);
|
|
}
|
|
|
|
desc->in = cdesc->current.afmt;
|
|
desc->out = SND_FORMAT(cdesc->current.afmt,
|
|
cdesc->target.matrix->channels, cdesc->target.matrix->ext);
|
|
|
|
ret = chn_addfeeder(c, fc, desc);
|
|
if (ret != 0) {
|
|
device_printf(c->dev,
|
|
"%s(): can't add feeder_matrix\n", __func__);
|
|
return (ret);
|
|
}
|
|
|
|
f = c->feeder;
|
|
ret = feeder_matrix_setup(f, cdesc->current.matrix,
|
|
cdesc->target.matrix);
|
|
if (ret != 0) {
|
|
device_printf(c->dev,
|
|
"%s(): feeder_matrix_setup() failed\n", __func__);
|
|
return (ret);
|
|
}
|
|
|
|
c->feederflags |= 1 << FEEDER_MATRIX;
|
|
|
|
cdesc->current.afmt = desc->out;
|
|
cdesc->current.matrix = cdesc->target.matrix;
|
|
cdesc->use_matrix = 0;
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* feeder_build_volume(): Chain soft volume.
|
|
*/
|
|
static int
|
|
feeder_build_volume(struct pcm_channel *c, struct feeder_chain_desc *cdesc)
|
|
{
|
|
struct feeder_class *fc;
|
|
struct pcm_feeder *f;
|
|
struct pcm_feederdesc *desc;
|
|
int ret;
|
|
|
|
ret = feeder_build_formatne(c, cdesc);
|
|
if (ret != 0)
|
|
return (ret);
|
|
|
|
desc = &(cdesc->desc);
|
|
desc->type = FEEDER_VOLUME;
|
|
desc->in = 0;
|
|
desc->out = 0;
|
|
desc->flags = 0;
|
|
|
|
fc = feeder_getclass(desc);
|
|
if (fc == NULL) {
|
|
device_printf(c->dev,
|
|
"%s(): can't find feeder_volume\n", __func__);
|
|
return (ENOTSUP);
|
|
}
|
|
|
|
desc->in = cdesc->current.afmt;
|
|
desc->out = desc->in;
|
|
|
|
ret = chn_addfeeder(c, fc, desc);
|
|
if (ret != 0) {
|
|
device_printf(c->dev,
|
|
"%s(): can't add feeder_volume\n", __func__);
|
|
return (ret);
|
|
}
|
|
|
|
f = c->feeder;
|
|
|
|
/*
|
|
* If in 'dummy' mode (possibly due to passthrough mode), set BYPASS
|
|
* mode since listener won't be hearing anything. Theoretically we can
|
|
* just disable it, but that will confuse volume per channel mixer.
|
|
*/
|
|
if (cdesc->dummy != 0) {
|
|
ret = FEEDER_SET(f, FEEDVOLUME_STATE, FEEDVOLUME_BYPASS);
|
|
if (ret != 0) {
|
|
device_printf(c->dev,
|
|
"%s(): can't set volume bypass\n", __func__);
|
|
return (ret);
|
|
}
|
|
}
|
|
|
|
ret = feeder_volume_apply_matrix(f, cdesc->current.matrix);
|
|
if (ret != 0) {
|
|
device_printf(c->dev,
|
|
"%s(): feeder_volume_apply_matrix() failed\n", __func__);
|
|
return (ret);
|
|
}
|
|
|
|
c->feederflags |= 1 << FEEDER_VOLUME;
|
|
|
|
cdesc->use_volume = 0;
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* feeder_build_eq(): Chain parametric software equalizer.
|
|
*/
|
|
static int
|
|
feeder_build_eq(struct pcm_channel *c, struct feeder_chain_desc *cdesc)
|
|
{
|
|
struct feeder_class *fc;
|
|
struct pcm_feeder *f;
|
|
struct pcm_feederdesc *desc;
|
|
int ret;
|
|
|
|
ret = feeder_build_formatne(c, cdesc);
|
|
if (ret != 0)
|
|
return (ret);
|
|
|
|
desc = &(cdesc->desc);
|
|
desc->type = FEEDER_EQ;
|
|
desc->in = 0;
|
|
desc->out = 0;
|
|
desc->flags = 0;
|
|
|
|
fc = feeder_getclass(desc);
|
|
if (fc == NULL) {
|
|
device_printf(c->dev,
|
|
"%s(): can't find feeder_eq\n", __func__);
|
|
return (ENOTSUP);
|
|
}
|
|
|
|
desc->in = cdesc->current.afmt;
|
|
desc->out = desc->in;
|
|
|
|
ret = chn_addfeeder(c, fc, desc);
|
|
if (ret != 0) {
|
|
device_printf(c->dev,
|
|
"%s(): can't add feeder_eq\n", __func__);
|
|
return (ret);
|
|
}
|
|
|
|
f = c->feeder;
|
|
|
|
ret = FEEDER_SET(f, FEEDEQ_RATE, cdesc->current.rate);
|
|
if (ret != 0) {
|
|
device_printf(c->dev,
|
|
"%s(): can't set rate on feeder_eq\n", __func__);
|
|
return (ret);
|
|
}
|
|
|
|
c->feederflags |= 1 << FEEDER_EQ;
|
|
|
|
cdesc->use_eq = 0;
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* feeder_build_root(): Chain root feeder, the top, father of all.
|
|
*/
|
|
static int
|
|
feeder_build_root(struct pcm_channel *c, struct feeder_chain_desc *cdesc)
|
|
{
|
|
struct feeder_class *fc;
|
|
int ret;
|
|
|
|
fc = feeder_getclass(NULL);
|
|
if (fc == NULL) {
|
|
device_printf(c->dev,
|
|
"%s(): can't find feeder_root\n", __func__);
|
|
return (ENOTSUP);
|
|
}
|
|
|
|
ret = chn_addfeeder(c, fc, NULL);
|
|
if (ret != 0) {
|
|
device_printf(c->dev,
|
|
"%s(): can't add feeder_root\n", __func__);
|
|
return (ret);
|
|
}
|
|
|
|
c->feederflags |= 1 << FEEDER_ROOT;
|
|
|
|
c->feeder->desc->in = cdesc->current.afmt;
|
|
c->feeder->desc->out = cdesc->current.afmt;
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* feeder_build_mixer(): Chain software mixer for virtual channels.
|
|
*/
|
|
static int
|
|
feeder_build_mixer(struct pcm_channel *c, struct feeder_chain_desc *cdesc)
|
|
{
|
|
struct feeder_class *fc;
|
|
struct pcm_feederdesc *desc;
|
|
int ret;
|
|
|
|
desc = &(cdesc->desc);
|
|
desc->type = FEEDER_MIXER;
|
|
desc->in = 0;
|
|
desc->out = 0;
|
|
desc->flags = 0;
|
|
|
|
fc = feeder_getclass(desc);
|
|
if (fc == NULL) {
|
|
device_printf(c->dev,
|
|
"%s(): can't find feeder_mixer\n", __func__);
|
|
return (ENOTSUP);
|
|
}
|
|
|
|
desc->in = cdesc->current.afmt;
|
|
desc->out = desc->in;
|
|
|
|
ret = chn_addfeeder(c, fc, desc);
|
|
if (ret != 0) {
|
|
device_printf(c->dev,
|
|
"%s(): can't add feeder_mixer\n", __func__);
|
|
return (ret);
|
|
}
|
|
|
|
c->feederflags |= 1 << FEEDER_MIXER;
|
|
|
|
return (0);
|
|
}
|
|
|
|
/* Macrosses to ease our job doing stuffs later. */
|
|
#define FEEDER_BW(c, t) ((c)->t.matrix->channels * (c)->t.rate)
|
|
|
|
#define FEEDRATE_UP(c) ((c)->target.rate > (c)->current.rate)
|
|
#define FEEDRATE_DOWN(c) ((c)->target.rate < (c)->current.rate)
|
|
#define FEEDRATE_REQUIRED(c) (FEEDRATE_UP(c) || FEEDRATE_DOWN(c))
|
|
|
|
#define FEEDMATRIX_UP(c) ((c)->target.matrix->channels > \
|
|
(c)->current.matrix->channels)
|
|
#define FEEDMATRIX_DOWN(c) ((c)->target.matrix->channels < \
|
|
(c)->current.matrix->channels)
|
|
#define FEEDMATRIX_REQUIRED(c) (FEEDMATRIX_UP(c) || \
|
|
FEEDMATRIX_DOWN(c) || (c)->use_matrix != 0)
|
|
|
|
#define FEEDFORMAT_REQUIRED(c) (AFMT_ENCODING((c)->current.afmt) != \
|
|
AFMT_ENCODING((c)->target.afmt))
|
|
|
|
#define FEEDVOLUME_REQUIRED(c) ((c)->use_volume != 0)
|
|
|
|
#define FEEDEQ_VALIDRATE(c, t) (feeder_eq_validrate((c)->t.rate) != 0)
|
|
#define FEEDEQ_ECONOMY(c) (FEEDER_BW(c, current) < FEEDER_BW(c, target))
|
|
#define FEEDEQ_REQUIRED(c) ((c)->use_eq != 0 && \
|
|
FEEDEQ_VALIDRATE(c, current))
|
|
|
|
#define FEEDFORMAT_NE_REQUIRED(c) \
|
|
((c)->afmt_ne != AFMT_S32_NE && \
|
|
(((c)->mode == FEEDER_CHAIN_16 && \
|
|
AFMT_ENCODING((c)->current.afmt) != AFMT_S16_NE) || \
|
|
((c)->mode == FEEDER_CHAIN_32 && \
|
|
AFMT_ENCODING((c)->current.afmt) != AFMT_S32_NE) || \
|
|
(c)->mode == FEEDER_CHAIN_FULLMULTI || \
|
|
((c)->mode == FEEDER_CHAIN_MULTI && \
|
|
((c)->current.afmt & AFMT_8BIT)) || \
|
|
((c)->mode == FEEDER_CHAIN_LEAN && \
|
|
!((c)->current.afmt & (AFMT_S16_NE | AFMT_S32_NE)))))
|
|
|
|
int
|
|
feeder_chain(struct pcm_channel *c)
|
|
{
|
|
struct snddev_info *d;
|
|
struct pcmchan_caps *caps;
|
|
struct feeder_chain_desc cdesc;
|
|
struct pcmchan_matrix *hwmatrix, *softmatrix;
|
|
uint32_t hwfmt, softfmt;
|
|
int ret;
|
|
|
|
CHN_LOCKASSERT(c);
|
|
|
|
/* Remove everything first. */
|
|
while (chn_removefeeder(c) == 0)
|
|
;
|
|
|
|
KASSERT(c->feeder == NULL, ("feeder chain not empty"));
|
|
|
|
/* clear and populate chain descriptor. */
|
|
bzero(&cdesc, sizeof(cdesc));
|
|
|
|
switch (feeder_chain_mode) {
|
|
case FEEDER_CHAIN_LEAN:
|
|
case FEEDER_CHAIN_16:
|
|
case FEEDER_CHAIN_32:
|
|
#if defined(SND_FEEDER_MULTIFORMAT) || defined(SND_FEEDER_FULL_MULTIFORMAT)
|
|
case FEEDER_CHAIN_MULTI:
|
|
#endif
|
|
#if defined(SND_FEEDER_FULL_MULTIFORMAT)
|
|
case FEEDER_CHAIN_FULLMULTI:
|
|
#endif
|
|
break;
|
|
default:
|
|
feeder_chain_mode = FEEDER_CHAIN_DEFAULT;
|
|
break;
|
|
}
|
|
|
|
cdesc.mode = feeder_chain_mode;
|
|
cdesc.expensive = 1; /* XXX faster.. */
|
|
|
|
#define VCHAN_PASSTHROUGH(c) (((c)->flags & (CHN_F_VIRTUAL | \
|
|
CHN_F_PASSTHROUGH)) == \
|
|
(CHN_F_VIRTUAL | CHN_F_PASSTHROUGH))
|
|
|
|
/* Get the best possible hardware format. */
|
|
if (VCHAN_PASSTHROUGH(c))
|
|
hwfmt = c->parentchannel->format;
|
|
else {
|
|
caps = chn_getcaps(c);
|
|
if (caps == NULL || caps->fmtlist == NULL) {
|
|
device_printf(c->dev,
|
|
"%s(): failed to get channel caps\n", __func__);
|
|
return (ENODEV);
|
|
}
|
|
|
|
if ((c->format & AFMT_PASSTHROUGH) &&
|
|
!snd_fmtvalid(c->format, caps->fmtlist))
|
|
return (ENODEV);
|
|
|
|
hwfmt = snd_fmtbest(c->format, caps->fmtlist);
|
|
if (hwfmt == 0 || !snd_fmtvalid(hwfmt, caps->fmtlist)) {
|
|
device_printf(c->dev,
|
|
"%s(): invalid hardware format 0x%08x\n",
|
|
__func__, hwfmt);
|
|
{
|
|
int i;
|
|
for (i = 0; caps->fmtlist[i] != 0; i++)
|
|
printf("0x%08x\n", caps->fmtlist[i]);
|
|
printf("Req: 0x%08x\n", c->format);
|
|
}
|
|
return (ENODEV);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The 'hardware' possibly have different intepretation of channel
|
|
* matrixing, so get it first .....
|
|
*/
|
|
hwmatrix = CHANNEL_GETMATRIX(c->methods, c->devinfo, hwfmt);
|
|
if (hwmatrix == NULL) {
|
|
device_printf(c->dev,
|
|
"%s(): failed to acquire hw matrix [0x%08x]\n",
|
|
__func__, hwfmt);
|
|
return (ENODEV);
|
|
}
|
|
/* ..... and rebuild hwfmt. */
|
|
hwfmt = SND_FORMAT(hwfmt, hwmatrix->channels, hwmatrix->ext);
|
|
|
|
/* Reset and rebuild default channel format/matrix map. */
|
|
softfmt = c->format;
|
|
softmatrix = &c->matrix;
|
|
if (softmatrix->channels != AFMT_CHANNEL(softfmt) ||
|
|
softmatrix->ext != AFMT_EXTCHANNEL(softfmt)) {
|
|
softmatrix = feeder_matrix_format_map(softfmt);
|
|
if (softmatrix == NULL) {
|
|
device_printf(c->dev,
|
|
"%s(): failed to acquire soft matrix [0x%08x]\n",
|
|
__func__, softfmt);
|
|
return (ENODEV);
|
|
}
|
|
c->matrix = *softmatrix;
|
|
c->matrix.id = SND_CHN_MATRIX_PCMCHANNEL;
|
|
}
|
|
softfmt = SND_FORMAT(softfmt, softmatrix->channels, softmatrix->ext);
|
|
if (softfmt != c->format)
|
|
device_printf(c->dev,
|
|
"%s(): WARNING: %s Soft format 0x%08x -> 0x%08x\n",
|
|
__func__, CHN_DIRSTR(c), c->format, softfmt);
|
|
|
|
/*
|
|
* PLAY and REC are opposite.
|
|
*/
|
|
if (c->direction == PCMDIR_PLAY) {
|
|
cdesc.origin.afmt = softfmt;
|
|
cdesc.origin.matrix = softmatrix;
|
|
cdesc.origin.rate = c->speed;
|
|
cdesc.target.afmt = hwfmt;
|
|
cdesc.target.matrix = hwmatrix;
|
|
cdesc.target.rate = sndbuf_getspd(c->bufhard);
|
|
} else {
|
|
cdesc.origin.afmt = hwfmt;
|
|
cdesc.origin.matrix = hwmatrix;
|
|
cdesc.origin.rate = sndbuf_getspd(c->bufhard);
|
|
cdesc.target.afmt = softfmt;
|
|
cdesc.target.matrix = softmatrix;
|
|
cdesc.target.rate = c->speed;
|
|
}
|
|
|
|
d = c->parentsnddev;
|
|
|
|
/*
|
|
* If channel is in bitperfect or passthrough mode, make it appear
|
|
* that 'origin' and 'target' identical, skipping mostly chain
|
|
* procedures.
|
|
*/
|
|
if (CHN_BITPERFECT(c) || (c->format & AFMT_PASSTHROUGH)) {
|
|
if (c->direction == PCMDIR_PLAY)
|
|
cdesc.origin = cdesc.target;
|
|
else
|
|
cdesc.target = cdesc.origin;
|
|
c->format = cdesc.target.afmt;
|
|
c->speed = cdesc.target.rate;
|
|
} else {
|
|
/* hwfmt is not convertible, so 'dummy' it. */
|
|
if (hwfmt & AFMT_PASSTHROUGH)
|
|
cdesc.dummy = 1;
|
|
|
|
if ((softfmt & AFMT_CONVERTIBLE) &&
|
|
(((d->flags & SD_F_VPC) && !(c->flags & CHN_F_HAS_VCHAN)) ||
|
|
(!(d->flags & SD_F_VPC) && (d->flags & SD_F_SOFTPCMVOL) &&
|
|
!(c->flags & CHN_F_VIRTUAL))))
|
|
cdesc.use_volume = 1;
|
|
|
|
if (feeder_matrix_compare(cdesc.origin.matrix,
|
|
cdesc.target.matrix) != 0)
|
|
cdesc.use_matrix = 1;
|
|
|
|
/* Soft EQ only applicable for PLAY. */
|
|
if (cdesc.dummy == 0 &&
|
|
c->direction == PCMDIR_PLAY && (d->flags & SD_F_EQ) &&
|
|
(((d->flags & SD_F_EQ_PC) &&
|
|
!(c->flags & CHN_F_HAS_VCHAN)) ||
|
|
(!(d->flags & SD_F_EQ_PC) && !(c->flags & CHN_F_VIRTUAL))))
|
|
cdesc.use_eq = 1;
|
|
|
|
if (FEEDFORMAT_NE_REQUIRED(&cdesc)) {
|
|
cdesc.afmt_ne =
|
|
(cdesc.dummy != 0) ?
|
|
snd_fmtbest(AFMT_ENCODING(softfmt),
|
|
feeder_chain_formats[cdesc.mode]) :
|
|
snd_fmtbest(AFMT_ENCODING(cdesc.target.afmt),
|
|
feeder_chain_formats[cdesc.mode]);
|
|
if (cdesc.afmt_ne == 0) {
|
|
device_printf(c->dev,
|
|
"%s(): snd_fmtbest failed!\n", __func__);
|
|
cdesc.afmt_ne =
|
|
(((cdesc.dummy != 0) ? softfmt :
|
|
cdesc.target.afmt) &
|
|
(AFMT_24BIT | AFMT_32BIT)) ?
|
|
AFMT_S32_NE : AFMT_S16_NE;
|
|
}
|
|
}
|
|
}
|
|
|
|
cdesc.current = cdesc.origin;
|
|
|
|
/* Build everything. */
|
|
|
|
c->feederflags = 0;
|
|
|
|
#define FEEDER_BUILD(t) do { \
|
|
ret = feeder_build_##t(c, &cdesc); \
|
|
if (ret != 0) \
|
|
return (ret); \
|
|
} while (0)
|
|
|
|
if (!(c->flags & CHN_F_HAS_VCHAN) || c->direction == PCMDIR_REC)
|
|
FEEDER_BUILD(root);
|
|
else if (c->direction == PCMDIR_PLAY && (c->flags & CHN_F_HAS_VCHAN))
|
|
FEEDER_BUILD(mixer);
|
|
else
|
|
return (ENOTSUP);
|
|
|
|
/*
|
|
* The basic idea is: The smaller the bandwidth, the cheaper the
|
|
* conversion process, with following constraints:-
|
|
*
|
|
* 1) Almost all feeders work best in 16/32 native endian.
|
|
* 2) Try to avoid 8bit feeders due to poor dynamic range.
|
|
* 3) Avoid volume, format, matrix and rate in BITPERFECT or
|
|
* PASSTHROUGH mode.
|
|
* 4) Try putting volume before EQ or rate. Should help to
|
|
* avoid/reduce possible clipping.
|
|
* 5) EQ require specific, valid rate, unless it allow sloppy
|
|
* conversion.
|
|
*/
|
|
if (FEEDMATRIX_UP(&cdesc)) {
|
|
if (FEEDEQ_REQUIRED(&cdesc) &&
|
|
(!FEEDEQ_VALIDRATE(&cdesc, target) ||
|
|
(cdesc.expensive == 0 && FEEDEQ_ECONOMY(&cdesc))))
|
|
FEEDER_BUILD(eq);
|
|
if (FEEDRATE_REQUIRED(&cdesc))
|
|
FEEDER_BUILD(rate);
|
|
FEEDER_BUILD(matrix);
|
|
if (FEEDVOLUME_REQUIRED(&cdesc))
|
|
FEEDER_BUILD(volume);
|
|
if (FEEDEQ_REQUIRED(&cdesc))
|
|
FEEDER_BUILD(eq);
|
|
} else if (FEEDMATRIX_DOWN(&cdesc)) {
|
|
FEEDER_BUILD(matrix);
|
|
if (FEEDVOLUME_REQUIRED(&cdesc))
|
|
FEEDER_BUILD(volume);
|
|
if (FEEDEQ_REQUIRED(&cdesc) &&
|
|
(!FEEDEQ_VALIDRATE(&cdesc, target) ||
|
|
FEEDEQ_ECONOMY(&cdesc)))
|
|
FEEDER_BUILD(eq);
|
|
if (FEEDRATE_REQUIRED(&cdesc))
|
|
FEEDER_BUILD(rate);
|
|
if (FEEDEQ_REQUIRED(&cdesc))
|
|
FEEDER_BUILD(eq);
|
|
} else {
|
|
if (FEEDRATE_DOWN(&cdesc)) {
|
|
if (FEEDEQ_REQUIRED(&cdesc) &&
|
|
!FEEDEQ_VALIDRATE(&cdesc, target)) {
|
|
if (FEEDVOLUME_REQUIRED(&cdesc))
|
|
FEEDER_BUILD(volume);
|
|
FEEDER_BUILD(eq);
|
|
}
|
|
FEEDER_BUILD(rate);
|
|
}
|
|
if (FEEDMATRIX_REQUIRED(&cdesc))
|
|
FEEDER_BUILD(matrix);
|
|
if (FEEDVOLUME_REQUIRED(&cdesc))
|
|
FEEDER_BUILD(volume);
|
|
if (FEEDRATE_UP(&cdesc)) {
|
|
if (FEEDEQ_REQUIRED(&cdesc) &&
|
|
!FEEDEQ_VALIDRATE(&cdesc, target))
|
|
FEEDER_BUILD(eq);
|
|
FEEDER_BUILD(rate);
|
|
}
|
|
if (FEEDEQ_REQUIRED(&cdesc))
|
|
FEEDER_BUILD(eq);
|
|
}
|
|
|
|
if (FEEDFORMAT_REQUIRED(&cdesc))
|
|
FEEDER_BUILD(format);
|
|
|
|
if (c->direction == PCMDIR_REC && (c->flags & CHN_F_HAS_VCHAN))
|
|
FEEDER_BUILD(mixer);
|
|
|
|
sndbuf_setfmt(c->bufsoft, c->format);
|
|
sndbuf_setspd(c->bufsoft, c->speed);
|
|
|
|
sndbuf_setfmt(c->bufhard, hwfmt);
|
|
|
|
chn_syncstate(c);
|
|
|
|
return (0);
|
|
}
|