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 <stdlib.h>
23#include <stdio.h>
24#include <signal.h>
25
26#include <check.h>
27
28#include <pulsecore/memblockq.h>
29#include <pulsecore/log.h>
30#include <pulsecore/macro.h>
31#include <pulsecore/strbuf.h>
32#include <pulsecore/core-util.h>
33
34#include <pulse/xmalloc.h>
35
36static const char *fixed[] = {
37    "1122444411441144__22__11______3333______________________________",
38    "__________________3333__________________________________________"
39};
40static const char *manual[] = {
41    "1122444411441144__22__11______3333______________________________",
42    "__________________3333______________________________"
43};
44
45/*
46 * utility function to create a memchunk
47 */
48static pa_memchunk memchunk_from_str(pa_mempool *p, const char* data)
49{
50    pa_memchunk res;
51    size_t size = strlen(data);
52
53    res.memblock = pa_memblock_new_fixed(p, (void*)data, size, true);
54    ck_assert_ptr_ne(res.memblock, NULL);
55
56    res.index = 0;
57    res.length = pa_memblock_get_length(res.memblock);
58
59    return res;
60}
61
62static void dump_chunk(const pa_memchunk *chunk, pa_strbuf *buf) {
63    size_t n;
64    void *q;
65    char *e;
66
67    fail_unless(chunk != NULL);
68
69    q = pa_memblock_acquire(chunk->memblock);
70    for (e = (char*) q + chunk->index, n = 0; n < chunk->length; n++, e++) {
71        fprintf(stderr, "%c", *e);
72        pa_strbuf_putc(buf, *e);
73    }
74    pa_memblock_release(chunk->memblock);
75}
76
77static void dump(pa_memblockq *bq, int n) {
78    pa_memchunk out;
79    pa_strbuf *buf;
80    char *str;
81
82    pa_assert(bq);
83
84    /* First let's dump this as fixed block */
85    fprintf(stderr, "FIXED >");
86    pa_memblockq_peek_fixed_size(bq, 64, &out);
87    buf = pa_strbuf_new();
88    dump_chunk(&out, buf);
89    pa_memblock_unref(out.memblock);
90    str = pa_strbuf_to_string_free(buf);
91    fail_unless(pa_streq(str, fixed[n]));
92    pa_xfree(str);
93    fprintf(stderr, "<\n");
94
95    /* Then let's dump the queue manually */
96    fprintf(stderr, "MANUAL>");
97
98    buf = pa_strbuf_new();
99    for (;;) {
100        if (pa_memblockq_peek(bq, &out) < 0)
101            break;
102
103        dump_chunk(&out, buf);
104        pa_memblock_unref(out.memblock);
105        pa_memblockq_drop(bq, out.length);
106    }
107    str = pa_strbuf_to_string_free(buf);
108    fail_unless(pa_streq(str, manual[n]));
109    pa_xfree(str);
110    fprintf(stderr, "<\n");
111}
112
113/*
114 * utility function to validate invariants
115 *
116 * The different values like base, maxlength etc follow certain rules.
117 * This convenience function makes sure that changes don't violate
118 * these rules.
119 */
120static void check_queue_invariants(pa_memblockq *bq) {
121    size_t base = pa_memblockq_get_base(bq);
122    size_t maxlength = pa_memblockq_get_maxlength(bq);
123    size_t tlength = pa_memblockq_get_tlength(bq);
124    size_t minreq = pa_memblockq_get_minreq(bq);
125    size_t prebuf = pa_memblockq_get_prebuf(bq);
126    size_t length = pa_memblockq_get_length(bq);
127
128    /* base > zero */
129    ck_assert_int_gt(base, 0);
130
131    /* maxlength multiple of base
132     * maxlength >= base */
133    ck_assert_int_eq(maxlength % base, 0);
134    ck_assert_int_ge(maxlength, base);
135
136    /* tlength multiple of base
137     * tlength >= base
138     * tlength <= maxlength */
139    ck_assert_int_eq(tlength % base, 0);
140    ck_assert_int_ge(tlength, base);
141    ck_assert_int_le(tlength, maxlength);
142
143    /* minreq multiple of base
144     * minreq >= base
145     * minreq <= tlength */
146    ck_assert_int_eq(minreq % base, 0);
147    ck_assert_int_ge(minreq, base);
148    ck_assert_int_le(minreq, tlength);
149
150    /* prebuf multiple of base
151     * prebuf >= 0
152     * prebuf <= tlength + base - minreq
153     * prebuf <= tlength (because minreq >= base) */
154    ck_assert_int_eq(prebuf % base, 0);
155    ck_assert_int_ge(prebuf, 0);
156    ck_assert_int_le(prebuf, tlength + base - minreq);
157    ck_assert_int_le(prebuf, tlength);
158
159    /* length >= 0
160     * length <= maxlength */
161    ck_assert_int_ge(length, 0);
162    ck_assert_int_le(length, maxlength);
163}
164
165START_TEST (memchunk_from_str_test) {
166    pa_mempool *p;
167    pa_memchunk chunk;
168
169    p = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true);
170    ck_assert_ptr_ne(p, NULL);
171
172    /* allocate memchunk and check default settings */
173    chunk = memchunk_from_str(p, "abcd");
174    ck_assert_ptr_ne(chunk.memblock, NULL);
175    ck_assert_int_eq(chunk.index, 0);
176    ck_assert_int_eq(chunk.length, 4);
177
178    /* cleanup */
179    pa_memblock_unref(chunk.memblock);
180    pa_mempool_unref(p);
181}
182END_TEST
183
184START_TEST (memblockq_test_initial_properties) {
185    pa_mempool *p;
186    pa_memblockq *bq;
187    pa_memchunk silence;
188    pa_sample_spec ss = {
189        .format = PA_SAMPLE_S32BE,
190        .rate = 48000,
191        .channels = 1
192    };
193    int64_t idx = 0;
194    size_t maxlength = 100;
195    size_t tlength = 20;
196    size_t prebuf = 16;
197    size_t minreq = 8;
198    size_t maxrewind = 40;
199
200    p = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true);
201    ck_assert_ptr_ne(p, NULL);
202
203    silence = memchunk_from_str(p, "__");
204
205    bq = pa_memblockq_new("test memblockq", idx, maxlength, tlength, &ss, prebuf, minreq, maxrewind, &silence);
206    fail_unless(bq != NULL);
207
208    /* check initial properties */
209    ck_assert_int_eq(pa_memblockq_is_readable(bq), false);
210    ck_assert_int_eq(pa_memblockq_get_length(bq), 0);
211    ck_assert_int_eq(pa_memblockq_get_maxlength(bq), maxlength);
212    ck_assert_int_eq(pa_memblockq_get_tlength(bq), tlength);
213    ck_assert_int_eq(pa_memblockq_get_prebuf(bq), prebuf);
214    ck_assert_int_eq(pa_memblockq_get_minreq(bq), minreq);
215    ck_assert_int_eq(pa_memblockq_get_maxrewind(bq), maxrewind);
216    ck_assert_int_eq(pa_memblockq_get_base(bq), pa_frame_size(&ss));
217    ck_assert_int_eq(pa_memblockq_get_read_index(bq), 0);
218    ck_assert_int_eq(pa_memblockq_get_write_index(bq), 0);
219
220    check_queue_invariants(bq);
221
222    /* Check reporting of missing bytes:
223     * Initially, tlength bytes are missing. The second call doesn't
224     * report additional missing data since the first call. */
225    ck_assert_int_eq(pa_memblockq_pop_missing(bq), tlength);
226    ck_assert_int_eq(pa_memblockq_pop_missing(bq), 0);
227
228    /* cleanup */
229    pa_memblockq_free(bq);
230    pa_memblock_unref(silence.memblock);
231    pa_mempool_unref(p);
232}
233END_TEST
234
235START_TEST (memblockq_test) {
236    int ret;
237
238    pa_mempool *p;
239    pa_memblockq *bq;
240    pa_memchunk chunk1, chunk2, chunk3, chunk4;
241    pa_memchunk silence;
242    pa_sample_spec ss = {
243        .format = PA_SAMPLE_S16LE,
244        .rate = 48000,
245        .channels = 1
246    };
247
248    p = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true);
249    ck_assert_ptr_ne(p, NULL);
250
251    silence = memchunk_from_str(p, "__");
252
253    bq = pa_memblockq_new("test memblockq", 0, 200, 10, &ss, 4, 4, 40, &silence);
254    fail_unless(bq != NULL);
255    check_queue_invariants(bq);
256
257    chunk1 = memchunk_from_str(p, "11");
258    chunk2 = memchunk_from_str(p, "XX22");
259    chunk2.index += 2;
260    chunk2.length -= 2;
261    chunk3 = memchunk_from_str(p, "3333");
262    chunk4 = memchunk_from_str(p, "44444444");
263
264    ret = pa_memblockq_push(bq, &chunk1);
265    fail_unless(ret == 0);
266
267    ret = pa_memblockq_push(bq, &chunk2);
268    fail_unless(ret == 0);
269
270    ret = pa_memblockq_push(bq, &chunk3);
271    fail_unless(ret == 0);
272
273    ret = pa_memblockq_push(bq, &chunk4);
274    fail_unless(ret == 0);
275
276    check_queue_invariants(bq);
277
278    pa_memblockq_seek(bq, -6, 0, true);
279    ret = pa_memblockq_push(bq, &chunk3);
280    fail_unless(ret == 0);
281
282    pa_memblockq_seek(bq, -2, 0, true);
283    ret = pa_memblockq_push(bq, &chunk1);
284    fail_unless(ret == 0);
285
286    pa_memblockq_seek(bq, -10, 0, true);
287    ret = pa_memblockq_push(bq, &chunk4);
288    fail_unless(ret == 0);
289
290    pa_memblockq_seek(bq, 10, 0, true);
291
292    ret = pa_memblockq_push(bq, &chunk1);
293    fail_unless(ret == 0);
294
295    pa_memblockq_seek(bq, -6, 0, true);
296    ret = pa_memblockq_push(bq, &chunk2);
297    fail_unless(ret == 0);
298
299    /* Test splitting */
300    pa_memblockq_seek(bq, -12, 0, true);
301    ret = pa_memblockq_push(bq, &chunk1);
302    fail_unless(ret == 0);
303
304    pa_memblockq_seek(bq, 20, 0, true);
305
306    /* Test merging */
307    ret = pa_memblockq_push(bq, &chunk3);
308    fail_unless(ret == 0);
309    pa_memblockq_seek(bq, -2, 0, true);
310
311    chunk3.index += 2;
312    chunk3.length -= 2;
313    ret = pa_memblockq_push(bq, &chunk3);
314    fail_unless(ret == 0);
315
316    pa_memblockq_seek(bq, 30, PA_SEEK_RELATIVE, true);
317
318    dump(bq, 0);
319
320    pa_memblockq_rewind(bq, 52);
321
322    dump(bq, 1);
323
324    check_queue_invariants(bq);
325
326    pa_memblockq_free(bq);
327    pa_memblock_unref(silence.memblock);
328    pa_memblock_unref(chunk1.memblock);
329    pa_memblock_unref(chunk2.memblock);
330    pa_memblock_unref(chunk3.memblock);
331    pa_memblock_unref(chunk4.memblock);
332
333    pa_mempool_unref(p);
334}
335END_TEST
336
337START_TEST (memblockq_test_length_changes) {
338    pa_mempool *p;
339    pa_memblockq *bq;
340    pa_memchunk silence, data;
341    pa_sample_spec ss = {
342        .format = PA_SAMPLE_S32BE,
343        .rate = 48000,
344        .channels = 1
345    };
346    int64_t idx = 0;
347    size_t maxlength = 60;
348    size_t tlength = 40;
349    size_t prebuf = 16;
350    size_t minreq = 20;
351    size_t maxrewind = 40;
352
353    p = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true);
354    ck_assert_ptr_ne(p, NULL);
355
356    silence = memchunk_from_str(p, "____");
357
358    bq = pa_memblockq_new("test memblockq", idx, maxlength, tlength, &ss, prebuf, minreq, maxrewind, &silence);
359    fail_unless(bq != NULL);
360
361    data = memchunk_from_str(p, "12345678");
362
363    /* insert some data */
364    ck_assert_int_eq(pa_memblockq_push(bq, &data), 0);
365    ck_assert_int_eq(pa_memblockq_push(bq, &data), 0);
366    ck_assert_int_eq(pa_memblockq_push(bq, &data), 0);
367    ck_assert_int_eq(pa_memblockq_push(bq, &data), 0);
368
369    /* check state */
370    ck_assert_int_eq(pa_memblockq_get_length(bq), 32);
371
372    /* adjust maximum length
373     * This might modify tlength, prebuf, minreq, too. */
374    pa_memblockq_set_maxlength(bq, maxlength/2);
375    check_queue_invariants(bq);
376
377    /* adjust target length
378     * This might modify minreq, too. */
379    pa_memblockq_set_tlength(bq, tlength/2);
380    check_queue_invariants(bq);
381
382    /* adjust minimum requested length
383     * This might modify prebuf, too. */
384    pa_memblockq_set_minreq(bq, minreq/2);
385    check_queue_invariants(bq);
386
387    /* adjust prebuffer length */
388    pa_memblockq_set_prebuf(bq, prebuf/2);
389    check_queue_invariants(bq);
390
391    /* cleanup */
392    pa_memblockq_free(bq);
393    pa_memblock_unref(silence.memblock);
394    pa_memblock_unref(data.memblock);
395    pa_mempool_unref(p);
396}
397END_TEST
398
399START_TEST (memblockq_test_pop_missing) {
400    pa_mempool *p;
401    pa_memblockq *bq;
402    pa_memchunk silence, data, chunk;
403    pa_sample_spec ss = {
404        .format = PA_SAMPLE_S16BE,
405        .rate = 48000,
406        .channels = 1
407    };
408    int64_t idx = 0;
409    size_t maxlength = 200;
410    size_t tlength = 100;
411    size_t prebuf = 0;
412    size_t minreq = 80;
413    size_t maxrewind = 0;
414
415    p = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true);
416    ck_assert_ptr_ne(p, NULL);
417
418    silence = memchunk_from_str(p, "____");
419    data = memchunk_from_str(p, "1234567890");
420
421    bq = pa_memblockq_new("test memblockq", idx, maxlength, tlength, &ss, prebuf, minreq, maxrewind, &silence);
422    fail_unless(bq != NULL);
423
424    /* The following equation regarding the internal variables of a memblockq
425     * is always true:
426     *
427     *   length + missing + requested = tlength
428     *
429     * "length" is the current memblockq length (write index minus read index)
430     * and "tlength" is the target length. The intuitive meaning of "missing"
431     * would be the difference between tlength and length, but actually
432     * "missing" and "requested" together constitute the amount that is missing
433     * from the queue. Writing to the queue decrements "requested" and reading
434     * from the queue increments "missing". pa_memblockq_pop_missing() resets
435     * "missing" to zero, returns the old "missing" value and adds the
436     * equivalent amount to "requested".
437     *
438     * This test has comments between each step documenting the assumed state
439     * of those internal variables. */
440
441    /* length + missing + requested = tlength
442     * 0      + 100     + 0         = 100 */
443
444    ck_assert_int_eq(pa_memblockq_pop_missing(bq), tlength);
445
446    /* length + missing + requested = tlength
447     * 0      + 0       + 100       = 100 */
448
449    for (int i = 0; i != 2; ++i)
450        ck_assert_int_eq(pa_memblockq_push(bq, &data), 0);
451    check_queue_invariants(bq);
452
453    /* length + missing + requested = tlength
454     * 20     + 0       + 80        = 100 */
455
456    ck_assert_int_eq(pa_memblockq_pop_missing(bq), 0);
457
458    /* length + missing + requested = tlength
459     * 20     + 0       + 80        = 100 */
460
461    for (int i = 0; i != 8; ++i)
462        ck_assert_int_eq(pa_memblockq_push(bq, &data), 0);
463    check_queue_invariants(bq);
464
465    /* length + missing + requested = tlength
466     * 100    + 0       + 0         = 100 */
467
468    ck_assert_int_eq(pa_memblockq_pop_missing(bq), 0);
469
470    /* length + missing + requested = tlength
471     * 100    + 0       + 0         = 100 */
472
473    ck_assert_int_eq(pa_memblockq_peek_fixed_size(bq, 40, &chunk), 0);
474    pa_memblockq_drop(bq, 40);
475    ck_assert_int_eq(chunk.length - chunk.index, 40);
476    pa_memblock_unref(chunk.memblock);
477    check_queue_invariants(bq);
478
479    /* length + missing + requested = tlength
480     * 60     + 40      + 0         = 100 */
481
482    /* 40 bytes are missing, but that's less than minreq, so 0 is reported */
483    ck_assert_int_eq(pa_memblockq_pop_missing(bq), 0);
484
485    /* length + missing + requested = tlength
486     * 60     + 40      + 0         = 100 */
487
488    /* Now we push 30 bytes even though it was not requested, so the requested
489     * counter goes negative! */
490    for (int i = 0; i != 3; ++i)
491        ck_assert_int_eq(pa_memblockq_push(bq, &data), 0);
492    check_queue_invariants(bq);
493
494    /* length + missing + requested = tlength
495     * 90     + 40      + -30       = 100 */
496
497    /* missing < minreq, so nothing is reported missing. */
498    ck_assert_int_eq(pa_memblockq_pop_missing(bq), 0);
499
500    /* length + missing + requested = tlength
501     * 90     + 40      + -30       = 100 */
502
503    ck_assert_int_eq(pa_memblockq_peek_fixed_size(bq, 20, &chunk), 0);
504    pa_memblockq_drop(bq, 20);
505    ck_assert_int_eq(chunk.length - chunk.index, 20);
506    pa_memblock_unref(chunk.memblock);
507    check_queue_invariants(bq);
508
509    /* length + missing + requested = tlength
510     * 70     + 60      + -30       = 100 */
511
512    /* missing < minreq, so nothing is reported missing. */
513    ck_assert_int_eq(pa_memblockq_pop_missing(bq), 0);
514
515    /* length + missing + requested = tlength
516     * 70     + 60      + -30       = 100 */
517
518    /* We push more data again even though it was not requested, so the
519     * requested counter goes further into the negative range. */
520    for (int i = 0; i != 5; ++i)
521        ck_assert_int_eq(pa_memblockq_push(bq, &data), 0);
522    check_queue_invariants(bq);
523
524    /* length + missing + requested = tlength
525     * 120    + 60      + -80       = 100 */
526
527    /* missing < minreq, so nothing is reported missing. */
528    ck_assert_int_eq(pa_memblockq_pop_missing(bq), 0);
529
530    /* length + missing + requested = tlength
531     * 120    + 60      + -80       = 100 */
532
533    ck_assert_int_eq(pa_memblockq_peek_fixed_size(bq, 20, &chunk), 0);
534    pa_memblockq_drop(bq, 20);
535    ck_assert_int_eq(chunk.length - chunk.index, 20);
536    pa_memblock_unref(chunk.memblock);
537    check_queue_invariants(bq);
538
539    /* length + missing + requested = tlength
540     * 100    + 80      + -80       = 100 */
541
542    /* missing has now reached the minreq threshold */
543    ck_assert_int_eq(pa_memblockq_pop_missing(bq), 80);
544
545    /* length + missing + requested = tlength
546     * 100    + 0       + 0         = 100 */
547
548    /* cleanup */
549    pa_memblockq_free(bq);
550    pa_memblock_unref(silence.memblock);
551    pa_memblock_unref(data.memblock);
552    pa_mempool_unref(p);
553}
554END_TEST
555
556START_TEST (memblockq_test_tlength_change) {
557    int ret;
558    size_t missing;
559
560    pa_mempool *p;
561    pa_memblockq *bq;
562    pa_memchunk chunk;
563    char buffer[2048];
564    pa_sample_spec ss = {
565        .format = PA_SAMPLE_S16LE,
566        .rate = 48000,
567        .channels = 1
568    };
569
570    pa_log_set_level(PA_LOG_DEBUG);
571
572    bq = pa_memblockq_new("test memblockq", 0, 4096, 2048, &ss, 0, 512, 512, NULL);
573    fail_unless(bq != NULL);
574
575    /* Empty buffer, so expect tlength */
576    missing = pa_memblockq_pop_missing(bq);
577    fail_unless(missing == 2048);
578
579    /* Everything requested, so should be satisfied */
580    missing = pa_memblockq_pop_missing(bq);
581    fail_unless(missing == 0);
582
583    p = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true);
584
585    chunk.memblock = pa_memblock_new_fixed(p, buffer, sizeof(buffer), 1);
586    fail_unless(chunk.memblock != NULL);
587
588    chunk.index = 0;
589    chunk.length = sizeof(buffer);
590
591    /* Fill buffer (i.e. satisfy earlier request) */
592    ret = pa_memblockq_push(bq, &chunk);
593    fail_unless(ret == 0);
594
595    /* Should still be happy */
596    missing = pa_memblockq_pop_missing(bq);
597    fail_unless(missing == 0);
598
599    /* Check that we don't request less than minreq */
600    pa_memblockq_drop(bq, 400);
601    missing = pa_memblockq_pop_missing(bq);
602    ck_assert_int_eq(missing, 0);
603
604    missing = pa_memblockq_pop_missing(bq);
605    fail_unless(missing == 0);
606
607    /* Reduce tlength under what's dropped and under previous minreq */
608    pa_memblockq_set_tlength(bq, 256);
609    pa_memblockq_set_minreq(bq, 64);
610
611    /* We are now overbuffered and should not request more */
612    missing = pa_memblockq_pop_missing(bq);
613    fail_unless(missing == 0);
614
615    /* Drop more data so we are below tlength again, but just barely */
616    pa_memblockq_drop(bq, 1400);
617
618    /* Should still honour minreq */
619    missing = pa_memblockq_pop_missing(bq);
620    fail_unless(missing == 0);
621
622    /* Finally drop enough to fall below minreq */
623    pa_memblockq_drop(bq, 80);
624
625    /* And expect a request */
626    missing = pa_memblockq_pop_missing(bq);
627    fail_unless(missing == 88);
628
629    pa_memblockq_free(bq);
630    pa_memblock_unref(chunk.memblock);
631    pa_mempool_unref(p);
632}
633END_TEST
634
635
636int main(int argc, char *argv[]) {
637    int failed = 0;
638    Suite *s;
639    TCase *tc;
640    SRunner *sr;
641
642    if (!getenv("MAKE_CHECK"))
643        pa_log_set_level(PA_LOG_DEBUG);
644
645    s = suite_create("Memblock Queue");
646    tc = tcase_create("memblockq");
647    tcase_add_test(tc, memchunk_from_str_test);
648    tcase_add_test(tc, memblockq_test_initial_properties);
649    tcase_add_test(tc, memblockq_test);
650    tcase_add_test(tc, memblockq_test_length_changes);
651    tcase_add_test(tc, memblockq_test_pop_missing);
652    tcase_add_test(tc, memblockq_test_tlength_change);
653    suite_add_tcase(s, tc);
654
655    sr = srunner_create(s);
656    srunner_run_all(sr, CK_NORMAL);
657    failed = srunner_ntests_failed(sr);
658    srunner_free(sr);
659
660    return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
661}
662