a580b31a54
in every sense. General ------- - Multichannel safe, endian safe, format safe * Large part of critical pcm filters such as vchan.c, feeder_rate.c, feeder_volume.c, feeder_fmt.c and feeder.c has been rewritten so that using them does not cause the pcm data to be converted to 16bit little endian. * Macrosses for accessing pcm data safely are defined within sound.h in the form of PCM_READ_* / PCM_WRITE_* * Currently, most of them are probably limited for mono/stereo handling, but the future addition of true multichannel will be much easier. - Low latency operation * Well, this require lot more works to do not just within sound driver, but we're heading towards right direction. Buffer/block sizing within channel.c is rewritten to calculate precise allocation for various combination of sample/data/rate size. As a result, applying correct SNDCTL_DSP_POLICY value will achive expected latency behaviour simmilar to what commercial 4front driver do. * Signal handling fix. ctrl+c of "cat /dev/zero > /dev/dsp" does not result long delay. * Eliminate sound truncation if the sound data is too small. DIY: 1) Download / extract http://people.freebsd.org/~ariff/lowlatency/shortfiles.tar.gz 2) Do a comparison between "cat state*.au > /dev/dsp" and "for x in state*.au ; do cat $x > /dev/dsp ; done" - there should be no "perceivable" differences. Double close for PR kern/31445. CAVEAT: Low latency come with (unbearable) price especially for poorly written applications. Applications that trying to act smarter by requesting (wrong) blocksize/blockcount will suffer the most. Fixup samples/patches can be found at: http://people.freebsd.org/~ariff/ports/ - Switch minimum/maximum sampling rate limit to "1" and "2016000" (48k * 42) due to closer compatibility with 4front driver. Discussed with: marcus@ (long time ago?) - All driver specific sysctls in the form of "hw.snd.pcm%d.*" have been moved to their own dev sysctl nodes, notably: hw.snd.pcm%d.vchans -> dev.pcm.%d.vchans Bump __FreeBSD_version. Driver specific --------------- - Ditto for sysctls. - snd_atiixp, snd_es137x, snd_via8233, snd_hda * Numerous cleanups and fixes. * _EXPERIMENTAL_ polling mode support using simple callout_* mechanisme. This was intended for pure debugging and latency measurement, but proven good enough in few unexpected and rare cases (such as problematic shared IRQ with GIANT devices - USB). Polling can be enabled/disabled through dev.pcm.0.polling. Disabled by default. - snd_ich * Fix possible overflow during speed calibration. Delay final initialization (pcm_setstatus) after calibration finished. PR: kern/100169 Tested by: Kevin Overman <oberman@es.net> * Inverted EAPD for few Nec VersaPro. PR: kern/104715 Submitted by: KAWATA Masahiko <kawata@mta.biglobe.ne.jp> Thanks to various people, notably Joel Dahl, Yuriy Tsibizov, Kevin Oberman, those at #freebsd-azalia @ freenode and others for testing. Joel Dahl will do the manpage update.
607 lines
19 KiB
C
607 lines
19 KiB
C
/*-
|
|
* Copyright (c) 1999 Cameron Grant <cg@FreeBSD.org>
|
|
* Copyright (c) 2003 Orion Hodson <orion@FreeBSD.org>
|
|
* Copyright (c) 2005 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.
|
|
*
|
|
* 2006-02-21:
|
|
* ==========
|
|
*
|
|
* Major cleanup and overhaul to remove much redundant codes.
|
|
* Highlights:
|
|
* 1) Support for signed / unsigned 16, 24 and 32 bit,
|
|
* big / little endian,
|
|
* 2) Unlimited channels.
|
|
*
|
|
* 2005-06-11:
|
|
* ==========
|
|
*
|
|
* *New* and rewritten soft sample rate converter supporting arbitrary sample
|
|
* rates, fine grained scaling/coefficients and a unified up/down stereo
|
|
* converter. Most of the disclaimers from orion's notes also applies
|
|
* here, regarding linear interpolation deficiencies and pre/post
|
|
* anti-aliasing filtering issues. This version comes with a 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 the 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, the 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_TEST(x, y) /* if (!(x)) printf y */
|
|
#define RATE_TRACE(x...) /* printf(x) */
|
|
|
|
MALLOC_DEFINE(M_RATEFEEDER, "ratefeed", "pcm rate feeder");
|
|
|
|
/*
|
|
* Don't overflow 32bit integer, since everything is done
|
|
* within 32bit arithmetic.
|
|
*/
|
|
#define RATE_FACTOR_MIN 1
|
|
#define RATE_FACTOR_MAX PCM_S24_MAX
|
|
#define RATE_FACTOR_SAFE(val) (!((val) < RATE_FACTOR_MIN || \
|
|
(val) > RATE_FACTOR_MAX))
|
|
|
|
struct feed_rate_info;
|
|
|
|
typedef uint32_t (*feed_rate_converter)(struct feed_rate_info *, uint8_t *, uint32_t);
|
|
|
|
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 limit */
|
|
uint32_t bufsz_init; /* allocated buffer size */
|
|
uint32_t channels; /* total channels */
|
|
uint32_t bps; /* bytes-per-sample */
|
|
uint32_t stray; /* stray bytes */
|
|
uint8_t *buffer;
|
|
feed_rate_converter convert;
|
|
};
|
|
|
|
int feeder_rate_min = FEEDRATE_RATEMIN;
|
|
int feeder_rate_max = FEEDRATE_RATEMAX;
|
|
int feeder_rate_round = FEEDRATE_ROUNDHZ;
|
|
|
|
TUNABLE_INT("hw.snd.feeder_rate_min", &feeder_rate_min);
|
|
TUNABLE_INT("hw.snd.feeder_rate_max", &feeder_rate_max);
|
|
TUNABLE_INT("hw.snd.feeder_rate_round", &feeder_rate_round);
|
|
|
|
static int
|
|
sysctl_hw_snd_feeder_rate_min(SYSCTL_HANDLER_ARGS)
|
|
{
|
|
int err, val;
|
|
|
|
val = feeder_rate_min;
|
|
err = sysctl_handle_int(oidp, &val, sizeof(val), req);
|
|
if (RATE_FACTOR_SAFE(val) && val < feeder_rate_max)
|
|
feeder_rate_min = val;
|
|
else
|
|
err = EINVAL;
|
|
return err;
|
|
}
|
|
SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_rate_min, CTLTYPE_INT | CTLFLAG_RW,
|
|
0, sizeof(int), sysctl_hw_snd_feeder_rate_min, "I",
|
|
"minimum allowable rate");
|
|
|
|
static int
|
|
sysctl_hw_snd_feeder_rate_max(SYSCTL_HANDLER_ARGS)
|
|
{
|
|
int err, val;
|
|
|
|
val = feeder_rate_max;
|
|
err = sysctl_handle_int(oidp, &val, sizeof(val), req);
|
|
if (RATE_FACTOR_SAFE(val) && val > feeder_rate_min)
|
|
feeder_rate_max = val;
|
|
else
|
|
err = EINVAL;
|
|
return err;
|
|
}
|
|
SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_rate_max, CTLTYPE_INT | CTLFLAG_RW,
|
|
0, sizeof(int), sysctl_hw_snd_feeder_rate_max, "I",
|
|
"maximum allowable rate");
|
|
|
|
static int
|
|
sysctl_hw_snd_feeder_rate_round(SYSCTL_HANDLER_ARGS)
|
|
{
|
|
int err, val;
|
|
|
|
val = feeder_rate_round;
|
|
err = sysctl_handle_int(oidp, &val, sizeof(val), req);
|
|
if (val < FEEDRATE_ROUNDHZ_MIN || val > FEEDRATE_ROUNDHZ_MAX)
|
|
err = EINVAL;
|
|
else
|
|
feeder_rate_round = val - (val % FEEDRATE_ROUNDHZ);
|
|
return err;
|
|
}
|
|
SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_rate_round, CTLTYPE_INT | CTLFLAG_RW,
|
|
0, sizeof(int), sysctl_hw_snd_feeder_rate_round, "I",
|
|
"sample rate converter rounding threshold");
|
|
|
|
#define FEEDER_RATE_CONVERT(FMTBIT, RATE_INTCAST, SIGN, SIGNS, ENDIAN, ENDIANS) \
|
|
static uint32_t \
|
|
feed_convert_##SIGNS##FMTBIT##ENDIANS(struct feed_rate_info *info, \
|
|
uint8_t *dst, uint32_t max) \
|
|
{ \
|
|
uint32_t ret, smpsz, bps, ch, pos, bpos, gx, gy, alpha, distance; \
|
|
int32_t x, y; \
|
|
int i; \
|
|
uint8_t *src, *sx, *sy; \
|
|
\
|
|
ret = 0; \
|
|
alpha = info->alpha; \
|
|
gx = info->gx; \
|
|
gy = info->gy; \
|
|
pos = info->pos; \
|
|
bpos = info->bpos; \
|
|
src = info->buffer + pos; \
|
|
ch = info->channels; \
|
|
bps = info->bps; \
|
|
smpsz = bps * ch; \
|
|
for (;;) { \
|
|
if (alpha < gx) { \
|
|
alpha += gy; \
|
|
pos += smpsz; \
|
|
if (pos == bpos) \
|
|
break; \
|
|
src += smpsz; \
|
|
} else { \
|
|
alpha -= gx; \
|
|
distance = (alpha << PCM_FXSHIFT) / gy; \
|
|
sx = src - smpsz; \
|
|
sy = src; \
|
|
i = ch; \
|
|
do { \
|
|
x = PCM_READ_##SIGN##FMTBIT##_##ENDIAN(sx); \
|
|
y = PCM_READ_##SIGN##FMTBIT##_##ENDIAN(sy); \
|
|
x = (((RATE_INTCAST)x * distance) + \
|
|
((RATE_INTCAST)y * ((1 << PCM_FXSHIFT) - \
|
|
distance))) >> PCM_FXSHIFT; \
|
|
PCM_WRITE_##SIGN##FMTBIT##_##ENDIAN(dst, x); \
|
|
dst += bps; \
|
|
sx += bps; \
|
|
sy += bps; \
|
|
ret += bps; \
|
|
} while (--i); \
|
|
if (ret == max) \
|
|
break; \
|
|
} \
|
|
} \
|
|
info->alpha = alpha; \
|
|
info->pos = pos; \
|
|
return ret; \
|
|
}
|
|
|
|
FEEDER_RATE_CONVERT(8, int32_t, S, s, NE, ne)
|
|
FEEDER_RATE_CONVERT(16, int32_t, S, s, LE, le)
|
|
FEEDER_RATE_CONVERT(24, int32_t, S, s, LE, le)
|
|
FEEDER_RATE_CONVERT(32, intpcm_t, S, s, LE, le)
|
|
FEEDER_RATE_CONVERT(16, int32_t, S, s, BE, be)
|
|
FEEDER_RATE_CONVERT(24, int32_t, S, s, BE, be)
|
|
FEEDER_RATE_CONVERT(32, intpcm_t, S, s, BE, be)
|
|
/* unsigned */
|
|
FEEDER_RATE_CONVERT(8, int32_t, U, u, NE, ne)
|
|
FEEDER_RATE_CONVERT(16, int32_t, U, u, LE, le)
|
|
FEEDER_RATE_CONVERT(24, int32_t, U, u, LE, le)
|
|
FEEDER_RATE_CONVERT(32, intpcm_t, U, u, LE, le)
|
|
FEEDER_RATE_CONVERT(16, int32_t, U, u, BE, be)
|
|
FEEDER_RATE_CONVERT(24, int32_t, U, u, BE, be)
|
|
FEEDER_RATE_CONVERT(32, intpcm_t, U, u, BE, be)
|
|
|
|
static void
|
|
feed_speed_ratio(uint32_t src, uint32_t dst, uint32_t *gx, uint32_t *gy)
|
|
{
|
|
uint32_t w, x = src, y = dst;
|
|
|
|
while (y != 0) {
|
|
w = x % y;
|
|
x = y;
|
|
y = w;
|
|
}
|
|
*gx = src / x;
|
|
*gy = dst / x;
|
|
}
|
|
|
|
static void
|
|
feed_rate_reset(struct feed_rate_info *info)
|
|
{
|
|
info->src = info->rsrc - (info->rsrc %
|
|
((feeder_rate_round > 0) ? feeder_rate_round : 1));
|
|
info->dst = info->rdst - (info->rdst %
|
|
((feeder_rate_round > 0) ? feeder_rate_round : 1));
|
|
info->gx = 1;
|
|
info->gy = 1;
|
|
info->alpha = 0;
|
|
info->channels = 2;
|
|
info->bps = 2;
|
|
info->convert = NULL;
|
|
info->bufsz = info->bufsz_init;
|
|
info->pos = 4;
|
|
info->bpos = 8;
|
|
info->stray = 0;
|
|
}
|
|
|
|
static int
|
|
feed_rate_setup(struct pcm_feeder *f)
|
|
{
|
|
struct feed_rate_info *info = f->data;
|
|
static const struct {
|
|
uint32_t format; /* pcm / audio format */
|
|
uint32_t bps; /* bytes-per-sample, regardless of
|
|
total channels */
|
|
feed_rate_converter convert;
|
|
} convtbl[] = {
|
|
{ AFMT_S8, PCM_8_BPS, feed_convert_s8ne },
|
|
{ AFMT_S16_LE, PCM_16_BPS, feed_convert_s16le },
|
|
{ AFMT_S24_LE, PCM_24_BPS, feed_convert_s24le },
|
|
{ AFMT_S32_LE, PCM_32_BPS, feed_convert_s32le },
|
|
{ AFMT_S16_BE, PCM_16_BPS, feed_convert_s16be },
|
|
{ AFMT_S24_BE, PCM_24_BPS, feed_convert_s24be },
|
|
{ AFMT_S32_BE, PCM_32_BPS, feed_convert_s32be },
|
|
/* unsigned */
|
|
{ AFMT_U8, PCM_8_BPS, feed_convert_u8ne },
|
|
{ AFMT_U16_LE, PCM_16_BPS, feed_convert_u16le },
|
|
{ AFMT_U24_LE, PCM_24_BPS, feed_convert_u24le },
|
|
{ AFMT_U32_LE, PCM_32_BPS, feed_convert_u32le },
|
|
{ AFMT_U16_BE, PCM_16_BPS, feed_convert_u16be },
|
|
{ AFMT_U24_BE, PCM_24_BPS, feed_convert_u24be },
|
|
{ AFMT_U32_BE, PCM_32_BPS, feed_convert_u32be },
|
|
{ 0, 0, NULL },
|
|
};
|
|
uint32_t i;
|
|
|
|
feed_rate_reset(info);
|
|
|
|
if (info->src != info->dst)
|
|
feed_speed_ratio(info->src, info->dst,
|
|
&info->gx, &info->gy);
|
|
|
|
if (!(RATE_FACTOR_SAFE(info->gx) && RATE_FACTOR_SAFE(info->gy)))
|
|
return -1;
|
|
|
|
for (i = 0; i < sizeof(convtbl) / sizeof(*convtbl); i++) {
|
|
if (convtbl[i].format == 0)
|
|
return -1;
|
|
if ((f->desc->out & ~AFMT_STEREO) == convtbl[i].format) {
|
|
info->bps = convtbl[i].bps;
|
|
info->convert = convtbl[i].convert;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* No need to interpolate/decimate, just do plain copy.
|
|
*/
|
|
if (info->gx == info->gy)
|
|
info->convert = NULL;
|
|
|
|
info->channels = (f->desc->out & AFMT_STEREO) ? 2 : 1;
|
|
info->pos = info->bps * info->channels;
|
|
info->bpos = info->pos << 1;
|
|
info->bufsz -= info->bufsz % info->pos;
|
|
|
|
memset(info->buffer, sndbuf_zerodata(f->desc->out), info->bpos);
|
|
|
|
RATE_TRACE("%s: %u (%u) -> %u (%u) [%u/%u] , "
|
|
"format=0x%08x, channels=%u, bufsz=%u\n",
|
|
__func__, info->src, info->rsrc, info->dst, info->rdst,
|
|
info->gx, info->gy,
|
|
f->desc->out, info->channels,
|
|
info->bufsz - info->pos);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
feed_rate_set(struct pcm_feeder *f, int what, int32_t value)
|
|
{
|
|
struct feed_rate_info *info = f->data;
|
|
|
|
if (value < feeder_rate_min || value > feeder_rate_max)
|
|
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;
|
|
|
|
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;
|
|
|
|
if (f->desc->out != f->desc->in)
|
|
return EINVAL;
|
|
|
|
info = malloc(sizeof(*info), M_RATEFEEDER, M_NOWAIT | M_ZERO);
|
|
if (info == NULL)
|
|
return ENOMEM;
|
|
/*
|
|
* bufsz = sample from last cycle + conversion space
|
|
*/
|
|
info->bufsz_init = 8 + feeder_buffersize;
|
|
info->buffer = malloc(sizeof(*info->buffer) * info->bufsz_init,
|
|
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 int
|
|
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, smpsz;
|
|
int32_t fetch, slot;
|
|
|
|
if (info->convert == NULL)
|
|
return FEEDER_FEED(f->source, c, b, count, source);
|
|
|
|
/*
|
|
* This loop has been optimized to generalize both up / down
|
|
* sampling without causing missing samples or excessive buffer
|
|
* feeding. The tricky part is to calculate *precise* (slot) value
|
|
* needed for the entire conversion space since we are bound to
|
|
* return and fill up the buffer according to the requested 'count'.
|
|
* Too much feeding will cause the extra buffer stay within temporary
|
|
* circular buffer forever and always manifest itself as a truncated
|
|
* sound during end of playback / recording. Too few, and we end up
|
|
* with possible underruns and waste of cpu cycles.
|
|
*
|
|
* 'Stray' management exist to combat with possible unaligned
|
|
* buffering by the caller.
|
|
*/
|
|
smpsz = info->bps * info->channels;
|
|
RATE_TEST(count >= smpsz && (count % smpsz) == 0,
|
|
("%s: Count size not sample integral (%d)\n", __func__, count));
|
|
if (count < smpsz)
|
|
return 0;
|
|
count -= count % smpsz;
|
|
/*
|
|
* This slot count formula will stay here for the next million years
|
|
* to come. This is the key of our circular buffering precision.
|
|
*/
|
|
slot = (((info->gx * (count / smpsz)) + info->gy - info->alpha - 1) / info->gy) * smpsz;
|
|
RATE_TEST((slot % smpsz) == 0, ("%s: Slot count not sample integral (%d)\n",
|
|
__func__, slot));
|
|
RATE_TEST(info->stray == 0, ("%s: [1] Stray bytes: %u\n",
|
|
__func__,info->stray));
|
|
if (info->pos != smpsz && info->bpos - info->pos == smpsz &&
|
|
info->bpos + slot > info->bufsz) {
|
|
/*
|
|
* Copy last unit sample and its previous to
|
|
* beginning of buffer.
|
|
*/
|
|
bcopy(info->buffer + info->pos - smpsz, info->buffer,
|
|
sizeof(*info->buffer) * (smpsz << 1));
|
|
info->pos = smpsz;
|
|
info->bpos = smpsz << 1;
|
|
}
|
|
RATE_ASSERT(slot >= 0, ("%s: Negative Slot: %d\n",
|
|
__func__, slot));
|
|
i = 0;
|
|
for (;;) {
|
|
for (;;) {
|
|
fetch = info->bufsz - info->bpos;
|
|
fetch -= info->stray;
|
|
RATE_ASSERT(fetch >= 0,
|
|
("%s: [1] Buffer overrun: %d > %d\n",
|
|
__func__, info->bpos, info->bufsz));
|
|
if (slot < fetch)
|
|
fetch = slot;
|
|
if (fetch > 0) {
|
|
RATE_ASSERT((int32_t)(info->bpos - info->stray) >= 0 &&
|
|
(info->bpos - info->stray) < info->bufsz,
|
|
("%s: DANGER - BUFFER OVERRUN! bufsz=%d, pos=%d\n", __func__,
|
|
info->bufsz, info->bpos - info->stray));
|
|
fetch = FEEDER_FEED(f->source, c,
|
|
info->buffer + info->bpos - info->stray,
|
|
fetch, source);
|
|
info->stray = 0;
|
|
if (fetch == 0)
|
|
break;
|
|
RATE_TEST((fetch % smpsz) == 0,
|
|
("%s: Fetch size not sample integral (%d)\n",
|
|
__func__, fetch));
|
|
info->stray += fetch % smpsz;
|
|
RATE_TEST(info->stray == 0,
|
|
("%s: Stray bytes detected (%d)\n",
|
|
__func__, info->stray));
|
|
fetch -= fetch % smpsz;
|
|
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_TEST(info->pos == smpsz,
|
|
("%s: EOF while in progress\n", __func__));
|
|
break;
|
|
}
|
|
RATE_ASSERT(info->pos <= info->bpos,
|
|
("%s: [2] 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) % smpsz) == 0,
|
|
("%s: Buffer not sample integral (%d)\n",
|
|
__func__, info->bpos - info->pos));
|
|
i += info->convert(info, b + i, count - i);
|
|
RATE_ASSERT(info->pos <= info->bpos,
|
|
("%s: [3] 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.
|
|
*/
|
|
RATE_TEST(info->stray == 0, ("%s: [2] Stray bytes: %u\n", __func__, info->stray));
|
|
bcopy(info->buffer + info->pos - smpsz, info->buffer,
|
|
sizeof(*info->buffer) * smpsz);
|
|
info->bpos = smpsz;
|
|
info->pos = smpsz;
|
|
}
|
|
if (i == count)
|
|
break;
|
|
}
|
|
|
|
RATE_TEST((slot == 0 && count == i) ||
|
|
(slot > 0 && count > i &&
|
|
info->pos == info->bpos && info->pos == smpsz),
|
|
("%s: Inconsistent slot/count! "
|
|
"Count Expect: %u , Got: %u, Slot Left: %d\n",
|
|
__func__, count, i, slot));
|
|
|
|
RATE_TEST(info->stray == 0, ("%s: [3] Stray bytes: %u\n", __func__, info->stray));
|
|
|
|
return i;
|
|
}
|
|
|
|
static struct pcm_feederdesc feeder_rate_desc[] = {
|
|
{FEEDER_RATE, AFMT_S8, AFMT_S8, 0},
|
|
{FEEDER_RATE, AFMT_S16_LE, AFMT_S16_LE, 0},
|
|
{FEEDER_RATE, AFMT_S24_LE, AFMT_S24_LE, 0},
|
|
{FEEDER_RATE, AFMT_S32_LE, AFMT_S32_LE, 0},
|
|
{FEEDER_RATE, AFMT_S16_BE, AFMT_S16_BE, 0},
|
|
{FEEDER_RATE, AFMT_S24_BE, AFMT_S24_BE, 0},
|
|
{FEEDER_RATE, AFMT_S32_BE, AFMT_S32_BE, 0},
|
|
{FEEDER_RATE, AFMT_S8 | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0},
|
|
{FEEDER_RATE, AFMT_S16_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0},
|
|
{FEEDER_RATE, AFMT_S24_LE | AFMT_STEREO, AFMT_S24_LE | AFMT_STEREO, 0},
|
|
{FEEDER_RATE, AFMT_S32_LE | AFMT_STEREO, AFMT_S32_LE | AFMT_STEREO, 0},
|
|
{FEEDER_RATE, AFMT_S16_BE | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0},
|
|
{FEEDER_RATE, AFMT_S24_BE | AFMT_STEREO, AFMT_S24_BE | AFMT_STEREO, 0},
|
|
{FEEDER_RATE, AFMT_S32_BE | AFMT_STEREO, AFMT_S32_BE | AFMT_STEREO, 0},
|
|
/* unsigned */
|
|
{FEEDER_RATE, AFMT_U8, AFMT_U8, 0},
|
|
{FEEDER_RATE, AFMT_U16_LE, AFMT_U16_LE, 0},
|
|
{FEEDER_RATE, AFMT_U24_LE, AFMT_U24_LE, 0},
|
|
{FEEDER_RATE, AFMT_U32_LE, AFMT_U32_LE, 0},
|
|
{FEEDER_RATE, AFMT_U16_BE, AFMT_U16_BE, 0},
|
|
{FEEDER_RATE, AFMT_U24_BE, AFMT_U24_BE, 0},
|
|
{FEEDER_RATE, AFMT_U32_BE, AFMT_U32_BE, 0},
|
|
{FEEDER_RATE, AFMT_U8 | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0},
|
|
{FEEDER_RATE, AFMT_U16_LE | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0},
|
|
{FEEDER_RATE, AFMT_U24_LE | AFMT_STEREO, AFMT_U24_LE | AFMT_STEREO, 0},
|
|
{FEEDER_RATE, AFMT_U32_LE | AFMT_STEREO, AFMT_U32_LE | AFMT_STEREO, 0},
|
|
{FEEDER_RATE, AFMT_U16_BE | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0},
|
|
{FEEDER_RATE, AFMT_U24_BE | AFMT_STEREO, AFMT_U24_BE | AFMT_STEREO, 0},
|
|
{FEEDER_RATE, AFMT_U32_BE | AFMT_STEREO, AFMT_U32_BE | 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);
|