1/***
2  This file is part of PulseAudio.
3
4  PulseAudio is free software; you can redistribute it and/or modify
5  it under the terms of the GNU Lesser General Public License as published
6  by the Free Software Foundation; either version 2.1 of the License,
7  or (at your option) any later version.
8
9  PulseAudio is distributed in the hope that it will be useful, but
10  WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  General Public License for more details.
13
14  You should have received a copy of the GNU Lesser General Public License
15  along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
16***/
17
18#ifdef HAVE_CONFIG_H
19#include <config.h>
20#endif
21
22#include <stdio.h>
23#include <getopt.h>
24#include <locale.h>
25#include <math.h>
26
27#include <pulse/pulseaudio.h>
28
29#include <pulse/rtclock.h>
30#include <pulse/sample.h>
31#include <pulse/volume.h>
32
33#include <pulsecore/i18n.h>
34#include <pulsecore/log.h>
35#include <pulsecore/resampler.h>
36#include <pulsecore/macro.h>
37#include <pulsecore/endianmacros.h>
38#include <pulsecore/memblock.h>
39#include <pulsecore/memblockq.h>
40#include <pulsecore/sample-util.h>
41#include <pulsecore/core-util.h>
42
43#define MEMBLOCKQ_MAXLENGTH (16*1024*1024)
44#define PA_SILENCE_MAX (pa_page_size()*16)
45#define MAX_MATCHING_PERIOD 500
46
47static pa_memblock *silence_memblock_new(pa_mempool *pool, uint8_t c) {
48    pa_memblock *b;
49    size_t length;
50    void *data;
51
52    pa_assert(pool);
53
54    length = PA_MIN(pa_mempool_block_size_max(pool), PA_SILENCE_MAX);
55
56    b = pa_memblock_new(pool, length);
57
58    data = pa_memblock_acquire(b);
59    memset(data, c, length);
60    pa_memblock_release(b);
61
62    pa_memblock_set_is_silence(b, true);
63
64    return b;
65}
66
67/* Calculate number of history bytes needed for the rewind */
68static size_t calculate_resampler_history_bytes(pa_resampler *r, size_t in_rewind_frames) {
69    size_t history_frames, history_max, matching_period, total_frames, remainder;
70    double delay;
71
72    if (!r)
73        return 0;
74
75    /* Initialize some variables, cut off full seconds from the rewind */
76    total_frames = 0;
77    in_rewind_frames = in_rewind_frames % r->i_ss.rate;
78    history_max = (uint64_t) PA_RESAMPLER_MAX_DELAY_USEC * r->i_ss.rate * 3 / PA_USEC_PER_SEC / 2;
79
80    /* Get the current internal delay of the resampler */
81    delay = pa_resampler_get_delay(r, false);
82
83    /* Calculate the matchiung period */
84    matching_period = r->i_ss.rate / pa_resampler_get_gcd(r);
85    pa_log_debug("Integral period length is %lu input frames", matching_period);
86
87    /* If the delay is larger than the length of the history queue, we can only
88     * replay as much as we have. */
89    if ((size_t)delay >= history_max) {
90        history_frames = history_max;
91        return history_frames * r->i_fz;
92    }
93
94    /* Initially set the history to 3 times the resampler delay. Use at least 2 ms. */
95    history_frames = (size_t)(delay * 3.0);
96    history_frames = PA_MAX(history_frames, r->i_ss.rate / 500);
97
98    /* Check how the rewind fits into multiples of the matching period. */
99    remainder = (in_rewind_frames + history_frames) % matching_period;
100
101    /* If possible, use between 2 and 3 times the resampler delay */
102    if (remainder < (size_t)delay && history_frames - remainder <= history_max)
103        total_frames = in_rewind_frames + history_frames - remainder;
104
105    /* Else, try above 3 times the delay */
106    else if (history_frames + matching_period - remainder <= history_max)
107        total_frames = in_rewind_frames + history_frames + matching_period - remainder;
108
109    if (total_frames != 0)
110        /* We found a perfect match. */
111        history_frames = total_frames - in_rewind_frames;
112    else {
113        /* Try to use 2.5 times the delay. */
114        history_frames = PA_MIN((size_t)(delay * 2.5), history_max);
115        pa_log_debug("No usable integral matching period");
116    }
117
118    return history_frames * r->i_fz;
119}
120
121static float compare_blocks(const pa_sample_spec *ss, const pa_memchunk *chunk_a, const pa_memchunk *chunk_b) {
122    float *a, *b, max_diff = 0;
123    unsigned i;
124
125    a = pa_memblock_acquire(chunk_a->memblock);
126    b = pa_memblock_acquire(chunk_b->memblock);
127    a += chunk_a->index / pa_frame_size(ss);
128    b += chunk_b->index / pa_frame_size(ss);
129
130    for (i = 0; i < chunk_a->length / pa_frame_size(ss); i++) {
131        if (fabs(a[i] - b[i]) > max_diff)
132            max_diff = fabs(a[i] - b[i]);
133    }
134
135    pa_memblock_release(chunk_a->memblock);
136    pa_memblock_release(chunk_b->memblock);
137
138    return max_diff;
139}
140
141static pa_memblock* generate_block(pa_mempool *pool, const pa_sample_spec *ss, unsigned frequency, double amplitude, size_t nr_of_samples) {
142    pa_memblock *r;
143    float *d;
144    float val;
145    unsigned i;
146    int n;
147    float t, dt, dt_period;
148
149    pa_assert(frequency);
150    pa_assert(nr_of_samples);
151    pa_assert(ss->channels == 1);
152    pa_assert(ss->format == PA_SAMPLE_FLOAT32NE);
153
154    pa_assert_se(r = pa_memblock_new(pool, pa_frame_size(ss) * nr_of_samples));
155    d = pa_memblock_acquire(r);
156
157    /* Generate square wave with given length, frequency and sample rate. */
158    val = amplitude;
159    t = 0;
160    n = 1;
161    dt = 1.0 / ss->rate;
162    dt_period = 1.0 / frequency;
163    for (i=0; i < nr_of_samples; i++) {
164        d[i] = val;
165
166        if ((int)(2 * t / dt_period) >= n) {
167            n++;
168            if (val >= amplitude)
169                val = - amplitude;
170            else
171                val = amplitude;
172        }
173
174        t += dt;
175    }
176
177    pa_memblock_release(r);
178
179    return r;
180}
181
182static void help(const char *argv0) {
183    printf("%s [options]\n\n"
184           "-h, --help                            Show this help\n"
185           "-v, --verbose                         Print debug messages\n"
186           "      --from-rate=SAMPLERATE          From sample rate in Hz (defaults to 44100)\n"
187           "      --to-rate=SAMPLERATE            To sample rate in Hz (defaults to 44100)\n"
188           "      --resample-method=METHOD        Resample method (defaults to auto)\n"
189           "      --frequency=unsigned            Frequency of square wave\n"
190           "      --samples=unsigned              Number of samples for square wave\n"
191           "      --rewind=unsigned               Number of output samples to rewind\n"
192           "\n"
193           "This test generates samples for a square wave of given frequency, number of samples\n"
194           "and input sample rate. Then this input data is resampled to the output rate, rewound\n"
195           "by rewind samples and the rewound part is processed again. Then output is compared to\n"
196           "the result of the first pass.\n"
197           "\n"
198           "See --dump-resample-methods for possible values of resample methods.\n",
199           argv0);
200}
201
202enum {
203    ARG_VERSION = 256,
204    ARG_FROM_SAMPLERATE,
205    ARG_TO_SAMPLERATE,
206    ARG_FREQUENCY,
207    ARG_SAMPLES,
208    ARG_REWIND,
209    ARG_RESAMPLE_METHOD,
210    ARG_DUMP_RESAMPLE_METHODS
211};
212
213static void dump_resample_methods(void) {
214    int i;
215
216    for (i = 0; i < PA_RESAMPLER_MAX; i++)
217        if (pa_resample_method_supported(i))
218            printf("%s\n", pa_resample_method_to_string(i));
219
220}
221
222int main(int argc, char *argv[]) {
223    pa_mempool *pool = NULL;
224    pa_sample_spec a, b;
225    pa_resample_method_t method;
226    int ret = 1, c;
227    unsigned samples, frequency, rewind;
228    unsigned crossover_freq = 120;
229    pa_resampler *resampler;
230    pa_memchunk in_chunk, out_chunk, rewound_chunk, silence_chunk;
231    pa_usec_t ts;
232    pa_memblockq *history_queue = NULL;
233    size_t in_rewind_size, in_frame_size, history_size, out_rewind_size, old_length, in_resampler_buffer, n_out_expected;
234    float max_diff;
235    double delay_before, delay_after, delay_expected;
236
237    static const struct option long_options[] = {
238        {"help",                  0, NULL, 'h'},
239        {"verbose",               0, NULL, 'v'},
240        {"version",               0, NULL, ARG_VERSION},
241        {"from-rate",             1, NULL, ARG_FROM_SAMPLERATE},
242        {"to-rate",               1, NULL, ARG_TO_SAMPLERATE},
243        {"frequency",             1, NULL, ARG_FREQUENCY},
244        {"samples",               1, NULL, ARG_SAMPLES},
245        {"rewind",                1, NULL, ARG_REWIND},
246        {"resample-method",       1, NULL, ARG_RESAMPLE_METHOD},
247        {"dump-resample-methods", 0, NULL, ARG_DUMP_RESAMPLE_METHODS},
248        {NULL,                    0, NULL, 0}
249    };
250
251    setlocale(LC_ALL, "");
252#ifdef ENABLE_NLS
253    bindtextdomain(GETTEXT_PACKAGE, PULSE_LOCALEDIR);
254#endif
255
256    pa_log_set_level(PA_LOG_WARN);
257    if (!getenv("MAKE_CHECK"))
258        pa_log_set_level(PA_LOG_INFO);
259
260    a.channels = b.channels = 1;
261    a.rate = 48000;
262    b.rate = 44100;
263    a.format = b.format = PA_SAMPLE_FLOAT32NE;
264
265    method = PA_RESAMPLER_AUTO;
266    frequency = 1000;
267    samples = 5000;
268    rewind = 2500;
269
270    while ((c = getopt_long(argc, argv, "hv", long_options, NULL)) != -1) {
271
272        switch (c) {
273            case 'h' :
274                help(argv[0]);
275                ret = 0;
276                goto quit;
277
278            case 'v':
279                pa_log_set_level(PA_LOG_DEBUG);
280                break;
281
282            case ARG_VERSION:
283                printf("%s %s\n", argv[0], PACKAGE_VERSION);
284                ret = 0;
285                goto quit;
286
287            case ARG_DUMP_RESAMPLE_METHODS:
288                dump_resample_methods();
289                ret = 0;
290                goto quit;
291
292            case ARG_FROM_SAMPLERATE:
293                a.rate = (uint32_t) atoi(optarg);
294                break;
295
296            case ARG_TO_SAMPLERATE:
297                b.rate = (uint32_t) atoi(optarg);
298                break;
299
300            case ARG_FREQUENCY:
301                frequency = (unsigned) atoi(optarg);
302                break;
303
304            case ARG_SAMPLES:
305                samples = (unsigned) atoi(optarg);
306                break;
307
308            case ARG_REWIND:
309                rewind = (unsigned) atoi(optarg);
310                break;
311
312            case ARG_RESAMPLE_METHOD:
313                if (*optarg == '\0' || pa_streq(optarg, "help")) {
314                    dump_resample_methods();
315                    ret = 0;
316                    goto quit;
317                }
318                method = pa_parse_resample_method(optarg);
319                break;
320
321            default:
322                goto quit;
323        }
324    }
325
326    pa_log_info("=== Square wave %u Hz, %u samples. Resampling using %s from %u Hz to %u Hz, rewinding %u output samples.", frequency,
327                   samples, pa_resample_method_to_string(method), a.rate, b.rate, rewind);
328
329    ret = 0;
330    pa_assert_se(pool = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true));
331
332    pa_log_debug("Compilation CFLAGS: %s", PA_CFLAGS);
333
334    /* Setup resampler */
335    ts = pa_rtclock_now();
336    pa_assert_se(resampler = pa_resampler_new(pool, &a, NULL, &b, NULL, crossover_freq, method, 0));
337    pa_log_info("Init took %llu usec", (long long unsigned)(pa_rtclock_now() - ts));
338
339    /* Generate input data */
340    in_chunk.memblock = generate_block(pool, &a, frequency, 0.5, samples);
341    in_chunk.length = pa_memblock_get_length(in_chunk.memblock);
342    in_chunk.index = 0;
343    in_frame_size = pa_frame_size(&a);
344
345    /* First, resample the full block */
346    ts = pa_rtclock_now();
347    pa_resampler_run(resampler, &in_chunk, &out_chunk);
348    if (!out_chunk.memblock) {
349        pa_memblock_unref(in_chunk.memblock);
350        pa_log_warn("Resampling did not return any output data");
351        ret = 1;
352        goto quit;
353    }
354
355    pa_log_info("resampling took %llu usec.", (long long unsigned)(pa_rtclock_now() - ts));
356    if (rewind > out_chunk.length / pa_frame_size(&b)) {
357        pa_log_warn("Specified number of frames to rewind (%u) larger than number of output frames (%lu), aborting.", rewind, out_chunk.length / pa_frame_size(&b));
358        ret = 1;
359        goto quit1;
360    }
361
362    /* Get delay after first resampling pass */
363    delay_before = pa_resampler_get_delay(resampler, true);
364
365    /* Create and prepare history queue */
366    silence_chunk.memblock = silence_memblock_new(pool, 0);
367    silence_chunk.length = pa_frame_align(pa_memblock_get_length(silence_chunk.memblock), &a);
368    silence_chunk.index = 0;
369    history_queue = pa_memblockq_new("Test-Queue", 0, MEMBLOCKQ_MAXLENGTH, 0, &a, 0, 1, samples * in_frame_size, &silence_chunk);
370    pa_memblock_unref(silence_chunk.memblock);
371
372    pa_memblockq_push(history_queue, &in_chunk);
373    pa_memblockq_drop(history_queue, samples * in_frame_size);
374
375    in_rewind_size = pa_resampler_request(resampler, rewind * pa_frame_size(&b));
376    out_rewind_size = rewind * pa_frame_size(&b);
377    pa_log_debug("Have to rewind %lu input frames", in_rewind_size / in_frame_size);
378    ts = pa_rtclock_now();
379
380    /* Now rewind the resampler */
381    pa_memblockq_rewind(history_queue, in_rewind_size);
382    history_size = calculate_resampler_history_bytes(resampler, in_rewind_size / in_frame_size);
383    pa_log_debug("History is %lu frames.", history_size / in_frame_size);
384    pa_resampler_rewind(resampler, out_rewind_size, history_queue, history_size);
385
386    pa_log_info("Rewind took %llu usec.", (long long unsigned)(pa_rtclock_now() - ts));
387    ts = pa_rtclock_now();
388
389    /* Re-run the resampler */
390    old_length = in_chunk.length;
391    in_chunk.length = in_rewind_size;
392    in_chunk.index = old_length - in_chunk.length;
393    pa_resampler_run(resampler, &in_chunk, &rewound_chunk);
394    if (!rewound_chunk.memblock) {
395        pa_log_warn("Resampler did not return output data for rewind");
396        ret = 1;
397        goto quit1;
398    }
399
400    /* Get delay after rewind */
401    delay_after = pa_resampler_get_delay(resampler, true);
402
403    /* Calculate expected delay */
404    n_out_expected = pa_resampler_result(resampler, in_rewind_size + history_size) / pa_frame_size(&b);
405    delay_expected = delay_before + (double)(in_rewind_size + history_size) / (double)in_frame_size - n_out_expected * (double)a.rate / (double)b.rate;
406
407    /* Check for leftover samples in the resampler buffer */
408    in_resampler_buffer = lround((delay_after - delay_expected) * (double)b.rate / (double)a.rate);
409    if (in_resampler_buffer != 0) {
410        pa_log_debug("%li output frames still in resampler buffer", in_resampler_buffer);
411    }
412
413    pa_log_info("Second resampler run took %llu usec.", (long long unsigned)(pa_rtclock_now() - ts));
414    pa_log_debug("Got %lu output frames", rewound_chunk.length / pa_frame_size(&b));
415    old_length = out_chunk.length;
416    out_chunk.length = rewound_chunk.length;
417    out_chunk.index = old_length - out_chunk.length;
418
419    max_diff = compare_blocks(&b, &out_chunk, &rewound_chunk);
420    pa_log_info("Maximum difference is %.*g", 6, max_diff);
421
422    pa_memblock_unref(rewound_chunk.memblock);
423
424quit1:
425    pa_memblock_unref(in_chunk.memblock);
426    pa_memblock_unref(out_chunk.memblock);
427
428    pa_resampler_free(resampler);
429    if (history_queue)
430        pa_memblockq_free(history_queue);
431
432quit:
433    if (pool)
434        pa_mempool_unref(pool);
435
436    return ret;
437}
438