/* * Copyright (c) 2003 Orion Hodson * 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. * * MAINTAINER: Orion Hodson * * 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/feedrate/ * * Special thanks to Ken Marx for exposing flaws in the code and for * testing revisions. * * $FreeBSD: src/sys/dev/sound/pcm/feeder_rate.c,v 1.2.2.3 2003/02/08 02:38:21 orion Exp $ * $DragonFly: src/sys/dev/sound/pcm/feeder_rate.c,v 1.2 2003/06/17 04:28:31 dillon Exp $ */ #ifdef _KERNEL #include #include "feeder_if.h" SND_DECLARE_FILE("$DragonFly: src/sys/dev/sound/pcm/feeder_rate.c,v 1.2 2003/06/17 04:28:31 dillon Exp $"); #endif /* _KERNEL */ MALLOC_DEFINE(M_RATEFEEDER, "ratefeed", "pcm rate feeder"); #ifndef RATE_ASSERT #define RATE_ASSERT(x, y) /* KASSERT(x) */ #endif /* RATE_ASSERT */ #ifndef RATE_TRACE #define RATE_TRACE(x...) /* printf(x) */ #endif /*****************************************************************************/ /* The following coefficients are coupled. They are chosen to be * guarantee calculable factors for the interpolation routine. They * have been tested over the range of RATEMIN-RATEMAX Hz. Decreasing * the granularity increases the required buffer size and affects the * gain values at different points in the space. These values were * found by running the test program with -p (probe) and some trial * and error. * * ROUNDHZ the granularity of sample rates (fits n*11025 and n*8000). * FEEDBUFSZ the amount of buffer space. * MINGAIN the minimum acceptable gain in coefficients search. */ #define ROUNDHZ 25 #define FEEDBUFSZ 8192 #define MINGAIN 92 #define RATEMIN 4000 #define RATEMAX 48000 struct feed_rate_info; typedef int (*rate_convert_method)(struct feed_rate_info *, uint32_t, uint32_t, int16_t *); static int convert_stereo_up(struct feed_rate_info *info, uint32_t src_ticks, uint32_t dst_ticks, int16_t *dst); static int convert_stereo_down(struct feed_rate_info *info, uint32_t src_ticks, uint32_t dst_ticks, int16_t *dst); struct feed_rate_info { uint32_t src, dst; /* source and destination rates */ uint16_t buffer_ticks; /* number of available samples in buffer */ uint16_t buffer_pos; /* next available sample in buffer */ uint16_t rounds; /* maximum number of cycle rounds w buffer */ uint16_t alpha; /* interpolation distance */ uint16_t sscale; /* src clock scale */ uint16_t dscale; /* dst clock scale */ uint16_t mscale; /* scale factor to avoid divide per sample */ uint16_t mroll; /* roll to again avoid divide per sample */ uint16_t channels; /* 1 = mono, 2 = stereo */ rate_convert_method convert; int16_t buffer[FEEDBUFSZ]; }; #define bytes_per_sample 2 #define src_ticks_per_cycle(info) (info->dscale * info->rounds) #define dst_ticks_per_cycle(info) (info->sscale * info->rounds) #define bytes_per_tick(info) (info->channels * bytes_per_sample) #define src_bytes_per_cycle(info) \ (src_ticks_per_cycle(info) * bytes_per_tick(info)) #define dst_bytes_per_cycle(info) \ (dst_ticks_per_cycle(info) * bytes_per_tick(info)) static uint32_t gcd(uint32_t x, uint32_t y) { uint32_t w; while (y != 0) { w = x % y; x = y; y = w; } return x; } static int feed_rate_setup(struct pcm_feeder *f) { struct feed_rate_info *info = f->data; uint32_t mscale, mroll, l, r, g; /* Beat sample rates down by greatest common divisor */ g = gcd(info->src, info->dst); info->sscale = info->dst / g; info->dscale = info->src / g; info->alpha = 0; info->buffer_ticks = 0; info->buffer_pos = 0; /* Pick suitable conversion routine */ if (info->src > info->dst) { info->convert = convert_stereo_down; } else { info->convert = convert_stereo_up; } /* * Determine number of conversion rounds that will fit into * buffer. NB Must set info->rounds to one before using * src_ticks_per_cycle here since it used by src_ticks_per_cycle. */ info->rounds = 1; r = (FEEDBUFSZ - bytes_per_tick(info)) / (src_ticks_per_cycle(info) * bytes_per_tick(info)); if (r == 0) { RATE_TRACE("Insufficient buffer space for conversion %d -> %d " "(%d < %d)\n", info->src, info->dst, FEEDBUFSZ, src_ticks_per_cycle(info) * bytes_per_tick(info)); return -1; } info->rounds = r; /* * Find scale and roll combination that allows us to trade * costly divide operations in the main loop for multiply-rolls. */ for (l = 96; l >= MINGAIN; l -= 3) { for (mroll = 0; mroll < 16; mroll ++) { mscale = (1 << mroll) / info->sscale; r = (mscale * info->sscale * 100) >> mroll; if (r > l && r <= 100) { info->mscale = mscale; info->mroll = mroll; RATE_TRACE("Converting %d to %d with " "mscale = %d and mroll = %d " "(gain = %d / 100)\n", info->src, info->dst, info->mscale, info->mroll, r); return 0; } } } RATE_TRACE("Failed to find a converter within %d%% gain for " "%d to %d.\n", l, info->src, info->dst); return -2; } static int feed_rate_set(struct pcm_feeder *f, int what, int value) { struct feed_rate_info *info = f->data; int rvalue; if (value < RATEMIN || value > RATEMAX) { return -1; } rvalue = (value / ROUNDHZ) * ROUNDHZ; if (value - rvalue > ROUNDHZ / 2) { rvalue += ROUNDHZ; } switch(what) { case FEEDRATE_SRC: info->src = rvalue; break; case FEEDRATE_DST: info->dst = rvalue; 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->src; case FEEDRATE_DST: return info->dst; 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_WAITOK | M_ZERO); if (info == NULL) return ENOMEM; info->src = DSP_DEFAULT_SPEED; info->dst = DSP_DEFAULT_SPEED; info->channels = 2; f->data = info; return 0; } static int feed_rate_free(struct pcm_feeder *f) { struct feed_rate_info *info = f->data; if (info) { free(info, M_RATEFEEDER); } f->data = NULL; return 0; } static int convert_stereo_up(struct feed_rate_info *info, uint32_t src_ticks, uint32_t dst_ticks, int16_t *dst) { uint32_t max_dst_ticks; int32_t alpha, dalpha, malpha, mroll, sp, dp, se, de, x, o; int16_t *src; sp = info->buffer_pos * 2; se = sp + src_ticks * 2; src = info->buffer; alpha = info->alpha * info->mscale; dalpha = info->dscale * info->mscale; /* Alpha increment */ malpha = info->sscale * info->mscale; /* Maximum allowed alpha value */ mroll = info->mroll; /* * For efficiency the main conversion loop should only depend on * one variable. We use the state to work out the maximum number * of output samples that are available and eliminate the checking of * sp from the loop. */ max_dst_ticks = src_ticks * info->dst / info->src - alpha / dalpha; if (max_dst_ticks < dst_ticks) { dst_ticks = max_dst_ticks; } dp = 0; de = dst_ticks * 2; /* * Unrolling this loop manually does not help much here because * of the alpha, malpha comparison. */ while (dp < de) { o = malpha - alpha; x = alpha * src[sp + 2] + o * src[sp]; dst[dp++] = x >> mroll; x = alpha * src[sp + 3] + o * src[sp + 1]; dst[dp++] = x >> mroll; alpha += dalpha; if (alpha >= malpha) { alpha -= malpha; sp += 2; } } RATE_ASSERT(sp <= se, ("%s: Source overrun\n", __func__)); info->buffer_pos = sp / info->channels; info->alpha = alpha / info->mscale; return dp / info->channels; } static int convert_stereo_down(struct feed_rate_info *info, uint32_t src_ticks, uint32_t dst_ticks, int16_t *dst) { int32_t alpha, dalpha, malpha, mroll, sp, dp, se, de, x, o, m, mdalpha, mstep; int16_t *src; sp = info->buffer_pos * 2; se = sp + src_ticks * 2; src = info->buffer; alpha = info->alpha * info->mscale; dalpha = info->dscale * info->mscale; /* Alpha increment */ malpha = info->sscale * info->mscale; /* Maximum allowed alpha value */ mroll = info->mroll; dp = 0; de = dst_ticks * 2; m = dalpha / malpha; mstep = m * 2; mdalpha = dalpha - m * malpha; /* * TODO: eliminate sp or dp from this loop comparison for a few * extra % performance. */ while (sp < se && dp < de) { o = malpha - alpha; x = alpha * src[sp + 2] + o * src[sp]; dst[dp++] = x >> mroll; x = alpha * src[sp + 3] + o * src[sp + 1]; dst[dp++] = x >> mroll; alpha += mdalpha; sp += mstep; if (alpha >= malpha) { alpha -= malpha; sp += 2; } } info->buffer_pos = sp / 2; info->alpha = alpha / info->mscale; RATE_ASSERT(info->buffer_pos <= info->buffer_ticks, ("%s: Source overrun\n", __func__)); return dp / 2; } 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 done, s_ticks, d_ticks; done = 0; RATE_ASSERT(info->channels == 2, ("%s: channels (%d) != 2", __func__, info->channels)); while (done < count) { /* Slurp in more data if input buffer is not full */ while (info->buffer_ticks < src_ticks_per_cycle(info)) { uint8_t *u8b; int fetch; fetch = src_bytes_per_cycle(info) - info->buffer_ticks * bytes_per_tick(info); u8b = (uint8_t*)info->buffer + (info->buffer_ticks + 1) * bytes_per_tick(info); fetch = FEEDER_FEED(f->source, c, u8b, fetch, source); RATE_ASSERT(fetch % bytes_per_tick(info) == 0, ("%s: fetched unaligned bytes (%d)", __func__, fetch)); info->buffer_ticks += fetch / bytes_per_tick(info); RATE_ASSERT(src_ticks_per_cycle(info) >= info->buffer_ticks, ("%s: buffer overfilled (%d > %d).", __func__, info->buffer_ticks, src_ticks_per_cycle(info))); if (fetch == 0) break; } /* Find amount of input buffer data that should be processed */ d_ticks = (count - done) / bytes_per_tick(info); s_ticks = info->buffer_ticks - info->buffer_pos; if (info->buffer_ticks != src_ticks_per_cycle(info)) { if (s_ticks > 8) s_ticks -= 8; else s_ticks = 0; } d_ticks = info->convert(info, s_ticks, d_ticks, (int16_t*)(b + done)); if (d_ticks == 0) break; done += d_ticks * bytes_per_tick(info); RATE_ASSERT(info->buffer_pos <= info->buffer_ticks, ("%s: buffer_ticks too big\n", __func__)); RATE_ASSERT(info->buffer_ticks <= src_ticks_per_cycle(info), ("too many ticks %d / %d\n", info->buffer_ticks, src_ticks_per_cycle(info))); RATE_TRACE("%s: ticks %5d / %d pos %d\n", __func__, info->buffer_ticks, src_ticks_per_cycle(info), info->buffer_pos); if (src_ticks_per_cycle(info) <= info->buffer_pos) { /* End of cycle reached, copy last samples to start */ uint8_t *u8b; u8b = (uint8_t*)info->buffer; bcopy(u8b + src_bytes_per_cycle(info), u8b, bytes_per_tick(info)); RATE_ASSERT(info->alpha == 0, ("%s: completed cycle with " "alpha non-zero", __func__, info->alpha)); info->buffer_pos = 0; info->buffer_ticks = 0; } } RATE_ASSERT(count >= done, ("%s: generated too many bytes of data (%d > %d).", __func__, done, count)); if (done != count) { RATE_TRACE("Only did %d of %d\n", done, count); } return done; } 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);