/*** This file is part of PulseAudio. PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, see . ***/ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include static const char *fixed[] = { "1122444411441144__22__11______3333______________________________", "__________________3333__________________________________________" }; static const char *manual[] = { "1122444411441144__22__11______3333______________________________", "__________________3333______________________________" }; /* * utility function to create a memchunk */ static pa_memchunk memchunk_from_str(pa_mempool *p, const char* data) { pa_memchunk res; size_t size = strlen(data); res.memblock = pa_memblock_new_fixed(p, (void*)data, size, true); ck_assert_ptr_ne(res.memblock, NULL); res.index = 0; res.length = pa_memblock_get_length(res.memblock); return res; } static void dump_chunk(const pa_memchunk *chunk, pa_strbuf *buf) { size_t n; void *q; char *e; fail_unless(chunk != NULL); q = pa_memblock_acquire(chunk->memblock); for (e = (char*) q + chunk->index, n = 0; n < chunk->length; n++, e++) { fprintf(stderr, "%c", *e); pa_strbuf_putc(buf, *e); } pa_memblock_release(chunk->memblock); } static void dump(pa_memblockq *bq, int n) { pa_memchunk out; pa_strbuf *buf; char *str; pa_assert(bq); /* First let's dump this as fixed block */ fprintf(stderr, "FIXED >"); pa_memblockq_peek_fixed_size(bq, 64, &out); buf = pa_strbuf_new(); dump_chunk(&out, buf); pa_memblock_unref(out.memblock); str = pa_strbuf_to_string_free(buf); fail_unless(pa_streq(str, fixed[n])); pa_xfree(str); fprintf(stderr, "<\n"); /* Then let's dump the queue manually */ fprintf(stderr, "MANUAL>"); buf = pa_strbuf_new(); for (;;) { if (pa_memblockq_peek(bq, &out) < 0) break; dump_chunk(&out, buf); pa_memblock_unref(out.memblock); pa_memblockq_drop(bq, out.length); } str = pa_strbuf_to_string_free(buf); fail_unless(pa_streq(str, manual[n])); pa_xfree(str); fprintf(stderr, "<\n"); } /* * utility function to validate invariants * * The different values like base, maxlength etc follow certain rules. * This convenience function makes sure that changes don't violate * these rules. */ static void check_queue_invariants(pa_memblockq *bq) { size_t base = pa_memblockq_get_base(bq); size_t maxlength = pa_memblockq_get_maxlength(bq); size_t tlength = pa_memblockq_get_tlength(bq); size_t minreq = pa_memblockq_get_minreq(bq); size_t prebuf = pa_memblockq_get_prebuf(bq); size_t length = pa_memblockq_get_length(bq); /* base > zero */ ck_assert_int_gt(base, 0); /* maxlength multiple of base * maxlength >= base */ ck_assert_int_eq(maxlength % base, 0); ck_assert_int_ge(maxlength, base); /* tlength multiple of base * tlength >= base * tlength <= maxlength */ ck_assert_int_eq(tlength % base, 0); ck_assert_int_ge(tlength, base); ck_assert_int_le(tlength, maxlength); /* minreq multiple of base * minreq >= base * minreq <= tlength */ ck_assert_int_eq(minreq % base, 0); ck_assert_int_ge(minreq, base); ck_assert_int_le(minreq, tlength); /* prebuf multiple of base * prebuf >= 0 * prebuf <= tlength + base - minreq * prebuf <= tlength (because minreq >= base) */ ck_assert_int_eq(prebuf % base, 0); ck_assert_int_ge(prebuf, 0); ck_assert_int_le(prebuf, tlength + base - minreq); ck_assert_int_le(prebuf, tlength); /* length >= 0 * length <= maxlength */ ck_assert_int_ge(length, 0); ck_assert_int_le(length, maxlength); } START_TEST (memchunk_from_str_test) { pa_mempool *p; pa_memchunk chunk; p = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true); ck_assert_ptr_ne(p, NULL); /* allocate memchunk and check default settings */ chunk = memchunk_from_str(p, "abcd"); ck_assert_ptr_ne(chunk.memblock, NULL); ck_assert_int_eq(chunk.index, 0); ck_assert_int_eq(chunk.length, 4); /* cleanup */ pa_memblock_unref(chunk.memblock); pa_mempool_unref(p); } END_TEST START_TEST (memblockq_test_initial_properties) { pa_mempool *p; pa_memblockq *bq; pa_memchunk silence; pa_sample_spec ss = { .format = PA_SAMPLE_S32BE, .rate = 48000, .channels = 1 }; int64_t idx = 0; size_t maxlength = 100; size_t tlength = 20; size_t prebuf = 16; size_t minreq = 8; size_t maxrewind = 40; p = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true); ck_assert_ptr_ne(p, NULL); silence = memchunk_from_str(p, "__"); bq = pa_memblockq_new("test memblockq", idx, maxlength, tlength, &ss, prebuf, minreq, maxrewind, &silence); fail_unless(bq != NULL); /* check initial properties */ ck_assert_int_eq(pa_memblockq_is_readable(bq), false); ck_assert_int_eq(pa_memblockq_get_length(bq), 0); ck_assert_int_eq(pa_memblockq_get_maxlength(bq), maxlength); ck_assert_int_eq(pa_memblockq_get_tlength(bq), tlength); ck_assert_int_eq(pa_memblockq_get_prebuf(bq), prebuf); ck_assert_int_eq(pa_memblockq_get_minreq(bq), minreq); ck_assert_int_eq(pa_memblockq_get_maxrewind(bq), maxrewind); ck_assert_int_eq(pa_memblockq_get_base(bq), pa_frame_size(&ss)); ck_assert_int_eq(pa_memblockq_get_read_index(bq), 0); ck_assert_int_eq(pa_memblockq_get_write_index(bq), 0); check_queue_invariants(bq); /* Check reporting of missing bytes: * Initially, tlength bytes are missing. The second call doesn't * report additional missing data since the first call. */ ck_assert_int_eq(pa_memblockq_pop_missing(bq), tlength); ck_assert_int_eq(pa_memblockq_pop_missing(bq), 0); /* cleanup */ pa_memblockq_free(bq); pa_memblock_unref(silence.memblock); pa_mempool_unref(p); } END_TEST START_TEST (memblockq_test) { int ret; pa_mempool *p; pa_memblockq *bq; pa_memchunk chunk1, chunk2, chunk3, chunk4; pa_memchunk silence; pa_sample_spec ss = { .format = PA_SAMPLE_S16LE, .rate = 48000, .channels = 1 }; p = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true); ck_assert_ptr_ne(p, NULL); silence = memchunk_from_str(p, "__"); bq = pa_memblockq_new("test memblockq", 0, 200, 10, &ss, 4, 4, 40, &silence); fail_unless(bq != NULL); check_queue_invariants(bq); chunk1 = memchunk_from_str(p, "11"); chunk2 = memchunk_from_str(p, "XX22"); chunk2.index += 2; chunk2.length -= 2; chunk3 = memchunk_from_str(p, "3333"); chunk4 = memchunk_from_str(p, "44444444"); ret = pa_memblockq_push(bq, &chunk1); fail_unless(ret == 0); ret = pa_memblockq_push(bq, &chunk2); fail_unless(ret == 0); ret = pa_memblockq_push(bq, &chunk3); fail_unless(ret == 0); ret = pa_memblockq_push(bq, &chunk4); fail_unless(ret == 0); check_queue_invariants(bq); pa_memblockq_seek(bq, -6, 0, true); ret = pa_memblockq_push(bq, &chunk3); fail_unless(ret == 0); pa_memblockq_seek(bq, -2, 0, true); ret = pa_memblockq_push(bq, &chunk1); fail_unless(ret == 0); pa_memblockq_seek(bq, -10, 0, true); ret = pa_memblockq_push(bq, &chunk4); fail_unless(ret == 0); pa_memblockq_seek(bq, 10, 0, true); ret = pa_memblockq_push(bq, &chunk1); fail_unless(ret == 0); pa_memblockq_seek(bq, -6, 0, true); ret = pa_memblockq_push(bq, &chunk2); fail_unless(ret == 0); /* Test splitting */ pa_memblockq_seek(bq, -12, 0, true); ret = pa_memblockq_push(bq, &chunk1); fail_unless(ret == 0); pa_memblockq_seek(bq, 20, 0, true); /* Test merging */ ret = pa_memblockq_push(bq, &chunk3); fail_unless(ret == 0); pa_memblockq_seek(bq, -2, 0, true); chunk3.index += 2; chunk3.length -= 2; ret = pa_memblockq_push(bq, &chunk3); fail_unless(ret == 0); pa_memblockq_seek(bq, 30, PA_SEEK_RELATIVE, true); dump(bq, 0); pa_memblockq_rewind(bq, 52); dump(bq, 1); check_queue_invariants(bq); pa_memblockq_free(bq); pa_memblock_unref(silence.memblock); pa_memblock_unref(chunk1.memblock); pa_memblock_unref(chunk2.memblock); pa_memblock_unref(chunk3.memblock); pa_memblock_unref(chunk4.memblock); pa_mempool_unref(p); } END_TEST START_TEST (memblockq_test_length_changes) { pa_mempool *p; pa_memblockq *bq; pa_memchunk silence, data; pa_sample_spec ss = { .format = PA_SAMPLE_S32BE, .rate = 48000, .channels = 1 }; int64_t idx = 0; size_t maxlength = 60; size_t tlength = 40; size_t prebuf = 16; size_t minreq = 20; size_t maxrewind = 40; p = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true); ck_assert_ptr_ne(p, NULL); silence = memchunk_from_str(p, "____"); bq = pa_memblockq_new("test memblockq", idx, maxlength, tlength, &ss, prebuf, minreq, maxrewind, &silence); fail_unless(bq != NULL); data = memchunk_from_str(p, "12345678"); /* insert some data */ ck_assert_int_eq(pa_memblockq_push(bq, &data), 0); ck_assert_int_eq(pa_memblockq_push(bq, &data), 0); ck_assert_int_eq(pa_memblockq_push(bq, &data), 0); ck_assert_int_eq(pa_memblockq_push(bq, &data), 0); /* check state */ ck_assert_int_eq(pa_memblockq_get_length(bq), 32); /* adjust maximum length * This might modify tlength, prebuf, minreq, too. */ pa_memblockq_set_maxlength(bq, maxlength/2); check_queue_invariants(bq); /* adjust target length * This might modify minreq, too. */ pa_memblockq_set_tlength(bq, tlength/2); check_queue_invariants(bq); /* adjust minimum requested length * This might modify prebuf, too. */ pa_memblockq_set_minreq(bq, minreq/2); check_queue_invariants(bq); /* adjust prebuffer length */ pa_memblockq_set_prebuf(bq, prebuf/2); check_queue_invariants(bq); /* cleanup */ pa_memblockq_free(bq); pa_memblock_unref(silence.memblock); pa_memblock_unref(data.memblock); pa_mempool_unref(p); } END_TEST START_TEST (memblockq_test_pop_missing) { pa_mempool *p; pa_memblockq *bq; pa_memchunk silence, data, chunk; pa_sample_spec ss = { .format = PA_SAMPLE_S16BE, .rate = 48000, .channels = 1 }; int64_t idx = 0; size_t maxlength = 200; size_t tlength = 100; size_t prebuf = 0; size_t minreq = 80; size_t maxrewind = 0; p = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true); ck_assert_ptr_ne(p, NULL); silence = memchunk_from_str(p, "____"); data = memchunk_from_str(p, "1234567890"); bq = pa_memblockq_new("test memblockq", idx, maxlength, tlength, &ss, prebuf, minreq, maxrewind, &silence); fail_unless(bq != NULL); /* The following equation regarding the internal variables of a memblockq * is always true: * * length + missing + requested = tlength * * "length" is the current memblockq length (write index minus read index) * and "tlength" is the target length. The intuitive meaning of "missing" * would be the difference between tlength and length, but actually * "missing" and "requested" together constitute the amount that is missing * from the queue. Writing to the queue decrements "requested" and reading * from the queue increments "missing". pa_memblockq_pop_missing() resets * "missing" to zero, returns the old "missing" value and adds the * equivalent amount to "requested". * * This test has comments between each step documenting the assumed state * of those internal variables. */ /* length + missing + requested = tlength * 0 + 100 + 0 = 100 */ ck_assert_int_eq(pa_memblockq_pop_missing(bq), tlength); /* length + missing + requested = tlength * 0 + 0 + 100 = 100 */ for (int i = 0; i != 2; ++i) ck_assert_int_eq(pa_memblockq_push(bq, &data), 0); check_queue_invariants(bq); /* length + missing + requested = tlength * 20 + 0 + 80 = 100 */ ck_assert_int_eq(pa_memblockq_pop_missing(bq), 0); /* length + missing + requested = tlength * 20 + 0 + 80 = 100 */ for (int i = 0; i != 8; ++i) ck_assert_int_eq(pa_memblockq_push(bq, &data), 0); check_queue_invariants(bq); /* length + missing + requested = tlength * 100 + 0 + 0 = 100 */ ck_assert_int_eq(pa_memblockq_pop_missing(bq), 0); /* length + missing + requested = tlength * 100 + 0 + 0 = 100 */ ck_assert_int_eq(pa_memblockq_peek_fixed_size(bq, 40, &chunk), 0); pa_memblockq_drop(bq, 40); ck_assert_int_eq(chunk.length - chunk.index, 40); pa_memblock_unref(chunk.memblock); check_queue_invariants(bq); /* length + missing + requested = tlength * 60 + 40 + 0 = 100 */ /* 40 bytes are missing, but that's less than minreq, so 0 is reported */ ck_assert_int_eq(pa_memblockq_pop_missing(bq), 0); /* length + missing + requested = tlength * 60 + 40 + 0 = 100 */ /* Now we push 30 bytes even though it was not requested, so the requested * counter goes negative! */ for (int i = 0; i != 3; ++i) ck_assert_int_eq(pa_memblockq_push(bq, &data), 0); check_queue_invariants(bq); /* length + missing + requested = tlength * 90 + 40 + -30 = 100 */ /* missing < minreq, so nothing is reported missing. */ ck_assert_int_eq(pa_memblockq_pop_missing(bq), 0); /* length + missing + requested = tlength * 90 + 40 + -30 = 100 */ ck_assert_int_eq(pa_memblockq_peek_fixed_size(bq, 20, &chunk), 0); pa_memblockq_drop(bq, 20); ck_assert_int_eq(chunk.length - chunk.index, 20); pa_memblock_unref(chunk.memblock); check_queue_invariants(bq); /* length + missing + requested = tlength * 70 + 60 + -30 = 100 */ /* missing < minreq, so nothing is reported missing. */ ck_assert_int_eq(pa_memblockq_pop_missing(bq), 0); /* length + missing + requested = tlength * 70 + 60 + -30 = 100 */ /* We push more data again even though it was not requested, so the * requested counter goes further into the negative range. */ for (int i = 0; i != 5; ++i) ck_assert_int_eq(pa_memblockq_push(bq, &data), 0); check_queue_invariants(bq); /* length + missing + requested = tlength * 120 + 60 + -80 = 100 */ /* missing < minreq, so nothing is reported missing. */ ck_assert_int_eq(pa_memblockq_pop_missing(bq), 0); /* length + missing + requested = tlength * 120 + 60 + -80 = 100 */ ck_assert_int_eq(pa_memblockq_peek_fixed_size(bq, 20, &chunk), 0); pa_memblockq_drop(bq, 20); ck_assert_int_eq(chunk.length - chunk.index, 20); pa_memblock_unref(chunk.memblock); check_queue_invariants(bq); /* length + missing + requested = tlength * 100 + 80 + -80 = 100 */ /* missing has now reached the minreq threshold */ ck_assert_int_eq(pa_memblockq_pop_missing(bq), 80); /* length + missing + requested = tlength * 100 + 0 + 0 = 100 */ /* cleanup */ pa_memblockq_free(bq); pa_memblock_unref(silence.memblock); pa_memblock_unref(data.memblock); pa_mempool_unref(p); } END_TEST START_TEST (memblockq_test_tlength_change) { int ret; size_t missing; pa_mempool *p; pa_memblockq *bq; pa_memchunk chunk; char buffer[2048]; pa_sample_spec ss = { .format = PA_SAMPLE_S16LE, .rate = 48000, .channels = 1 }; pa_log_set_level(PA_LOG_DEBUG); bq = pa_memblockq_new("test memblockq", 0, 4096, 2048, &ss, 0, 512, 512, NULL); fail_unless(bq != NULL); /* Empty buffer, so expect tlength */ missing = pa_memblockq_pop_missing(bq); fail_unless(missing == 2048); /* Everything requested, so should be satisfied */ missing = pa_memblockq_pop_missing(bq); fail_unless(missing == 0); p = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true); chunk.memblock = pa_memblock_new_fixed(p, buffer, sizeof(buffer), 1); fail_unless(chunk.memblock != NULL); chunk.index = 0; chunk.length = sizeof(buffer); /* Fill buffer (i.e. satisfy earlier request) */ ret = pa_memblockq_push(bq, &chunk); fail_unless(ret == 0); /* Should still be happy */ missing = pa_memblockq_pop_missing(bq); fail_unless(missing == 0); /* Check that we don't request less than minreq */ pa_memblockq_drop(bq, 400); missing = pa_memblockq_pop_missing(bq); ck_assert_int_eq(missing, 0); missing = pa_memblockq_pop_missing(bq); fail_unless(missing == 0); /* Reduce tlength under what's dropped and under previous minreq */ pa_memblockq_set_tlength(bq, 256); pa_memblockq_set_minreq(bq, 64); /* We are now overbuffered and should not request more */ missing = pa_memblockq_pop_missing(bq); fail_unless(missing == 0); /* Drop more data so we are below tlength again, but just barely */ pa_memblockq_drop(bq, 1400); /* Should still honour minreq */ missing = pa_memblockq_pop_missing(bq); fail_unless(missing == 0); /* Finally drop enough to fall below minreq */ pa_memblockq_drop(bq, 80); /* And expect a request */ missing = pa_memblockq_pop_missing(bq); fail_unless(missing == 88); pa_memblockq_free(bq); pa_memblock_unref(chunk.memblock); pa_mempool_unref(p); } END_TEST int main(int argc, char *argv[]) { int failed = 0; Suite *s; TCase *tc; SRunner *sr; if (!getenv("MAKE_CHECK")) pa_log_set_level(PA_LOG_DEBUG); s = suite_create("Memblock Queue"); tc = tcase_create("memblockq"); tcase_add_test(tc, memchunk_from_str_test); tcase_add_test(tc, memblockq_test_initial_properties); tcase_add_test(tc, memblockq_test); tcase_add_test(tc, memblockq_test_length_changes); tcase_add_test(tc, memblockq_test_pop_missing); tcase_add_test(tc, memblockq_test_tlength_change); suite_add_tcase(s, tc); sr = srunner_create(s); srunner_run_all(sr, CK_NORMAL); failed = srunner_ntests_failed(sr); srunner_free(sr); return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; }