#ifdef HAVE_KERNEL_OPTION_HEADERS
#include "opt_snd.h"
#endif
#include <dev/sound/pcm/sound.h>
#include "feeder_if.h"
SND_DECLARE_FILE("$FreeBSD: head/sys/dev/sound/pcm/feeder_chain.c 267992 2014-06-28 03:56:17Z hselasky $");
struct feeder_chain_state {
uint32_t afmt;
uint32_t rate;
struct pcmchan_matrix *matrix;
};
struct feeder_chain_desc {
struct feeder_chain_state origin;
struct feeder_chain_state current;
struct feeder_chain_state target;
struct pcm_feederdesc desc;
uint32_t afmt_ne;
int mode;
int use_eq;
int use_matrix;
int use_volume;
int dummy;
int 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
static uint32_t feeder_chain_formats_lean[] = {
AFMT_S16_NE, AFMT_S32_NE,
0
};
static uint32_t feeder_chain_formats_16[] = {
AFMT_S16_NE,
0
};
static uint32_t feeder_chain_formats_32[] = {
AFMT_S32_NE,
0
};
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
};
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
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);
}
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);
}
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 (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);
}
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);
}
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 (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);
}
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);
}
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);
}
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);
}
#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);
while (chn_removefeeder(c) == 0)
;
KASSERT(c->feeder == NULL, ("feeder chain not empty"));
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;
#define VCHAN_PASSTHROUGH(c) (((c)->flags & (CHN_F_VIRTUAL | \
CHN_F_PASSTHROUGH)) == \
(CHN_F_VIRTUAL | CHN_F_PASSTHROUGH))
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++)
kprintf("0x%08x\n", caps->fmtlist[i]);
kprintf("Req: 0x%08x\n", c->format);
}
return (ENODEV);
}
}
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);
}
hwfmt = SND_FORMAT(hwfmt, hwmatrix->channels, hwmatrix->ext);
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);
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 (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 {
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;
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;
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);
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);
}