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 
35 struct 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 
input_frames(void *obj, snd_pcm_uframes_t frames)49 static 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 
output_frames(void *obj, snd_pcm_uframes_t frames)58 static 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 
linear_expand(struct rate_linear *rate, const snd_pcm_channel_area_t *dst_areas, snd_pcm_uframes_t dst_offset, unsigned int dst_frames, const snd_pcm_channel_area_t *src_areas, snd_pcm_uframes_t src_offset, unsigned int src_frames)67 static 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 */
linear_expand_s16(struct rate_linear *rate, const snd_pcm_channel_area_t *dst_areas, snd_pcm_uframes_t dst_offset, unsigned int dst_frames, const snd_pcm_channel_area_t *src_areas, snd_pcm_uframes_t src_offset, unsigned int src_frames)138 static 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 
linear_shrink(struct rate_linear *rate, const snd_pcm_channel_area_t *dst_areas, snd_pcm_uframes_t dst_offset, unsigned int dst_frames, const snd_pcm_channel_area_t *src_areas, snd_pcm_uframes_t src_offset, unsigned int src_frames)189 static 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 */
linear_shrink_s16(struct rate_linear *rate, const snd_pcm_channel_area_t *dst_areas, snd_pcm_uframes_t dst_offset, unsigned int dst_frames, const snd_pcm_channel_area_t *src_areas, snd_pcm_uframes_t src_offset, unsigned int src_frames)259 static 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 
linear_convert(void *obj, const snd_pcm_channel_area_t *dst_areas, snd_pcm_uframes_t dst_offset, unsigned int dst_frames, const snd_pcm_channel_area_t *src_areas, snd_pcm_uframes_t src_offset, unsigned int src_frames)310 static 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 
linear_free(void *obj)321 static 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 
linear_init(void *obj, snd_pcm_rate_info_t *info)329 static 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 
linear_adjust_pitch(void *obj, snd_pcm_rate_info_t *info)360 static 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 
linear_reset(void *obj)393 static 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 
linear_close(void *obj)402 static void linear_close(void *obj)
403 {
404 	free(obj);
405 }
406 
get_supported_rates(ATTRIBUTE_UNUSED void *rate, unsigned int *rate_min, unsigned int *rate_max)407 static 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 
linear_dump(ATTRIBUTE_UNUSED void *rate, snd_output_t *out)415 static void linear_dump(ATTRIBUTE_UNUSED void *rate, snd_output_t *out)
416 {
417 	snd_output_printf(out, "Converter: linear-interpolation\n");
418 }
419 
420 static 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 
linear(ATTRIBUTE_UNUSED unsigned int version, void **objp, snd_pcm_rate_ops_t *ops)434 int 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