87506547d2
1. Support wide range sampling rate, as low as 1hz up to int32 max (which is, insane) through new feeder_rate, multiple precisions choice (32/64 bit converter). This is indeed, quite insane, but it does give us more room and flexibility. Plenty sysctl options to adjust resampling characteristics. 2. Support 24/32 bit pcm format conversion through new, much improved, simplified and optimized feeder_fmt. Changes: 1. buffer.c / dsp.c / sound.h * Support for 24/32 AFMT. 2. feeder_rate.c * New implementation of sampling rate conversion with 32/64 bit precision, 1 - int32max hz (which is, ridiculous, yet very addictive). Much improved / smarter buffer management to not cause any missing samples at the end of conversion process * Tunable sysctls for various aspect: hw.snd.feeder_rate_ratemin - minimum allowable sampling rate (default to 4000) hw.snd.feeder_rate_ratemax - maximum allowable sampling rate (default to 1102500) hw.snd.feeder_rate_buffersize - conversion buffer size (default to 8192) hw.snd.feeder_rate_scaling - scaling / conversion method (please refer to the source for explaination). Default to previous implementation type. 3. feeder_fmt.c / sound.h * New implementation, support for 24/32bit conversion, optimized, and simplified. Few routines has been removed (8 to xlaw, 16 to 8). It just doesn't make sense. 4. channel.c * Support for 24/32 AFMT * Fix wrong xruns increment, causing incorrect underruns statistic while using vchans. 5. vchan.c * Support for 24/32 AFMT * Proper speed / rate detection especially for fixed rate ac97. User can override it using kernel hint: hint.pcm.<unit>.vchanrate="xxxx". Notes / Issues: * Virtual Channels (vchans) Enabling vchans can really, really help to solve overrun issues. This is quite understandable, because it operates entirely within its own buffering system without relying on hardware interrupt / state. Even if you don't need vchan, just enable single channel can help much. Few soundcards (notably via8233x, sblive, possibly others) have their own hardware multi channel, and this is unfortunately beyond vchan reachability. * The arrival of 24/32 also come with a price. Applications that can do 24/32bit playback need to be recompiled (notably mplayer). Use (recompiled) mplayer to experiment / test / debug this various format using -af format=fmt. Note that 24bit seeking in mplayer is a little bit broken, sometimes can cause silence or loud static noise. Pausing / seeking few times can solve this problem. You don't have to rebuild world entirely for this. Simply copy /usr/src/sys/sys/soundcard.h to /usr/include/sys/soundcard.h would suffice. Few drivers also need recompilation, and this can be done via /usr/src/sys/modules/sound/. Support for 24bit hardware playback is beyond the scope of this changes. That would require spessific hardware driver changes. * Don't expect playing 9999999999hz is a wise decision. Be reasonable. The new feeder_rate implemention provide flexibility, not insanity. You can easily chew up your CPU with this kind of mind instability. Please use proper mosquito repellent device for this obvious cracked brain attempt. As for testing purposes, you can use (again) mplayer to generate / play with different sampling rate. Use something like "mplayer -af resample=192000:0:0 <files>". Submitted by: Ariff Abdullah <skywizard@MyBSD.org.my> Tested by: multimedia@
807 lines
21 KiB
C
807 lines
21 KiB
C
/*-
|
|
* Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk>
|
|
* Copyright (c) 2003 Orion Hodson <orion@freebsd.org>
|
|
* Copyright (c) 2005 Ariff Abdullah <skywizard@MyBSD.org.my>
|
|
* 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.
|
|
*
|
|
* 2005-06-11:
|
|
* ==========
|
|
*
|
|
* *New* and rewritten soft sample rate converter supporting arbitary sample
|
|
* rate, fine grained scalling/coefficients and unified up/down stereo
|
|
* converter. Most of disclaimers from orion's previous version also applied
|
|
* here, regarding with linear interpolation deficiencies, pre/post
|
|
* anti-aliasing filtering issues. This version comes with much simpler and
|
|
* tighter interface, although it works almost exactly like the older one.
|
|
*
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
* *
|
|
* This new implementation is fully dedicated in memory of Cameron Grant, *
|
|
* the creator of magnificent, highly addictive feeder infrastructure. *
|
|
* *
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
*
|
|
* Orion's notes:
|
|
* =============
|
|
*
|
|
* This rate conversion code uses linear interpolation without any
|
|
* pre- or post- interpolation filtering to combat aliasing. This
|
|
* greatly limits the sound quality and should be addressed at some
|
|
* stage in the future.
|
|
*
|
|
* Since this accuracy of interpolation is sensitive and examination
|
|
* of the algorithm output is harder from the kernel, th code is
|
|
* designed to be compiled in the kernel and in a userland test
|
|
* harness. This is done by selectively including and excluding code
|
|
* with several portions based on whether _KERNEL is defined. It's a
|
|
* little ugly, but exceedingly useful. The testsuite and its
|
|
* revisions can be found at:
|
|
* http://people.freebsd.org/~orion/files/feedrate/
|
|
*
|
|
* Special thanks to Ken Marx for exposing flaws in the code and for
|
|
* testing revisions.
|
|
*/
|
|
|
|
#include <dev/sound/pcm/sound.h>
|
|
#include "feeder_if.h"
|
|
|
|
SND_DECLARE_FILE("$FreeBSD$");
|
|
|
|
#define RATE_ASSERT(x, y) /* KASSERT(x,y) */
|
|
#define RATE_TRACE(x...) /* printf(x) */
|
|
|
|
MALLOC_DEFINE(M_RATEFEEDER, "ratefeed", "pcm rate feeder");
|
|
|
|
#define FEEDBUFSZ 8192
|
|
#define ROUNDHZ 25
|
|
#define RATEMIN 4000
|
|
/* 8000 * 138 or 11025 * 100 . This is insane, indeed! */
|
|
#define RATEMAX 1102500
|
|
#define MINGAIN 92
|
|
#define MAXGAIN 96
|
|
|
|
#define FEEDRATE_CONVERT_64 0
|
|
#define FEEDRATE_CONVERT_SCALE64 1
|
|
#define FEEDRATE_CONVERT_SCALE32 2
|
|
#define FEEDRATE_CONVERT_PLAIN 3
|
|
#define FEEDRATE_CONVERT_FIXED 4
|
|
#define FEEDRATE_CONVERT_OPTIMAL 5
|
|
#define FEEDRATE_CONVERT_WORST 6
|
|
|
|
#define FEEDRATE_64_MAXROLL 32
|
|
#define FEEDRATE_32_MAXROLL 16
|
|
|
|
struct feed_rate_info {
|
|
uint32_t src, dst; /* rounded source / destination rates */
|
|
uint32_t rsrc, rdst; /* original source / destination rates */
|
|
uint32_t gx, gy; /* interpolation / decimation ratio */
|
|
uint32_t alpha; /* interpolation distance */
|
|
uint32_t pos, bpos; /* current sample / buffer positions */
|
|
uint32_t bufsz; /* total buffer size */
|
|
int32_t scale, roll; /* scale / roll factor */
|
|
int16_t *buffer;
|
|
uint32_t (*convert)(struct feed_rate_info *, int16_t *, uint32_t);
|
|
};
|
|
|
|
static uint32_t
|
|
feed_convert_64(struct feed_rate_info *, int16_t *, uint32_t);
|
|
static uint32_t
|
|
feed_convert_scale64(struct feed_rate_info *, int16_t *, uint32_t);
|
|
static uint32_t
|
|
feed_convert_scale32(struct feed_rate_info *, int16_t *, uint32_t);
|
|
static uint32_t
|
|
feed_convert_plain(struct feed_rate_info *, int16_t *, uint32_t);
|
|
|
|
int feeder_rate_ratemin = RATEMIN;
|
|
int feeder_rate_ratemax = RATEMAX;
|
|
/*
|
|
* See 'Feeder Scaling Type' below..
|
|
*/
|
|
static int feeder_rate_scaling = FEEDRATE_CONVERT_OPTIMAL;
|
|
static int feeder_rate_buffersize = FEEDBUFSZ & ~1;
|
|
|
|
/*
|
|
* sysctls.. I love sysctls..
|
|
*/
|
|
TUNABLE_INT("hw.snd.feeder_rate_ratemin", &feeder_rate_ratemin);
|
|
TUNABLE_INT("hw.snd.feeder_rate_ratemax", &feeder_rate_ratemin);
|
|
TUNABLE_INT("hw.snd.feeder_rate_scaling", &feeder_rate_scaling);
|
|
TUNABLE_INT("hw.snd.feeder_rate_buffersize", &feeder_rate_buffersize);
|
|
|
|
static int
|
|
sysctl_hw_snd_feeder_rate_ratemin(SYSCTL_HANDLER_ARGS)
|
|
{
|
|
int err, val;
|
|
|
|
val = feeder_rate_ratemin;
|
|
err = sysctl_handle_int(oidp, &val, sizeof(val), req);
|
|
if (val < 1 || val >= feeder_rate_ratemax)
|
|
err = EINVAL;
|
|
else
|
|
feeder_rate_ratemin = val;
|
|
return err;
|
|
}
|
|
SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_rate_ratemin, CTLTYPE_INT | CTLFLAG_RW,
|
|
0, sizeof(int), sysctl_hw_snd_feeder_rate_ratemin, "I", "");
|
|
|
|
static int
|
|
sysctl_hw_snd_feeder_rate_ratemax(SYSCTL_HANDLER_ARGS)
|
|
{
|
|
int err, val;
|
|
|
|
val = feeder_rate_ratemax;
|
|
err = sysctl_handle_int(oidp, &val, sizeof(val), req);
|
|
if (val <= feeder_rate_ratemin || val > 0x7fffff)
|
|
err = EINVAL;
|
|
else
|
|
feeder_rate_ratemax = val;
|
|
return err;
|
|
}
|
|
SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_rate_ratemax, CTLTYPE_INT | CTLFLAG_RW,
|
|
0, sizeof(int), sysctl_hw_snd_feeder_rate_ratemax, "I", "");
|
|
|
|
static int
|
|
sysctl_hw_snd_feeder_rate_scaling(SYSCTL_HANDLER_ARGS)
|
|
{
|
|
int err, val;
|
|
|
|
val = feeder_rate_scaling;
|
|
err = sysctl_handle_int(oidp, &val, sizeof(val), req);
|
|
/*
|
|
* Feeder Scaling Type
|
|
* ===================
|
|
*
|
|
* 1. Plain 64bit (high precision)
|
|
* 2. 64bit scaling (high precision, CPU friendly, but can
|
|
* cause gain up/down).
|
|
* 3. 32bit scaling (somehow can cause hz roundup, gain
|
|
* up/down).
|
|
* 4. Plain copy (default if src == dst. Except if src == dst,
|
|
* this is the worst / silly conversion method!).
|
|
*
|
|
* Sysctl options:-
|
|
*
|
|
* 0 - Plain 64bit - no fallback.
|
|
* 1 - 64bit scaling - no fallback.
|
|
* 2 - 32bit scaling - no fallback.
|
|
* 3 - Plain copy - no fallback.
|
|
* 4 - Fixed rate. Means that, choose optimal conversion method
|
|
* without causing hz roundup.
|
|
* 32bit scaling (as long as hz roundup does not occur),
|
|
* 64bit scaling, Plain 64bit.
|
|
* 5 - Optimal / CPU friendly (DEFAULT).
|
|
* 32bit scaling, 64bit scaling, Plain 64bit
|
|
* 6 - Optimal to worst, no 64bit arithmetic involved.
|
|
* 32bit scaling, Plain copy.
|
|
*/
|
|
if (val < FEEDRATE_CONVERT_64 || val > FEEDRATE_CONVERT_WORST)
|
|
err = EINVAL;
|
|
else
|
|
feeder_rate_scaling = val;
|
|
return err;
|
|
}
|
|
SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_rate_scaling, CTLTYPE_INT | CTLFLAG_RW,
|
|
0, sizeof(int), sysctl_hw_snd_feeder_rate_scaling, "I", "");
|
|
|
|
static int
|
|
sysctl_hw_snd_feeder_rate_buffersize(SYSCTL_HANDLER_ARGS)
|
|
{
|
|
int err, val;
|
|
|
|
val = feeder_rate_buffersize;
|
|
err = sysctl_handle_int(oidp, &val, sizeof(val), req);
|
|
/*
|
|
* Don't waste too much kernel space
|
|
*/
|
|
if (val < 2 || val > 65536)
|
|
err = EINVAL;
|
|
else
|
|
feeder_rate_buffersize = val & ~1;
|
|
return err;
|
|
}
|
|
SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_rate_buffersize, CTLTYPE_INT | CTLFLAG_RW,
|
|
0, sizeof(int), sysctl_hw_snd_feeder_rate_buffersize, "I", "");
|
|
|
|
static void
|
|
feed_speed_ratio(uint32_t x, uint32_t y, uint32_t *gx, uint32_t *gy)
|
|
{
|
|
uint32_t w, src = x, dst = y;
|
|
|
|
while (y != 0) {
|
|
w = x % y;
|
|
x = y;
|
|
y = w;
|
|
}
|
|
*gx = src / x;
|
|
*gy = dst / x;
|
|
}
|
|
|
|
static void
|
|
feed_scale_roll(uint32_t dst, int32_t *scale, int32_t *roll, int32_t max)
|
|
{
|
|
int64_t k, tscale;
|
|
int32_t j, troll;
|
|
|
|
*scale = *roll = -1;
|
|
for (j = MAXGAIN; j >= MINGAIN; j -= 3) {
|
|
for (troll = 0; troll < max; troll++) {
|
|
tscale = (1 << troll) / dst;
|
|
k = (tscale * dst * 100) >> troll;
|
|
if (k > j && k <= 100) {
|
|
*scale = tscale;
|
|
*roll = troll;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int
|
|
feed_get_best_coef(uint32_t *src, uint32_t *dst, uint32_t *gx, uint32_t *gy,
|
|
int32_t *scale, int32_t *roll)
|
|
{
|
|
uint32_t tsrc, tdst, sscale, dscale;
|
|
int32_t tscale, troll;
|
|
int i, j, hzmin, hzmax;
|
|
|
|
*scale = *roll = -1;
|
|
for (i = 0; i < 2; i++) {
|
|
hzmin = (ROUNDHZ * i) + 1;
|
|
hzmax = hzmin + ROUNDHZ;
|
|
for (j = hzmin; j < hzmax; j++) {
|
|
tsrc = *src - (*src % j);
|
|
tdst = *dst;
|
|
if (tsrc < 1 || tdst < 1)
|
|
goto coef_failed;
|
|
feed_speed_ratio(tsrc, tdst, &sscale, &dscale);
|
|
feed_scale_roll(dscale, &tscale, &troll,
|
|
FEEDRATE_32_MAXROLL);
|
|
if (tscale != -1 && troll != -1) {
|
|
*src = tsrc;
|
|
*gx = sscale;
|
|
*gy = dscale;
|
|
*scale = tscale;
|
|
*roll = troll;
|
|
return j;
|
|
}
|
|
}
|
|
for (j = hzmin; j < hzmax; j++) {
|
|
tsrc = *src - (*src % j);
|
|
tdst = *dst - (*dst % j);
|
|
if (tsrc < 1 || tdst < 1)
|
|
goto coef_failed;
|
|
feed_speed_ratio(tsrc, tdst, &sscale, &dscale);
|
|
feed_scale_roll(dscale, &tscale, &troll,
|
|
FEEDRATE_32_MAXROLL);
|
|
if (tscale != -1 && troll != -1) {
|
|
*src = tsrc;
|
|
*dst = tdst;
|
|
*gx = sscale;
|
|
*gy = dscale;
|
|
*scale = tscale;
|
|
*roll = troll;
|
|
return j;
|
|
}
|
|
}
|
|
for (j = hzmin; j < hzmax; j++) {
|
|
tsrc = *src;
|
|
tdst = *dst - (*dst % j);
|
|
if (tsrc < 1 || tdst < 1)
|
|
goto coef_failed;
|
|
feed_speed_ratio(tsrc, tdst, &sscale, &dscale);
|
|
feed_scale_roll(dscale, &tscale, &troll,
|
|
FEEDRATE_32_MAXROLL);
|
|
if (tscale != -1 && troll != -1) {
|
|
*src = tsrc;
|
|
*dst = tdst;
|
|
*gx = sscale;
|
|
*gy = dscale;
|
|
*scale = tscale;
|
|
*roll = troll;
|
|
return j;
|
|
}
|
|
}
|
|
}
|
|
coef_failed:
|
|
feed_speed_ratio(*src, *dst, gx, gy);
|
|
feed_scale_roll(*gy, scale, roll, FEEDRATE_32_MAXROLL);
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
feed_rate_reset(struct feed_rate_info *info)
|
|
{
|
|
info->scale = -1;
|
|
info->roll = -1;
|
|
info->src = info->rsrc;
|
|
info->dst = info->rdst;
|
|
info->gx = 0;
|
|
info->gy = 0;
|
|
}
|
|
|
|
static int
|
|
feed_rate_setup(struct pcm_feeder *f)
|
|
{
|
|
struct feed_rate_info *info = f->data;
|
|
int r = 0;
|
|
|
|
info->pos = 2;
|
|
info->bpos = 4;
|
|
info->alpha = 0;
|
|
feed_rate_reset(info);
|
|
if (info->src == info->dst) {
|
|
/*
|
|
* No conversion ever needed. Just do plain copy.
|
|
*/
|
|
info->convert = feed_convert_plain;
|
|
info->gx = 1;
|
|
info->gy = 1;
|
|
} else {
|
|
switch (feeder_rate_scaling) {
|
|
case FEEDRATE_CONVERT_64:
|
|
feed_speed_ratio(info->src, info->dst,
|
|
&info->gx, &info->gy);
|
|
info->convert = feed_convert_64;
|
|
break;
|
|
case FEEDRATE_CONVERT_SCALE64:
|
|
feed_speed_ratio(info->src, info->dst,
|
|
&info->gx, &info->gy);
|
|
feed_scale_roll(info->gy, &info->scale,
|
|
&info->roll, FEEDRATE_64_MAXROLL);
|
|
if (info->scale == -1 || info->roll == -1)
|
|
return -1;
|
|
info->convert = feed_convert_scale64;
|
|
break;
|
|
case FEEDRATE_CONVERT_SCALE32:
|
|
r = feed_get_best_coef(&info->src, &info->dst,
|
|
&info->gx, &info->gy, &info->scale,
|
|
&info->roll);
|
|
if (r == 0)
|
|
return -1;
|
|
info->convert = feed_convert_scale32;
|
|
break;
|
|
case FEEDRATE_CONVERT_PLAIN:
|
|
feed_speed_ratio(info->src, info->dst,
|
|
&info->gx, &info->gy);
|
|
info->convert = feed_convert_plain;
|
|
break;
|
|
case FEEDRATE_CONVERT_FIXED:
|
|
r = feed_get_best_coef(&info->src, &info->dst,
|
|
&info->gx, &info->gy, &info->scale,
|
|
&info->roll);
|
|
if (r != 0 && info->src == info->rsrc &&
|
|
info->dst == info->rdst)
|
|
info->convert = feed_convert_scale32;
|
|
else {
|
|
/* Fallback */
|
|
feed_rate_reset(info);
|
|
feed_speed_ratio(info->src, info->dst,
|
|
&info->gx, &info->gy);
|
|
feed_scale_roll(info->gy, &info->scale,
|
|
&info->roll, FEEDRATE_64_MAXROLL);
|
|
if (info->scale != -1 && info->roll != -1)
|
|
info->convert = feed_convert_scale64;
|
|
else
|
|
info->convert = feed_convert_64;
|
|
}
|
|
break;
|
|
case FEEDRATE_CONVERT_OPTIMAL:
|
|
r = feed_get_best_coef(&info->src, &info->dst,
|
|
&info->gx, &info->gy, &info->scale,
|
|
&info->roll);
|
|
if (r != 0)
|
|
info->convert = feed_convert_scale32;
|
|
else {
|
|
/* Fallback */
|
|
feed_rate_reset(info);
|
|
feed_speed_ratio(info->src, info->dst,
|
|
&info->gx, &info->gy);
|
|
feed_scale_roll(info->gy, &info->scale,
|
|
&info->roll, FEEDRATE_64_MAXROLL);
|
|
if (info->scale != -1 && info->roll != -1)
|
|
info->convert = feed_convert_scale64;
|
|
else
|
|
info->convert = feed_convert_64;
|
|
}
|
|
break;
|
|
case FEEDRATE_CONVERT_WORST:
|
|
r = feed_get_best_coef(&info->src, &info->dst,
|
|
&info->gx, &info->gy, &info->scale,
|
|
&info->roll);
|
|
if (r != 0)
|
|
info->convert = feed_convert_scale32;
|
|
else {
|
|
/* Fallback */
|
|
feed_rate_reset(info);
|
|
feed_speed_ratio(info->src, info->dst,
|
|
&info->gx, &info->gy);
|
|
info->convert = feed_convert_plain;
|
|
}
|
|
break;
|
|
default:
|
|
return -1;
|
|
break;
|
|
}
|
|
/* No way! */
|
|
if (info->gx == 0 || info->gy == 0)
|
|
return -1;
|
|
/*
|
|
* No need to interpolate/decimate, just do plain copy.
|
|
* This probably caused by Hz roundup.
|
|
*/
|
|
if (info->gx == info->gy)
|
|
info->convert = feed_convert_plain;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
feed_rate_set(struct pcm_feeder *f, int what, int value)
|
|
{
|
|
struct feed_rate_info *info = f->data;
|
|
|
|
if (value < feeder_rate_ratemin || value > feeder_rate_ratemax)
|
|
return -1;
|
|
|
|
switch (what) {
|
|
case FEEDRATE_SRC:
|
|
info->rsrc = value;
|
|
break;
|
|
case FEEDRATE_DST:
|
|
info->rdst = value;
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
return feed_rate_setup(f);
|
|
}
|
|
|
|
static int
|
|
feed_rate_get(struct pcm_feeder *f, int what)
|
|
{
|
|
struct feed_rate_info *info = f->data;
|
|
|
|
/*
|
|
* Return *real* src/dst rate.
|
|
*/
|
|
switch (what) {
|
|
case FEEDRATE_SRC:
|
|
return info->rsrc;
|
|
case FEEDRATE_DST:
|
|
return info->rdst;
|
|
default:
|
|
return -1;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
feed_rate_init(struct pcm_feeder *f)
|
|
{
|
|
struct feed_rate_info *info;
|
|
|
|
info = malloc(sizeof(*info), M_RATEFEEDER, M_NOWAIT | M_ZERO);
|
|
if (info == NULL)
|
|
return ENOMEM;
|
|
/*
|
|
* bufsz = sample from last cycle + conversion space
|
|
*/
|
|
info->bufsz = 2 + feeder_rate_buffersize;
|
|
info->buffer = malloc(sizeof(*info->buffer) * info->bufsz,
|
|
M_RATEFEEDER, M_NOWAIT | M_ZERO);
|
|
if (info->buffer == NULL) {
|
|
free(info, M_RATEFEEDER);
|
|
return ENOMEM;
|
|
}
|
|
info->rsrc = DSP_DEFAULT_SPEED;
|
|
info->rdst = DSP_DEFAULT_SPEED;
|
|
f->data = info;
|
|
return feed_rate_setup(f);
|
|
}
|
|
|
|
static int
|
|
feed_rate_free(struct pcm_feeder *f)
|
|
{
|
|
struct feed_rate_info *info = f->data;
|
|
|
|
if (info) {
|
|
if (info->buffer)
|
|
free(info->buffer, M_RATEFEEDER);
|
|
free(info, M_RATEFEEDER);
|
|
}
|
|
f->data = NULL;
|
|
return 0;
|
|
}
|
|
|
|
static uint32_t
|
|
feed_convert_64(struct feed_rate_info *info, int16_t *dst, uint32_t max)
|
|
{
|
|
int64_t x, alpha, distance;
|
|
uint32_t ret;
|
|
int32_t pos, bpos, gx, gy;
|
|
int16_t *src;
|
|
/*
|
|
* Plain, straight forward 64bit arith. No bit-magic applied here.
|
|
*/
|
|
ret = 0;
|
|
alpha = info->alpha;
|
|
gx = info->gx;
|
|
gy = info->gy;
|
|
pos = info->pos;
|
|
bpos = info->bpos;
|
|
src = info->buffer;
|
|
for (;;) {
|
|
if (alpha < gx) {
|
|
alpha += gy;
|
|
pos += 2;
|
|
if (pos == bpos)
|
|
break;
|
|
} else {
|
|
alpha -= gx;
|
|
distance = gy - alpha;
|
|
x = (alpha * src[pos - 2]) + (distance * src[pos]);
|
|
dst[ret++] = x / gy;
|
|
x = (alpha * src[pos - 1]) + (distance * src[pos + 1]);
|
|
dst[ret++] = x / gy;
|
|
if (ret == max)
|
|
break;
|
|
}
|
|
}
|
|
info->alpha = alpha;
|
|
info->pos = pos;
|
|
return ret;
|
|
}
|
|
|
|
static uint32_t
|
|
feed_convert_scale64(struct feed_rate_info *info, int16_t *dst, uint32_t max)
|
|
{
|
|
int64_t x, alpha, distance;
|
|
uint32_t ret;
|
|
int32_t pos, bpos, gx, gy, roll;
|
|
int16_t *src;
|
|
/*
|
|
* 64bit scaling.
|
|
*/
|
|
ret = 0;
|
|
roll = info->roll;
|
|
alpha = info->alpha * info->scale;
|
|
gx = info->gx * info->scale;
|
|
gy = info->gy * info->scale;
|
|
pos = info->pos;
|
|
bpos = info->bpos;
|
|
src = info->buffer;
|
|
for (;;) {
|
|
if (alpha < gx) {
|
|
alpha += gy;
|
|
pos += 2;
|
|
if (pos == bpos)
|
|
break;
|
|
} else {
|
|
alpha -= gx;
|
|
distance = gy - alpha;
|
|
x = (alpha * src[pos - 2]) + (distance * src[pos]);
|
|
dst[ret++] = x >> roll;
|
|
x = (alpha * src[pos - 1]) + (distance * src[pos + 1]);
|
|
dst[ret++] = x >> roll;
|
|
if (ret == max)
|
|
break;
|
|
}
|
|
}
|
|
info->alpha = alpha / info->scale;
|
|
info->pos = pos;
|
|
return ret;
|
|
}
|
|
|
|
static uint32_t
|
|
feed_convert_scale32(struct feed_rate_info *info, int16_t *dst, uint32_t max)
|
|
{
|
|
uint32_t ret;
|
|
int32_t x, pos, bpos, gx, gy, alpha, roll, distance;
|
|
int16_t *src;
|
|
/*
|
|
* 32bit scaling.
|
|
*/
|
|
ret = 0;
|
|
roll = info->roll;
|
|
alpha = info->alpha * info->scale;
|
|
gx = info->gx * info->scale;
|
|
gy = info->gy * info->scale;
|
|
pos = info->pos;
|
|
bpos = info->bpos;
|
|
src = info->buffer;
|
|
for (;;) {
|
|
if (alpha < gx) {
|
|
alpha += gy;
|
|
pos += 2;
|
|
if (pos == bpos)
|
|
break;
|
|
} else {
|
|
alpha -= gx;
|
|
distance = gy - alpha;
|
|
x = (alpha * src[pos - 2]) + (distance * src[pos]);
|
|
dst[ret++] = x >> roll;
|
|
x = (alpha * src[pos - 1]) + (distance * src[pos + 1]);
|
|
dst[ret++] = x >> roll;
|
|
if (ret == max)
|
|
break;
|
|
}
|
|
}
|
|
info->alpha = alpha / info->scale;
|
|
info->pos = pos;
|
|
return ret;
|
|
}
|
|
|
|
static uint32_t
|
|
feed_convert_plain(struct feed_rate_info *info, int16_t *dst, uint32_t max)
|
|
{
|
|
uint32_t ret;
|
|
int32_t pos, bpos, gx, gy, alpha;
|
|
int16_t *src;
|
|
/*
|
|
* Plain copy.
|
|
*/
|
|
ret = 0;
|
|
gx = info->gx;
|
|
gy = info->gy;
|
|
alpha = info->alpha;
|
|
pos = info->pos;
|
|
bpos = info->bpos;
|
|
src = info->buffer;
|
|
for (;;) {
|
|
if (alpha < gx) {
|
|
alpha += gy;
|
|
pos += 2;
|
|
if (pos == bpos)
|
|
break;
|
|
} else {
|
|
alpha -= gx;
|
|
dst[ret++] = src[pos];
|
|
dst[ret++] = src[pos + 1];
|
|
if (ret == max)
|
|
break;
|
|
}
|
|
}
|
|
info->pos = pos;
|
|
info->alpha = alpha;
|
|
return ret;
|
|
}
|
|
|
|
static int32_t
|
|
feed_rate(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b,
|
|
uint32_t count, void *source)
|
|
{
|
|
struct feed_rate_info *info = f->data;
|
|
uint32_t i;
|
|
int32_t fetch, slot;
|
|
int16_t *dst = (int16_t *)b;
|
|
/*
|
|
* This loop has been optimized to generalize both up / down
|
|
* sampling without causing missing samples or excessive buffer
|
|
* feeding.
|
|
*/
|
|
RATE_ASSERT(count >= 4 && count % 4 == 0,
|
|
("%s: Count size not byte integral\n", __func__));
|
|
count >>= 1;
|
|
slot = (((info->gx * (count >> 1)) + info->gy - info->alpha - 1) / info->gy) << 1;
|
|
/*
|
|
* Optimize buffer feeding aggresively to ensure calculated slot
|
|
* can be fitted nicely into available buffer free space, hence
|
|
* avoiding multiple feeding.
|
|
*/
|
|
if (info->pos != 2 && info->bpos - info->pos == 2 &&
|
|
info->bpos + slot > info->bufsz) {
|
|
/*
|
|
* Copy last unit sample and its previous to
|
|
* beginning of buffer.
|
|
*/
|
|
info->buffer[0] = info->buffer[info->pos - 2];
|
|
info->buffer[1] = info->buffer[info->pos - 1];
|
|
info->buffer[2] = info->buffer[info->pos];
|
|
info->buffer[3] = info->buffer[info->pos + 1];
|
|
info->pos = 2;
|
|
info->bpos = 4;
|
|
}
|
|
RATE_ASSERT(slot >= 0, ("%s: Negative Slot: %d\n",
|
|
__func__, slot));
|
|
i = 0;
|
|
for (;;) {
|
|
for (;;) {
|
|
fetch = info->bufsz - info->bpos;
|
|
RATE_ASSERT(fetch >= 0,
|
|
("%s: Buffer overrun: %d > %d\n",
|
|
__func__, info->bpos, info->bufsz));
|
|
if (slot < fetch)
|
|
fetch = slot;
|
|
if (fetch > 0) {
|
|
RATE_ASSERT(fetch % 2 == 0,
|
|
("%s: Fetch size not sample integral\n",
|
|
__func__));
|
|
fetch = FEEDER_FEED(f->source, c,
|
|
(uint8_t *)(info->buffer + info->bpos),
|
|
fetch << 1, source);
|
|
if (fetch == 0)
|
|
break;
|
|
RATE_ASSERT(fetch % 4 == 0,
|
|
("%s: Fetch size not byte integral\n",
|
|
__func__));
|
|
fetch >>= 1;
|
|
info->bpos += fetch;
|
|
slot -= fetch;
|
|
RATE_ASSERT(slot >= 0,
|
|
("%s: Negative Slot: %d\n", __func__
|
|
slot));
|
|
if (slot == 0)
|
|
break;
|
|
if (info->bpos == info->bufsz)
|
|
break;
|
|
} else
|
|
break;
|
|
}
|
|
if (info->pos == info->bpos) {
|
|
RATE_ASSERT(info->pos == 2,
|
|
("%s: EOF while in progress\n", __func__));
|
|
break;
|
|
}
|
|
RATE_ASSERT(info->pos <= info->bpos,
|
|
("%s: Buffer overrun: %d > %d\n", __func__,
|
|
info->pos, info->bpos));
|
|
RATE_ASSERT(info->pos < info->bpos,
|
|
("%s: Zero buffer!\n", __func__));
|
|
RATE_ASSERT((info->bpos - info->pos) % 2 == 0,
|
|
("%s: Buffer not sample integral\n", __func__));
|
|
i += info->convert(info, dst + i, count - i);
|
|
RATE_ASSERT(info->pos <= info->bpos,
|
|
("%s: Buffer overrun: %d > %d\n",
|
|
__func__, info->pos, info->bpos));
|
|
if (info->pos == info->bpos) {
|
|
/*
|
|
* End of buffer cycle. Copy last unit sample
|
|
* to beginning of buffer so next cycle can
|
|
* interpolate using it.
|
|
*/
|
|
info->buffer[0] = info->buffer[info->pos - 2];
|
|
info->buffer[1] = info->buffer[info->pos - 1];
|
|
info->bpos = 2;
|
|
info->pos = 2;
|
|
}
|
|
if (i == count)
|
|
break;
|
|
}
|
|
return i << 1;
|
|
}
|
|
|
|
static struct pcm_feederdesc feeder_rate_desc[] = {
|
|
{FEEDER_RATE, AFMT_S16_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0},
|
|
{0, 0, 0, 0},
|
|
};
|
|
static kobj_method_t feeder_rate_methods[] = {
|
|
KOBJMETHOD(feeder_init, feed_rate_init),
|
|
KOBJMETHOD(feeder_free, feed_rate_free),
|
|
KOBJMETHOD(feeder_set, feed_rate_set),
|
|
KOBJMETHOD(feeder_get, feed_rate_get),
|
|
KOBJMETHOD(feeder_feed, feed_rate),
|
|
{0, 0}
|
|
};
|
|
FEEDER_DECLARE(feeder_rate, 2, NULL);
|