1/*
2 *  Linear rate converter plugin
3 *
4 *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
5 *                2004 by Jaroslav Kysela <perex@perex.cz>
6 *                2006 by Takashi Iwai <tiwai@suse.de>
7 *
8 *   This library is free software; you can redistribute it and/or modify
9 *   it under the terms of the GNU Lesser General Public License as
10 *   published by the Free Software Foundation; either version 2.1 of
11 *   the License, or (at your option) any later version.
12 *
13 *   This program is distributed in the hope that it will be useful,
14 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
15 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 *   GNU Lesser General Public License for more details.
17 *
18 *   You should have received a copy of the GNU Lesser General Public
19 *   License along with this library; if not, write to the Free Software
20 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
21 */
22
23#include "pcm_local.h"
24#include "pcm_plugin.h"
25#include "pcm_rate.h"
26#include "plugin_ops.h"
27#include "bswap.h"
28#include <inttypes.h>
29
30
31/* LINEAR_DIV needs to be large enough to handle resampling from 768000 -> 8000 */
32#define LINEAR_DIV_SHIFT 19
33#define LINEAR_DIV (1<<LINEAR_DIV_SHIFT)
34
35struct rate_linear {
36	unsigned int get_idx;
37	unsigned int put_idx;
38	unsigned int pitch;
39	unsigned int pitch_shift;	/* for expand interpolation */
40	unsigned int channels;
41	int16_t *old_sample;
42	void (*func)(struct rate_linear *rate,
43		     const snd_pcm_channel_area_t *dst_areas,
44		     snd_pcm_uframes_t dst_offset, unsigned int dst_frames,
45		     const snd_pcm_channel_area_t *src_areas,
46		     snd_pcm_uframes_t src_offset, unsigned int src_frames);
47};
48
49static snd_pcm_uframes_t input_frames(void *obj, snd_pcm_uframes_t frames)
50{
51	struct rate_linear *rate = obj;
52	if (frames == 0)
53		return 0;
54	/* Round toward zero */
55	return muldiv_near(frames, LINEAR_DIV, rate->pitch);
56}
57
58static snd_pcm_uframes_t output_frames(void *obj, snd_pcm_uframes_t frames)
59{
60	struct rate_linear *rate = obj;
61	if (frames == 0)
62		return 0;
63	/* Round toward zero */
64	return muldiv_near(frames, rate->pitch, LINEAR_DIV);
65}
66
67static void linear_expand(struct rate_linear *rate,
68			  const snd_pcm_channel_area_t *dst_areas,
69			  snd_pcm_uframes_t dst_offset, unsigned int dst_frames,
70			  const snd_pcm_channel_area_t *src_areas,
71			  snd_pcm_uframes_t src_offset, unsigned int src_frames)
72{
73#define GET16_LABELS
74#define PUT16_LABELS
75#include "plugin_ops.h"
76#undef GET16_LABELS
77#undef PUT16_LABELS
78	void *get = get16_labels[rate->get_idx];
79	void *put = put16_labels[rate->put_idx];
80	unsigned int get_threshold = rate->pitch;
81	unsigned int channel;
82	unsigned int src_frames1;
83	unsigned int dst_frames1;
84	int16_t sample = 0;
85	unsigned int pos;
86
87	for (channel = 0; channel < rate->channels; ++channel) {
88		const snd_pcm_channel_area_t *src_area = &src_areas[channel];
89		const snd_pcm_channel_area_t *dst_area = &dst_areas[channel];
90		const char *src;
91		char *dst;
92		int src_step, dst_step;
93		int16_t old_sample = 0;
94		int16_t new_sample;
95		int old_weight, new_weight;
96		src = snd_pcm_channel_area_addr(src_area, src_offset);
97		dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
98		src_step = snd_pcm_channel_area_step(src_area);
99		dst_step = snd_pcm_channel_area_step(dst_area);
100		src_frames1 = 0;
101		dst_frames1 = 0;
102		new_sample = rate->old_sample[channel];
103		pos = get_threshold;
104		while (dst_frames1 < dst_frames) {
105			if (pos >= get_threshold) {
106				pos -= get_threshold;
107				old_sample = new_sample;
108				if (src_frames1 < src_frames) {
109					goto *get;
110#define GET16_END after_get
111#include "plugin_ops.h"
112#undef GET16_END
113				after_get:
114					new_sample = sample;
115				}
116			}
117			new_weight = (pos << (16 - rate->pitch_shift)) / (get_threshold >> rate->pitch_shift);
118			old_weight = 0x10000 - new_weight;
119			sample = (old_sample * old_weight + new_sample * new_weight) >> 16;
120			goto *put;
121#define PUT16_END after_put
122#include "plugin_ops.h"
123#undef PUT16_END
124		after_put:
125			dst += dst_step;
126			dst_frames1++;
127			pos += LINEAR_DIV;
128			if (pos >= get_threshold) {
129				src += src_step;
130				src_frames1++;
131			}
132		}
133		rate->old_sample[channel] = new_sample;
134	}
135}
136
137/* optimized version for S16 format */
138static void linear_expand_s16(struct rate_linear *rate,
139			      const snd_pcm_channel_area_t *dst_areas,
140			      snd_pcm_uframes_t dst_offset, unsigned int dst_frames,
141			      const snd_pcm_channel_area_t *src_areas,
142			      snd_pcm_uframes_t src_offset, unsigned int src_frames)
143{
144	unsigned int channel;
145	unsigned int src_frames1;
146	unsigned int dst_frames1;
147	unsigned int get_threshold = rate->pitch;
148	unsigned int pos;
149
150	for (channel = 0; channel < rate->channels; ++channel) {
151		const snd_pcm_channel_area_t *src_area = &src_areas[channel];
152		const snd_pcm_channel_area_t *dst_area = &dst_areas[channel];
153		const int16_t *src;
154		int16_t *dst;
155		int src_step, dst_step;
156		int16_t old_sample = 0;
157		int16_t new_sample;
158		int old_weight, new_weight;
159		src = snd_pcm_channel_area_addr(src_area, src_offset);
160		dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
161		src_step = snd_pcm_channel_area_step(src_area) >> 1;
162		dst_step = snd_pcm_channel_area_step(dst_area) >> 1;
163		src_frames1 = 0;
164		dst_frames1 = 0;
165		new_sample = rate->old_sample[channel];
166		pos = get_threshold;
167		while (dst_frames1 < dst_frames) {
168			if (pos >= get_threshold) {
169				pos -= get_threshold;
170				old_sample = new_sample;
171				if (src_frames1 < src_frames)
172					new_sample = *src;
173			}
174			new_weight = (pos << (16 - rate->pitch_shift)) / (get_threshold >> rate->pitch_shift);
175			old_weight = 0x10000 - new_weight;
176			*dst = (old_sample * old_weight + new_sample * new_weight) >> 16;
177			dst += dst_step;
178			dst_frames1++;
179			pos += LINEAR_DIV;
180			if (pos >= get_threshold) {
181				src += src_step;
182				src_frames1++;
183			}
184		}
185		rate->old_sample[channel] = new_sample;
186	}
187}
188
189static void linear_shrink(struct rate_linear *rate,
190			  const snd_pcm_channel_area_t *dst_areas,
191			  snd_pcm_uframes_t dst_offset, unsigned int dst_frames,
192			  const snd_pcm_channel_area_t *src_areas,
193			  snd_pcm_uframes_t src_offset, unsigned int src_frames)
194{
195#define GET16_LABELS
196#define PUT16_LABELS
197#include "plugin_ops.h"
198#undef GET16_LABELS
199#undef PUT16_LABELS
200	void *get = get16_labels[rate->get_idx];
201	void *put = put16_labels[rate->put_idx];
202	unsigned int get_increment = rate->pitch;
203	unsigned int channel;
204	unsigned int src_frames1;
205	unsigned int dst_frames1;
206	int16_t sample = 0;
207	unsigned int pos;
208
209	for (channel = 0; channel < rate->channels; ++channel) {
210		const snd_pcm_channel_area_t *src_area = &src_areas[channel];
211		const snd_pcm_channel_area_t *dst_area = &dst_areas[channel];
212		const char *src;
213		char *dst;
214		int src_step, dst_step;
215		int16_t old_sample = 0;
216		int16_t new_sample = 0;
217		int old_weight, new_weight;
218		pos = LINEAR_DIV - get_increment; /* Force first sample to be copied */
219		src = snd_pcm_channel_area_addr(src_area, src_offset);
220		dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
221		src_step = snd_pcm_channel_area_step(src_area);
222		dst_step = snd_pcm_channel_area_step(dst_area);
223		src_frames1 = 0;
224		dst_frames1 = 0;
225		while (src_frames1 < src_frames) {
226
227			goto *get;
228#define GET16_END after_get
229#include "plugin_ops.h"
230#undef GET16_END
231		after_get:
232			new_sample = sample;
233			src += src_step;
234			src_frames1++;
235			pos += get_increment;
236			if (pos >= LINEAR_DIV) {
237				pos -= LINEAR_DIV;
238				old_weight = (pos << (32 - LINEAR_DIV_SHIFT)) / (get_increment >> (LINEAR_DIV_SHIFT - 16));
239				new_weight = 0x10000 - old_weight;
240				sample = (old_sample * old_weight + new_sample * new_weight) >> 16;
241				goto *put;
242#define PUT16_END after_put
243#include "plugin_ops.h"
244#undef PUT16_END
245			after_put:
246				dst += dst_step;
247				dst_frames1++;
248				if (CHECK_SANITY(dst_frames1 > dst_frames)) {
249					SNDERR("dst_frames overflow");
250					break;
251				}
252			}
253			old_sample = new_sample;
254		}
255	}
256}
257
258/* optimized version for S16 format */
259static void linear_shrink_s16(struct rate_linear *rate,
260			      const snd_pcm_channel_area_t *dst_areas,
261			      snd_pcm_uframes_t dst_offset, unsigned int dst_frames,
262			      const snd_pcm_channel_area_t *src_areas,
263			      snd_pcm_uframes_t src_offset, unsigned int src_frames)
264{
265	unsigned int get_increment = rate->pitch;
266	unsigned int channel;
267	unsigned int src_frames1;
268	unsigned int dst_frames1;
269	unsigned int pos = 0;
270
271	for (channel = 0; channel < rate->channels; ++channel) {
272		const snd_pcm_channel_area_t *src_area = &src_areas[channel];
273		const snd_pcm_channel_area_t *dst_area = &dst_areas[channel];
274		const int16_t *src;
275		int16_t *dst;
276		int src_step, dst_step;
277		int16_t old_sample = 0;
278		int16_t new_sample = 0;
279		int old_weight, new_weight;
280		pos = LINEAR_DIV - get_increment; /* Force first sample to be copied */
281		src = snd_pcm_channel_area_addr(src_area, src_offset);
282		dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
283		src_step = snd_pcm_channel_area_step(src_area) >> 1;
284		dst_step = snd_pcm_channel_area_step(dst_area) >> 1 ;
285		src_frames1 = 0;
286		dst_frames1 = 0;
287		while (src_frames1 < src_frames) {
288
289			new_sample = *src;
290			src += src_step;
291			src_frames1++;
292			pos += get_increment;
293			if (pos >= LINEAR_DIV) {
294				pos -= LINEAR_DIV;
295				old_weight = (pos << (32 - LINEAR_DIV_SHIFT)) / (get_increment >> (LINEAR_DIV_SHIFT - 16));
296				new_weight = 0x10000 - old_weight;
297				*dst = (old_sample * old_weight + new_sample * new_weight) >> 16;
298				dst += dst_step;
299				dst_frames1++;
300				if (CHECK_SANITY(dst_frames1 > dst_frames)) {
301					SNDERR("dst_frames overflow");
302					break;
303				}
304			}
305			old_sample = new_sample;
306		}
307	}
308}
309
310static void linear_convert(void *obj,
311			   const snd_pcm_channel_area_t *dst_areas,
312			   snd_pcm_uframes_t dst_offset, unsigned int dst_frames,
313			   const snd_pcm_channel_area_t *src_areas,
314			   snd_pcm_uframes_t src_offset, unsigned int src_frames)
315{
316	struct rate_linear *rate = obj;
317	rate->func(rate, dst_areas, dst_offset, dst_frames,
318		   src_areas, src_offset, src_frames);
319}
320
321static void linear_free(void *obj)
322{
323	struct rate_linear *rate = obj;
324
325	free(rate->old_sample);
326	rate->old_sample = NULL;
327}
328
329static int linear_init(void *obj, snd_pcm_rate_info_t *info)
330{
331	struct rate_linear *rate = obj;
332
333	rate->get_idx = snd_pcm_linear_get_index(info->in.format, SND_PCM_FORMAT_S16);
334	rate->put_idx = snd_pcm_linear_put_index(SND_PCM_FORMAT_S16, info->out.format);
335	if (info->in.rate < info->out.rate) {
336		if (info->in.format == info->out.format && info->in.format == SND_PCM_FORMAT_S16)
337			rate->func = linear_expand_s16;
338		else
339			rate->func = linear_expand;
340		/* pitch is get_threshold */
341	} else {
342		if (info->in.format == info->out.format && info->in.format == SND_PCM_FORMAT_S16)
343			rate->func = linear_shrink_s16;
344		else
345			rate->func = linear_shrink;
346		/* pitch is get_increment */
347	}
348	rate->pitch = (((uint64_t)info->out.rate * LINEAR_DIV) +
349		       (info->in.rate / 2)) / info->in.rate;
350	rate->channels = info->channels;
351
352	free(rate->old_sample);
353	rate->old_sample = malloc(sizeof(*rate->old_sample) * rate->channels);
354	if (! rate->old_sample)
355		return -ENOMEM;
356
357	return 0;
358}
359
360static int linear_adjust_pitch(void *obj, snd_pcm_rate_info_t *info)
361{
362	struct rate_linear *rate = obj;
363	snd_pcm_uframes_t cframes;
364
365	rate->pitch = (((uint64_t)info->out.period_size * LINEAR_DIV) +
366		       (info->in.period_size/2) ) / info->in.period_size;
367
368	cframes = input_frames(rate, info->out.period_size);
369	while (cframes != info->in.period_size) {
370		snd_pcm_uframes_t cframes_new;
371		if (cframes > info->in.period_size)
372			rate->pitch++;
373		else
374			rate->pitch--;
375		cframes_new = input_frames(rate, info->out.period_size);
376		if ((cframes > info->in.period_size && cframes_new < info->in.period_size) ||
377		    (cframes < info->in.period_size && cframes_new > info->in.period_size)) {
378			SNDERR("invalid pcm period_size %ld -> %ld",
379			       info->in.period_size, info->out.period_size);
380			return -EIO;
381		}
382		cframes = cframes_new;
383	}
384	if (rate->pitch >= LINEAR_DIV) {
385		/* shift for expand linear interpolation */
386		rate->pitch_shift = 0;
387		while ((rate->pitch >> rate->pitch_shift) >= (1 << 16))
388			rate->pitch_shift++;
389	}
390	return 0;
391}
392
393static void linear_reset(void *obj)
394{
395	struct rate_linear *rate = obj;
396
397	/* for expand */
398	if (rate->old_sample)
399		memset(rate->old_sample, 0, sizeof(*rate->old_sample) * rate->channels);
400}
401
402static void linear_close(void *obj)
403{
404	free(obj);
405}
406
407static int get_supported_rates(ATTRIBUTE_UNUSED void *rate,
408			       unsigned int *rate_min, unsigned int *rate_max)
409{
410	*rate_min = SND_PCM_PLUGIN_RATE_MIN;
411	*rate_max = SND_PCM_PLUGIN_RATE_MAX;
412	return 0;
413}
414
415static void linear_dump(ATTRIBUTE_UNUSED void *rate, snd_output_t *out)
416{
417	snd_output_printf(out, "Converter: linear-interpolation\n");
418}
419
420static const snd_pcm_rate_ops_t linear_ops = {
421	.close = linear_close,
422	.init = linear_init,
423	.free = linear_free,
424	.reset = linear_reset,
425	.adjust_pitch = linear_adjust_pitch,
426	.convert = linear_convert,
427	.input_frames = input_frames,
428	.output_frames = output_frames,
429	.version = SND_PCM_RATE_PLUGIN_VERSION,
430	.get_supported_rates = get_supported_rates,
431	.dump = linear_dump,
432};
433
434int SND_PCM_RATE_PLUGIN_ENTRY(linear) (ATTRIBUTE_UNUSED unsigned int version,
435				       void **objp, snd_pcm_rate_ops_t *ops)
436{
437	struct rate_linear *rate;
438
439	rate = calloc(1, sizeof(*rate));
440	if (! rate)
441		return -ENOMEM;
442
443	*objp = rate;
444	*ops = linear_ops;
445	return 0;
446}
447