freebsd-dev/sys/dev/sound/pcm/feeder_rate.c
Alexander Leidinger 87506547d2 Whats New:
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@
2005-07-31 16:16:22 +00:00

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);