diff options
author | Matthew Jordan <mjordan@digium.com> | 2012-03-14 18:56:15 +0000 |
---|---|---|
committer | Matthew Jordan <mjordan@digium.com> | 2012-03-14 18:56:15 +0000 |
commit | a699bb72ad8d255c84998e97addd2e20ab3f6b7b (patch) | |
tree | 46e9a4a5333a9fbb37db219e234ef252aa99df57 /tests/test_jitterbuf.c | |
parent | a22b6f6e4b8722ce8c2a174b2e883f0f6ba1f33d (diff) |
Add tests for main/jitterbuf.c
This patch adds unit tests for main/jitterbuf.c. This includes checking for
the following:
* Nominal insertion and retrieval of frames
* Insertion and retrieval of frames where the frames are inserted out of
order with respect to the previous frame
* Insertion and retrieval of frames where some number of frames that would
occur in the expected sequence are instead dropped
* Insertion and retrieval of frames with an arrival time that does not occur
at the same rate as the surrounding frames
* Resynchronization of the jitter buffer when an inserted frame breaks the
resynchronization threshold
* Overfilling of the jitter buffer
For each of the tests, both JB_TYPE_VOICE and JB_TYPE_CONTROL permutations
exist.
Review: https://reviewboard.asterisk.org/r/1815
(issue: ASTERISK-18964)
Reported by: Kris Shaw
Tested by: Kris Shaw, Matt Jordan
git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@359406 65c4cc65-6c06-0410-ace0-fbb531ad65f3
Diffstat (limited to 'tests/test_jitterbuf.c')
-rw-r--r-- | tests/test_jitterbuf.c | 1254 |
1 files changed, 1254 insertions, 0 deletions
diff --git a/tests/test_jitterbuf.c b/tests/test_jitterbuf.c new file mode 100644 index 000000000..65e7d4dcd --- /dev/null +++ b/tests/test_jitterbuf.c @@ -0,0 +1,1254 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2012, Matt Jordan + * + * Matt Jordan <mjordan@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! + * \file + * \brief Unit tests for jitterbuf.c + * + * \author\verbatim Matt Jordan <mjordan@digium.com> \endverbatim + * + * \ingroup tests + */ + +/*** MODULEINFO + <depend>TEST_FRAMEWORK</depend> + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/utils.h" +#include "asterisk/module.h" +#include "asterisk/test.h" +#include "jitterbuf.h" + +#define DEFAULT_MAX_JITTERBUFFER 1000 +#define DEFAULT_RESYNCH_THRESHOLD 1000 +#define DEFAULT_MAX_CONTIG_INTERP 10 +#define DEFAULT_TARGET_EXTRA -1 +#define DEFAULT_CODEC_INTERP_LEN 20 + +/*! \internal + * Test two numeric (long int) values. Failure automatically attempts + * to jump to a cleanup tag + */ +#define JB_NUMERIC_TEST(attribute, expected) do { \ + if ((attribute) != (expected)) { \ + ast_test_status_update(test, #attribute ": expected [%ld]; actual [%ld]\n", (long int)(expected), (attribute)); \ + goto cleanup; \ + } \ +} while (0) + +/*! \internal + * Print out as debug the frame related contents of a jb_info object + */ +#define JB_INFO_PRINT_FRAME_DEBUG(jbinfo) do { \ + ast_debug(1, "JitterBuffer Frame Info:\n" \ + "\tFrames In: %ld\n\tFrames Out: %ld\n" \ + "\tDropped Frames: %ld\n\tLate Frames: %ld\n" \ + "\tLost Frames: %ld\n\tOut of Order Frames: %ld\n" \ + "\tCurrent Frame: %ld\n", jbinfo.frames_in, jbinfo.frames_out, \ + jbinfo.frames_dropped, jbinfo.frames_late, jbinfo.frames_lost, \ + jbinfo.frames_ooo, jbinfo.frames_cur); \ +} while (0) + +/*! \internal + * This macro installs the error, warning, and debug functions for a test. It is + * expected that at the end of a test, the functions are removed. + * Note that the debug statement is in here merely to aid in tracing in a log where + * the jitter buffer debug output begins. + */ +#define JB_TEST_BEGIN(test_name) do { \ + jb_setoutput(test_jb_error_output, test_jb_warn_output, test_jb_debug_output); \ + ast_debug(1, "Starting %s\n", test_name); \ +} while (0) + +/*! \internal + * Uninstall the error, warning, and debug functions from a test + */ +#define JB_TEST_END do { \ + jb_setoutput(NULL, NULL, NULL); \ +} while (0) + +static const char *jitter_buffer_return_codes[] = { + "JB_OK", /* 0 */ + "JB_EMPTY", /* 1 */ + "JB_NOFRAME", /* 2 */ + "JB_INTERP", /* 3 */ + "JB_DROP", /* 4 */ + "JB_SCHED" /* 5 */ +}; + +/*! \internal \brief Make a default jitter buffer configuration */ +static void test_jb_populate_config(struct jb_conf *jbconf) +{ + if (!jbconf) { + return; + } + + jbconf->max_jitterbuf = DEFAULT_MAX_JITTERBUFFER; + jbconf->resync_threshold = DEFAULT_RESYNCH_THRESHOLD; + jbconf->max_contig_interp = DEFAULT_MAX_CONTIG_INTERP; + jbconf->target_extra = 0; +} + +/*! \internal \brief Debug callback function for the jitter buffer's jb_dbg function */ +static void __attribute__((format(printf, 1, 2))) test_jb_debug_output(const char *fmt, ...) +{ + va_list args; + char buf[1024]; + + va_start(args, fmt); + vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + + ast_debug(1, "%s", buf); +} + +/*! \internal \brief Warning callback function for the jitter buffer's jb_warn function */ +static void __attribute__((format(printf, 1, 2))) test_jb_warn_output(const char *fmt, ...) +{ + va_list args; + char buf[1024]; + + va_start(args, fmt); + vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + + ast_log(AST_LOG_WARNING, "%s", buf); +} + +/*! \internal \brief Error callback function for the jitter buffer's jb_err function */ +static void __attribute__((format(printf, 1, 2))) test_jb_error_output(const char *fmt, ...) +{ + va_list args; + char buf[1024]; + + va_start(args, fmt); + vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + + ast_log(AST_LOG_ERROR, "%s", buf); +} + +/*! \internal \brief Insert frames into the jitter buffer for the nominal tests */ +static int test_jb_nominal_frame_insertion(struct ast_test *test, struct jitterbuf *jb, enum jb_frame_type frame_type) +{ + int i = 0, ret = 0; + + for (i = 0; i < 40; i++) { + if (jb_put(jb, NULL, frame_type, 20, i * 20, i * 20 + 5) == JB_DROP) { + ast_test_status_update(test, "Jitter buffer dropped packet %d\n", i); + ret = 1; + break; + } + } + + return ret; +} + +AST_TEST_DEFINE(jitterbuffer_nominal_voice_frames) +{ + enum ast_test_result_state result = AST_TEST_FAIL; + struct jitterbuf *jb = NULL; + struct jb_frame frame; + struct jb_conf jbconf; + struct jb_info jbinfo; + int i = 0; + + switch (cmd) { + case TEST_INIT: + info->name = "jitterbuffer_nominal_voice_frames"; + info->category = "/main/jitterbuf/"; + info->summary = "Nominal operation of jitter buffer with audio data"; + info->description = + "Tests the nominal case of putting audio data into a jitter buffer, " + "retrieving the frames, and querying for the next frame"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + JB_TEST_BEGIN("jitterbuffer_nominal_voice_frames"); + + if (!(jb = jb_new())) { + ast_test_status_update(test, "Failed to allocate memory for jitterbuffer\n"); + goto cleanup; + } + + test_jb_populate_config(&jbconf); + if (jb_setconf(jb, &jbconf) != JB_OK) { + ast_test_status_update(test, "Failed to set jitterbuffer configuration\n"); + goto cleanup; + } + + if (test_jb_nominal_frame_insertion(test, jb, JB_TYPE_VOICE)) { + goto cleanup; + } + + for (i = 0; i < 40; i++) { + enum jb_return_code ret; + /* We should have a frame for each point in time */ + if ((ret = jb_get(jb, &frame, i * 20 + 5, DEFAULT_CODEC_INTERP_LEN)) != JB_OK) { + ast_test_status_update(test, + "Unexpected jitter buffer return code [%s] when retrieving frame %d\n", + jitter_buffer_return_codes[ret], i); + goto cleanup; + } + JB_NUMERIC_TEST(frame.ms, 20); + JB_NUMERIC_TEST(frame.ts, i * 20 - jb->info.resync_offset); + JB_NUMERIC_TEST(jb_next(jb), (i + 1) * 20 + 5); + } + + result = AST_TEST_PASS; + + if (jb_getinfo(jb, &jbinfo) != JB_OK) { + ast_test_status_update(test, "Failed to get jitterbuffer information\n"); + goto cleanup; + } + JB_INFO_PRINT_FRAME_DEBUG(jbinfo); + JB_NUMERIC_TEST(jbinfo.frames_dropped, 0); + JB_NUMERIC_TEST(jbinfo.frames_in, 40); + JB_NUMERIC_TEST(jbinfo.frames_out, 40); + JB_NUMERIC_TEST(jbinfo.frames_late, 0); + JB_NUMERIC_TEST(jbinfo.frames_lost, 0); + JB_NUMERIC_TEST(jbinfo.frames_ooo, 0); + +cleanup: + if (jb) { + /* No need to do anything - this will put all frames on the 'free' list, + * so jb_destroy will dispose of them */ + while (jb_getall(jb, &frame) == JB_OK) { } + jb_destroy(jb); + } + + JB_TEST_END; + + return result; +} + +AST_TEST_DEFINE(jitterbuffer_nominal_control_frames) +{ + enum ast_test_result_state result = AST_TEST_FAIL; + struct jitterbuf *jb = NULL; + struct jb_frame frame; + struct jb_conf jbconf; + struct jb_info jbinfo; + int i = 0; + + switch (cmd) { + case TEST_INIT: + info->name = "jitterbuffer_nominal_control_frames"; + info->category = "/main/jitterbuf/"; + info->summary = "Nominal operation of jitter buffer with control frames"; + info->description = + "Tests the nominal case of putting control frames into a jitter buffer, " + "retrieving the frames, and querying for the next frame"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + JB_TEST_BEGIN("jitterbuffer_nominal_control_frames"); + + if (!(jb = jb_new())) { + ast_test_status_update(test, "Failed to allocate memory for jitterbuffer\n"); + goto cleanup; + } + + test_jb_populate_config(&jbconf); + if (jb_setconf(jb, &jbconf) != JB_OK) { + ast_test_status_update(test, "Failed to set jitterbuffer configuration\n"); + goto cleanup; + } + + if (test_jb_nominal_frame_insertion(test, jb, JB_TYPE_CONTROL)) { + goto cleanup; + } + + for (i = 0; i < 40; i++) { + enum jb_return_code ret; + /* We should have a frame for each point in time */ + if ((ret = jb_get(jb, &frame, i * 20 + 5, DEFAULT_CODEC_INTERP_LEN)) != JB_OK) { + ast_test_status_update(test, + "Unexpected jitter buffer return code [%s] when retrieving frame %d\n", + jitter_buffer_return_codes[ret], i); + goto cleanup; + } + JB_NUMERIC_TEST(frame.ms, 20); + JB_NUMERIC_TEST(frame.ts, i * 20 - jb->info.resync_offset); + } + + if (jb_getinfo(jb, &jbinfo) != JB_OK) { + ast_test_status_update(test, "Failed to get jitterbuffer information\n"); + goto cleanup; + } + JB_INFO_PRINT_FRAME_DEBUG(jbinfo); + JB_NUMERIC_TEST(jbinfo.frames_dropped, 0); + JB_NUMERIC_TEST(jbinfo.frames_in, 40); + JB_NUMERIC_TEST(jbinfo.frames_out, 40); + JB_NUMERIC_TEST(jbinfo.frames_late, 0); + JB_NUMERIC_TEST(jbinfo.frames_lost, 0); + JB_NUMERIC_TEST(jbinfo.frames_ooo, 0); + + result = AST_TEST_PASS; + +cleanup: + if (jb) { + /* No need to do anything - this will put all frames on the 'free' list, + * so jb_destroy will dispose of them */ + while (jb_getall(jb, &frame) == JB_OK) { } + jb_destroy(jb); + } + + JB_TEST_END; + + return result; +} + +/*! \internal \brief Insert frames into the jitter buffer for the out of order tests */ +static int test_jb_out_of_order_frame_insertion(struct ast_test *test, struct jitterbuf *jb, enum jb_frame_type frame_type) +{ + int i = 0, ret = 0; + + for (i = 0; i < 40; i++) { + if (i % 4 == 0) { + /* Add the next frame */ + if (jb_put(jb, NULL, frame_type, 20, (i + 1) * 20, (i + 1) * 20 + 5) == JB_DROP) { + ast_test_status_update(test, "Jitter buffer dropped packet %d\n", (i+1)); + ret = 1; + break; + } + /* Add the current frame */ + if (jb_put(jb, NULL, frame_type, 20, i * 20, i * 20 + 5) == JB_DROP) { + ast_test_status_update(test, "Jitter buffer dropped packet %d\n", i); + ret = 1; + break; + } + i++; + } else { + if (jb_put(jb, NULL, frame_type, 20, i * 20, i * 20 + 5) == JB_DROP) { + ast_test_status_update(test, "Jitter buffer dropped packet %d\n", i); + ret = 1; + break; + } + } + } + + return ret; +} + +AST_TEST_DEFINE(jitterbuffer_out_of_order_voice) +{ + enum ast_test_result_state result = AST_TEST_FAIL; + struct jitterbuf *jb = NULL; + struct jb_frame frame; + struct jb_info jbinfo; + struct jb_conf jbconf; + int i; + + switch (cmd) { + case TEST_INIT: + info->name = "jitterbuffer_out_of_order_voice"; + info->category = "/main/jitterbuf/"; + info->summary = "Tests sending out of order audio frames to a jitter buffer"; + info->description = + "Every 5th frame sent to a jitter buffer is reversed with the previous " + "frame. The expected result is to have a jitter buffer with the frames " + "in order, while a total of 10 frames should be recorded as having been " + "received out of order."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + JB_TEST_BEGIN("jitterbuffer_out_of_order_voice"); + + if (!(jb = jb_new())) { + ast_test_status_update(test, "Failed to allocate memory for jitterbuffer\n"); + goto cleanup; + } + + test_jb_populate_config(&jbconf); + if (jb_setconf(jb, &jbconf) != JB_OK) { + ast_test_status_update(test, "Failed to set jitterbuffer configuration\n"); + goto cleanup; + } + + if (test_jb_out_of_order_frame_insertion(test, jb, JB_TYPE_VOICE)) { + goto cleanup; + } + + for (i = 0; i < 40; i++) { + enum jb_return_code ret; + /* We should have a frame for each point in time */ + if ((ret = jb_get(jb, &frame, i * 20 + 5, DEFAULT_CODEC_INTERP_LEN)) != JB_OK) { + ast_test_status_update(test, + "Unexpected jitter buffer return code [%s] when retrieving frame %d\n", + jitter_buffer_return_codes[ret], i); + goto cleanup; + } + JB_NUMERIC_TEST(frame.ms, 20); + JB_NUMERIC_TEST(frame.ts, i * 20 - jb->info.resync_offset); + } + + if (jb_getinfo(jb, &jbinfo) != JB_OK) { + ast_test_status_update(test, "Failed to get jitterbuffer information\n"); + goto cleanup; + } + JB_INFO_PRINT_FRAME_DEBUG(jbinfo); + JB_NUMERIC_TEST(jbinfo.frames_dropped, 0); + JB_NUMERIC_TEST(jbinfo.frames_in, 40); + JB_NUMERIC_TEST(jbinfo.frames_out, 40); + JB_NUMERIC_TEST(jbinfo.frames_late, 0); + JB_NUMERIC_TEST(jbinfo.frames_lost, 0); + JB_NUMERIC_TEST(jbinfo.frames_ooo, 10); + + result = AST_TEST_PASS; + +cleanup: + if (jb) { + /* No need to do anything - this will put all frames on the 'free' list, + * so jb_destroy will dispose of them */ + while (jb_getall(jb, &frame) == JB_OK) { } + jb_destroy(jb); + } + + JB_TEST_END; + + return result; +} + +AST_TEST_DEFINE(jitterbuffer_out_of_order_control) +{ + enum ast_test_result_state result = AST_TEST_FAIL; + struct jitterbuf *jb = NULL; + struct jb_frame frame; + struct jb_info jbinfo; + struct jb_conf jbconf; + int i; + + switch (cmd) { + case TEST_INIT: + info->name = "jitterbuffer_out_of_order_voice"; + info->category = "/main/jitterbuf/"; + info->summary = "Tests sending out of order audio frames to a jitter buffer"; + info->description = + "Every 5th frame sent to a jitter buffer is reversed with the previous " + "frame. The expected result is to have a jitter buffer with the frames " + "in order, while a total of 10 frames should be recorded as having been " + "received out of order."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + JB_TEST_BEGIN("jitterbuffer_out_of_order_control"); + + if (!(jb = jb_new())) { + ast_test_status_update(test, "Failed to allocate memory for jitterbuffer\n"); + goto cleanup; + } + + test_jb_populate_config(&jbconf); + if (jb_setconf(jb, &jbconf) != JB_OK) { + ast_test_status_update(test, "Failed to set jitterbuffer configuration\n"); + goto cleanup; + } + + if (test_jb_out_of_order_frame_insertion(test, jb, JB_TYPE_CONTROL)) { + goto cleanup; + } + + for (i = 0; i < 40; i++) { + enum jb_return_code ret; + /* We should have a frame for each point in time */ + if ((ret = jb_get(jb, &frame, i * 20 + 5, DEFAULT_CODEC_INTERP_LEN)) != JB_OK) { + ast_test_status_update(test, + "Unexpected jitter buffer return code [%s] when retrieving frame %d\n", + jitter_buffer_return_codes[ret], i); + goto cleanup; + } + JB_NUMERIC_TEST(frame.ms, 20); + JB_NUMERIC_TEST(frame.ts, i * 20 - jb->info.resync_offset); + } + + if (jb_getinfo(jb, &jbinfo) != JB_OK) { + ast_test_status_update(test, "Failed to get jitterbuffer information\n"); + goto cleanup; + } + JB_INFO_PRINT_FRAME_DEBUG(jbinfo); + JB_NUMERIC_TEST(jbinfo.frames_dropped, 0); + JB_NUMERIC_TEST(jbinfo.frames_in, 40); + JB_NUMERIC_TEST(jbinfo.frames_out, 40); + JB_NUMERIC_TEST(jbinfo.frames_late, 0); + JB_NUMERIC_TEST(jbinfo.frames_lost, 0); + JB_NUMERIC_TEST(jbinfo.frames_ooo, 10); + + result = AST_TEST_PASS; + +cleanup: + if (jb) { + /* No need to do anything - this will put all frames on the 'free' list, + * so jb_destroy will dispose of them */ + while (jb_getall(jb, &frame) == JB_OK) { } + jb_destroy(jb); + } + + JB_TEST_END; + + return result; +} + +/*! \internal \brief Insert frames into the jitter buffer for the lost frame tests */ +static int test_jb_lost_frame_insertion(struct ast_test *test, struct jitterbuf *jb, enum jb_frame_type frame_type) +{ + int i = 0, ret = 0; + + for (i = 0; i < 40; i++) { + if (i % 5 == 0) { + i++; + } + if (jb_put(jb, NULL, frame_type, 20, i * 20, i * 20 + 5) == JB_DROP) { + ast_test_status_update(test, "Jitter buffer dropped packet %d\n", i); + ret = 1; + break; + } + } + + return ret; +} + +AST_TEST_DEFINE(jitterbuffer_lost_voice) +{ + enum ast_test_result_state result = AST_TEST_FAIL; + struct jitterbuf *jb = NULL; + struct jb_frame frame; + struct jb_conf jbconf; + struct jb_info jbinfo; + int i; + + switch (cmd) { + case TEST_INIT: + info->name = "jitterbuffer_lost_voice"; + info->category = "/main/jitterbuf/"; + info->summary = "Tests missing frames in the jitterbuffer"; + info->description = + "Every 5th frame that would be sent to a jitter buffer is instead" + "dropped. When reading data from the jitter buffer, the jitter buffer" + "should interpolate the voice frame."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + JB_TEST_BEGIN("jitterbuffer_lost_voice"); + + if (!(jb = jb_new())) { + ast_test_status_update(test, "Failed to allocate memory for jitterbuffer\n"); + goto cleanup; + } + + test_jb_populate_config(&jbconf); + if (jb_setconf(jb, &jbconf) != JB_OK) { + ast_test_status_update(test, "Failed to set jitterbuffer configuration\n"); + goto cleanup; + } + + if (test_jb_lost_frame_insertion(test, jb, JB_TYPE_VOICE)) { + goto cleanup; + } + + for (i = 0; i < 40; i++) { + enum jb_return_code ret; + if ((ret = jb_get(jb, &frame, i * 20 + 5, DEFAULT_CODEC_INTERP_LEN)) != JB_OK) { + /* If we didn't get an OK, make sure that it was an expected lost frame */ + if (!((ret == JB_INTERP && i % 5 == 0) || (ret == JB_NOFRAME && i == 0))) { + ast_test_status_update(test, + "Unexpected jitter buffer return code [%s] when retrieving frame %d\n", + jitter_buffer_return_codes[ret], i); + goto cleanup; + } + } else { + JB_NUMERIC_TEST(frame.ms, 20); + JB_NUMERIC_TEST(frame.ts, i * 20 - jb->info.resync_offset); + } + } + + if (jb_getinfo(jb, &jbinfo) != JB_OK) { + ast_test_status_update(test, "Failed to get jitterbuffer information\n"); + goto cleanup; + } + JB_INFO_PRINT_FRAME_DEBUG(jbinfo); + /* Note: The first frame (at i = 0) never got added, so nothing existed at that point. + * Its neither dropped nor lost. + */ + JB_NUMERIC_TEST(jbinfo.frames_ooo, 0); + JB_NUMERIC_TEST(jbinfo.frames_late, 0); + JB_NUMERIC_TEST(jbinfo.frames_lost, 7); + JB_NUMERIC_TEST(jbinfo.frames_in, 32); + JB_NUMERIC_TEST(jbinfo.frames_out, 32); + JB_NUMERIC_TEST(jbinfo.frames_dropped, 0); + + result = AST_TEST_PASS; + +cleanup: + if (jb) { + /* No need to do anything - this will put all frames on the 'free' list, + * so jb_destroy will dispose of them */ + while (jb_getall(jb, &frame) == JB_OK) { } + jb_destroy(jb); + } + + JB_TEST_END; + + return result; +} + +AST_TEST_DEFINE(jitterbuffer_lost_control) +{ + enum ast_test_result_state result = AST_TEST_FAIL; + struct jitterbuf *jb = NULL; + struct jb_frame frame; + struct jb_conf jbconf; + struct jb_info jbinfo; + int i; + + switch (cmd) { + case TEST_INIT: + info->name = "jitterbuffer_lost_control"; + info->category = "/main/jitterbuf/"; + info->summary = "Tests missing frames in the jitterbuffer"; + info->description = + "Every 5th frame that would be sent to a jitter buffer is instead" + "dropped. When reading data from the jitter buffer, the jitter buffer" + "simply reports that no frame exists for that time slot"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + JB_TEST_BEGIN("jitterbuffer_lost_control"); + + if (!(jb = jb_new())) { + ast_test_status_update(test, "Failed to allocate memory for jitterbuffer\n"); + goto cleanup; + } + + test_jb_populate_config(&jbconf); + if (jb_setconf(jb, &jbconf) != JB_OK) { + ast_test_status_update(test, "Failed to set jitterbuffer configuration\n"); + goto cleanup; + } + + if (test_jb_lost_frame_insertion(test, jb, JB_TYPE_CONTROL)) { + goto cleanup; + } + + for (i = 0; i < 40; i++) { + enum jb_return_code ret; + if ((ret = jb_get(jb, &frame, i * 20 + 5, DEFAULT_CODEC_INTERP_LEN)) != JB_OK) { + /* If we didn't get an OK, make sure that it was an expected lost frame */ + if (!(ret == JB_NOFRAME && i % 5 == 0)) { + ast_test_status_update(test, + "Unexpected jitter buffer return code [%s] when retrieving frame %d\n", + jitter_buffer_return_codes[ret], i); + goto cleanup; + } + } else { + JB_NUMERIC_TEST(frame.ms, 20); + JB_NUMERIC_TEST(frame.ts, i * 20 - jb->info.resync_offset); + } + } + + if (jb_getinfo(jb, &jbinfo) != JB_OK) { + ast_test_status_update(test, "Failed to get jitterbuffer information\n"); + goto cleanup; + } + JB_INFO_PRINT_FRAME_DEBUG(jbinfo); + /* Note: The first frame (at i = 0) never got added, so nothing existed at that point. + * Its neither dropped nor lost. + */ + JB_NUMERIC_TEST(jbinfo.frames_ooo, 0); + JB_NUMERIC_TEST(jbinfo.frames_late, 0); + JB_NUMERIC_TEST(jbinfo.frames_lost, 0); + JB_NUMERIC_TEST(jbinfo.frames_in, 32); + JB_NUMERIC_TEST(jbinfo.frames_out, 32); + JB_NUMERIC_TEST(jbinfo.frames_dropped, 0); + + result = AST_TEST_PASS; + +cleanup: + if (jb) { + /* No need to do anything - this will put all frames on the 'free' list, + * so jb_destroy will dispose of them */ + while (jb_getall(jb, &frame) == JB_OK) { } + jb_destroy(jb); + } + + JB_TEST_END; + + return result; +} + +/*! \internal \brief Insert frames into the jitter buffer for the late frame tests */ +static int test_jb_late_frame_insertion(struct ast_test *test, struct jitterbuf *jb, enum jb_frame_type frame_type) +{ + int i = 0, ret = 0; + + for (i = 0; i < 40; i++) { + if (i % 5 == 0) { + /* Add 5th frame */ + if (jb_put(jb, NULL, frame_type, 20, i * 20, i * 20 + 20) == JB_DROP) { + ast_test_status_update(test, "Jitter buffer dropped packet %d\n", (i+1)); + ret = 1; + break; + } + } else { + if (jb_put(jb, NULL, frame_type, 20, i * 20, i * 20 + 5) == JB_DROP) { + ast_test_status_update(test, "Jitter buffer dropped packet %d\n", i); + ret = 1; + break; + } + } + } + + return ret; +} + +AST_TEST_DEFINE(jitterbuffer_late_voice) +{ + enum ast_test_result_state result = AST_TEST_FAIL; + struct jitterbuf *jb = NULL; + struct jb_frame frame; + struct jb_info jbinfo; + struct jb_conf jbconf; + int i; + + switch (cmd) { + case TEST_INIT: + info->name = "jitterbuffer_late_voice"; + info->category = "/main/jitterbuf/"; + info->summary = "Tests sending frames to a jitter buffer that arrive late"; + info->description = + "Every 5th frame sent to a jitter buffer arrives late, but still in " + "order with respect to the previous and next packet"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + JB_TEST_BEGIN("jitterbuffer_late_voice"); + + if (!(jb = jb_new())) { + ast_test_status_update(test, "Failed to allocate memory for jitterbuffer\n"); + goto cleanup; + } + + test_jb_populate_config(&jbconf); + if (jb_setconf(jb, &jbconf) != JB_OK) { + ast_test_status_update(test, "Failed to set jitterbuffer configuration\n"); + goto cleanup; + } + + if (test_jb_late_frame_insertion(test, jb, JB_TYPE_VOICE)) { + goto cleanup; + } + + for (i = 0; i < 40; i++) { + enum jb_return_code ret; + /* We should have a frame for each point in time */ + if ((ret = jb_get(jb, &frame, i * 20 + 5, DEFAULT_CODEC_INTERP_LEN)) != JB_OK) { + ast_test_status_update(test, + "Unexpected jitter buffer return code [%s] when retrieving frame %d\n", + jitter_buffer_return_codes[ret], i); + goto cleanup; + } + JB_NUMERIC_TEST(frame.ms, 20); + JB_NUMERIC_TEST(frame.ts, i * 20 - jb->info.resync_offset); + } + + if (jb_getinfo(jb, &jbinfo) != JB_OK) { + ast_test_status_update(test, "Failed to get jitterbuffer information\n"); + goto cleanup; + } + JB_INFO_PRINT_FRAME_DEBUG(jbinfo); + JB_NUMERIC_TEST(jbinfo.frames_ooo, 0); + JB_NUMERIC_TEST(jbinfo.frames_late, 0); + JB_NUMERIC_TEST(jbinfo.frames_lost, 0); + JB_NUMERIC_TEST(jbinfo.frames_in, 40); + JB_NUMERIC_TEST(jbinfo.frames_out, 40); + JB_NUMERIC_TEST(jbinfo.frames_dropped, 0); + + result = AST_TEST_PASS; + +cleanup: + if (jb) { + /* No need to do anything - this will put all frames on the 'free' list, + * so jb_destroy will dispose of them */ + while (jb_getall(jb, &frame) == JB_OK) { } + jb_destroy(jb); + } + + JB_TEST_END; + + return result; +} + +AST_TEST_DEFINE(jitterbuffer_late_control) +{ + enum ast_test_result_state result = AST_TEST_FAIL; + struct jitterbuf *jb = NULL; + struct jb_frame frame; + struct jb_info jbinfo; + struct jb_conf jbconf; + int i; + + switch (cmd) { + case TEST_INIT: + info->name = "jitterbuffer_late_control"; + info->category = "/main/jitterbuf/"; + info->summary = "Tests sending frames to a jitter buffer that arrive late"; + info->description = + "Every 5th frame sent to a jitter buffer arrives late, but still in " + "order with respect to the previous and next packet"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + JB_TEST_BEGIN("jitterbuffer_late_voice"); + + if (!(jb = jb_new())) { + ast_test_status_update(test, "Failed to allocate memory for jitterbuffer\n"); + goto cleanup; + } + + test_jb_populate_config(&jbconf); + if (jb_setconf(jb, &jbconf) != JB_OK) { + ast_test_status_update(test, "Failed to set jitterbuffer configuration\n"); + goto cleanup; + } + + if (test_jb_late_frame_insertion(test, jb, JB_TYPE_CONTROL)) { + goto cleanup; + } + + for (i = 0; i < 40; i++) { + enum jb_return_code ret; + /* We should have a frame for each point in time */ + if ((ret = jb_get(jb, &frame, i * 20 + 5, DEFAULT_CODEC_INTERP_LEN)) != JB_OK) { + ast_test_status_update(test, + "Unexpected jitter buffer return code [%s] when retrieving frame %d\n", + jitter_buffer_return_codes[ret], i); + goto cleanup; + } + JB_NUMERIC_TEST(frame.ms, 20); + JB_NUMERIC_TEST(frame.ts, i * 20 - jb->info.resync_offset); + } + + if (jb_getinfo(jb, &jbinfo) != JB_OK) { + ast_test_status_update(test, "Failed to get jitterbuffer information\n"); + goto cleanup; + } + JB_INFO_PRINT_FRAME_DEBUG(jbinfo); + JB_NUMERIC_TEST(jbinfo.frames_ooo, 0); + JB_NUMERIC_TEST(jbinfo.frames_late, 0); + JB_NUMERIC_TEST(jbinfo.frames_lost, 0); + JB_NUMERIC_TEST(jbinfo.frames_in, 40); + JB_NUMERIC_TEST(jbinfo.frames_out, 40); + JB_NUMERIC_TEST(jbinfo.frames_dropped, 0); + + result = AST_TEST_PASS; + +cleanup: + if (jb) { + /* No need to do anything - this will put all frames on the 'free' list, + * so jb_destroy will dispose of them */ + while (jb_getall(jb, &frame) == JB_OK) { } + jb_destroy(jb); + } + + JB_TEST_END; + + return result; +} + +/*! \internal \brief Insert frames into the jitter buffer for the overflow tests */ +static void test_jb_overflow_frame_insertion(struct jitterbuf *jb, enum jb_frame_type frame_type) +{ + int i = 0; + + for (i = 0; i < 100; i++) { + jb_put(jb, NULL, frame_type, 20, i * 20, i * 20 + 5); + } +} + +AST_TEST_DEFINE(jitterbuffer_overflow_voice) +{ + enum ast_test_result_state result = AST_TEST_FAIL; + struct jitterbuf *jb = NULL; + struct jb_frame frame; + struct jb_info jbinfo; + struct jb_conf jbconf; + int i = 0; + + switch (cmd) { + case TEST_INIT: + info->name = "jitterbuffer_overflow_voice"; + info->category = "/main/jitterbuf/"; + info->summary = "Tests overfilling a jitter buffer with voice frames"; + info->description = "Tests overfilling a jitter buffer with voice frames"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + JB_TEST_BEGIN("jitterbuffer_overflow_voice"); + + if (!(jb = jb_new())) { + ast_test_status_update(test, "Failed to allocate memory for jitterbuffer\n"); + goto cleanup; + } + + test_jb_populate_config(&jbconf); + if (jb_setconf(jb, &jbconf) != JB_OK) { + ast_test_status_update(test, "Failed to set jitterbuffer configuration\n"); + goto cleanup; + } + + test_jb_overflow_frame_insertion(jb, JB_TYPE_VOICE); + + while (jb_get(jb, &frame, i * 20 + 5, DEFAULT_CODEC_INTERP_LEN) == JB_OK) { + JB_NUMERIC_TEST(frame.ms, 20); + JB_NUMERIC_TEST(frame.ts, i * 20 - jb->info.resync_offset); + ++i; + } + + if (jb_getinfo(jb, &jbinfo) != JB_OK) { + ast_test_status_update(test, "Failed to get jitterbuffer information\n"); + goto cleanup; + } + + JB_INFO_PRINT_FRAME_DEBUG(jbinfo); + JB_NUMERIC_TEST(jbinfo.frames_dropped, 49); + JB_NUMERIC_TEST(jbinfo.frames_out, 51); + JB_NUMERIC_TEST(jbinfo.frames_in, 51); + JB_NUMERIC_TEST(jbinfo.frames_late, 0); + /* Note that the last frame will be interpolated */ + JB_NUMERIC_TEST(jbinfo.frames_lost, 1); + JB_NUMERIC_TEST(jbinfo.frames_ooo, 0); + + result = AST_TEST_PASS; + +cleanup: + if (jb) { + /* No need to do anything - this will put all frames on the 'free' list, + * so jb_destroy will dispose of them */ + while (jb_getall(jb, &frame) == JB_OK) { } + jb_destroy(jb); + } + + JB_TEST_END; + + return result; +} + +AST_TEST_DEFINE(jitterbuffer_overflow_control) +{ + enum ast_test_result_state result = AST_TEST_FAIL; + struct jitterbuf *jb = NULL; + struct jb_frame frame; + struct jb_info jbinfo; + struct jb_conf jbconf; + int i = 0; + + switch (cmd) { + case TEST_INIT: + info->name = "jitterbuffer_overflow_control"; + info->category = "/main/jitterbuf/"; + info->summary = "Tests overfilling a jitter buffer with control frames"; + info->description = "Tests overfilling a jitter buffer with control frames"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + JB_TEST_BEGIN("jitterbuffer_overflow_control"); + + if (!(jb = jb_new())) { + ast_test_status_update(test, "Failed to allocate memory for jitterbuffer\n"); + goto cleanup; + } + + test_jb_populate_config(&jbconf); + if (jb_setconf(jb, &jbconf) != JB_OK) { + ast_test_status_update(test, "Failed to set jitterbuffer configuration\n"); + goto cleanup; + } + + test_jb_overflow_frame_insertion(jb, JB_TYPE_CONTROL); + + while (jb_get(jb, &frame, i * 20 + 5, DEFAULT_CODEC_INTERP_LEN) == JB_OK) { + JB_NUMERIC_TEST(frame.ms, 20); + JB_NUMERIC_TEST(frame.ts, i * 20 - jb->info.resync_offset); + ++i; + } + + if (jb_getinfo(jb, &jbinfo) != JB_OK) { + ast_test_status_update(test, "Failed to get jitterbuffer information\n"); + goto cleanup; + } + + JB_INFO_PRINT_FRAME_DEBUG(jbinfo); + JB_NUMERIC_TEST(jbinfo.frames_dropped, 49); + JB_NUMERIC_TEST(jbinfo.frames_out, 51); + JB_NUMERIC_TEST(jbinfo.frames_in, 51); + JB_NUMERIC_TEST(jbinfo.frames_late, 0); + JB_NUMERIC_TEST(jbinfo.frames_lost, 0); + JB_NUMERIC_TEST(jbinfo.frames_ooo, 0); + + result = AST_TEST_PASS; + +cleanup: + if (jb) { + /* No need to do anything - this will put all frames on the 'free' list, + * so jb_destroy will dispose of them */ + while (jb_getall(jb, &frame) == JB_OK) { } + jb_destroy(jb); + } + + JB_TEST_END; + + return result; +} + +/*! \internal \brief Insert frames into the jitter buffer for the resynch tests */ +static void test_jb_resynch_frame_insertion(struct jitterbuf *jb, enum jb_frame_type frame_type) +{ + int i = 0; + + for (i = 0; i < 20; i++) { + jb_put(jb, NULL, frame_type, 20, i * 20, i * 20 + 5); + } + + for (i = 20; i < 40; i++) { + jb_put(jb, NULL, frame_type, 20, i * 20 + 500, i * 20 + 5); + } +} + +AST_TEST_DEFINE(jitterbuffer_resynch_control) +{ + enum ast_test_result_state result = AST_TEST_FAIL; + struct jitterbuf *jb = NULL; + struct jb_frame frame; + struct jb_info jbinfo; + struct jb_conf jbconf; + int interpolated_frames = 0; + int i; + + switch (cmd) { + case TEST_INIT: + info->name = "jitterbuffer_resynch_control"; + info->category = "/main/jitterbuf/"; + info->summary = "Tests sending control frames that force a resynch"; + info->description = "Control frames are sent to a jitter buffer. After some " + "number of frames, the source timestamps jump, forcing a resync of " + "the jitter buffer. Since the frames are control, the resync happens " + "immediately."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + JB_TEST_BEGIN("jitterbuffer_resynch_control"); + + if (!(jb = jb_new())) { + ast_test_status_update(test, "Failed to allocate memory for jitterbuffer\n"); + goto cleanup; + } + + test_jb_populate_config(&jbconf); + jbconf.resync_threshold = 200; + if (jb_setconf(jb, &jbconf) != JB_OK) { + ast_test_status_update(test, "Failed to set jitterbuffer configuration\n"); + goto cleanup; + } + + test_jb_resynch_frame_insertion(jb, JB_TYPE_CONTROL); + + for (i = 0; i <= 40; i++) { + if (jb_get(jb, &frame, i * 20 + 5, DEFAULT_CODEC_INTERP_LEN) == JB_INTERP) { + ++interpolated_frames; + } + } + + if (jb_getinfo(jb, &jbinfo) != JB_OK) { + ast_test_status_update(test, "Failed to get jitterbuffer information\n"); + goto cleanup; + } + /* With control frames, a resync happens automatically */ + JB_INFO_PRINT_FRAME_DEBUG(jbinfo); + JB_NUMERIC_TEST(jbinfo.frames_dropped, 0); + JB_NUMERIC_TEST(jbinfo.frames_out, 40); + JB_NUMERIC_TEST(jbinfo.frames_in, 40); + /* Verify that each of the interpolated frames is counted */ + JB_NUMERIC_TEST(jbinfo.frames_lost, interpolated_frames); + JB_NUMERIC_TEST(jbinfo.frames_late, 0); + JB_NUMERIC_TEST(jbinfo.frames_ooo, 0); + + result = AST_TEST_PASS; + +cleanup: + if (jb) { + /* No need to do anything - this will put all frames on the 'free' list, + * so jb_destroy will dispose of them */ + while (jb_getall(jb, &frame) == JB_OK) { } + jb_destroy(jb); + } + + JB_TEST_END; + + return result; +} + +AST_TEST_DEFINE(jitterbuffer_resynch_voice) +{ + enum ast_test_result_state result = AST_TEST_FAIL; + struct jitterbuf *jb = NULL; + struct jb_frame frame; + struct jb_info jbinfo; + struct jb_conf jbconf; + int interpolated_frames = 0; + int i; + + switch (cmd) { + case TEST_INIT: + info->name = "jitterbuffer_resynch_voice"; + info->category = "/main/jitterbuf/"; + info->summary = "Tests sending voice frames that force a resynch"; + info->description = "Voice frames are sent to a jitter buffer. After some " + "number of frames, the source timestamps jump, forcing a resync of " + "the jitter buffer. Since the frames are voice, the resync happens " + "after observing three packets that break the resync threshold."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + JB_TEST_BEGIN("jitterbuffer_resynch_voice"); + + if (!(jb = jb_new())) { + ast_test_status_update(test, "Failed to allocate memory for jitterbuffer\n"); + goto cleanup; + } + + test_jb_populate_config(&jbconf); + jbconf.resync_threshold = 200; + if (jb_setconf(jb, &jbconf) != JB_OK) { + ast_test_status_update(test, "Failed to set jitterbuffer configuration\n"); + goto cleanup; + } + + test_jb_resynch_frame_insertion(jb, JB_TYPE_VOICE); + + for (i = 0; i <= 40; i++) { + if (jb_get(jb, &frame, i * 20 + 5, DEFAULT_CODEC_INTERP_LEN) == JB_INTERP) { + ++interpolated_frames; + } + } + + if (jb_getinfo(jb, &jbinfo) != JB_OK) { + ast_test_status_update(test, "Failed to get jitterbuffer information\n"); + goto cleanup; + } + /* The first three packets before the resync should be dropped */ + JB_INFO_PRINT_FRAME_DEBUG(jbinfo); + JB_NUMERIC_TEST(jbinfo.frames_dropped, 3); + JB_NUMERIC_TEST(jbinfo.frames_out, 37); + JB_NUMERIC_TEST(jbinfo.frames_in, 37); + /* Verify that each of the interpolated frames is counted */ + JB_NUMERIC_TEST(jbinfo.frames_lost, interpolated_frames); + JB_NUMERIC_TEST(jbinfo.frames_late, 0); + JB_NUMERIC_TEST(jbinfo.frames_ooo, 0); + + + result = AST_TEST_PASS; + +cleanup: + if (jb) { + /* No need to do anything - this will put all frames on the 'free' list, + * so jb_destroy will dispose of them */ + while (jb_getall(jb, &frame) == JB_OK) { } + jb_destroy(jb); + } + + JB_TEST_END; + + return result; +} + +static int unload_module(void) +{ + AST_TEST_UNREGISTER(jitterbuffer_nominal_voice_frames); + AST_TEST_UNREGISTER(jitterbuffer_nominal_control_frames); + AST_TEST_UNREGISTER(jitterbuffer_out_of_order_voice); + AST_TEST_UNREGISTER(jitterbuffer_out_of_order_control); + AST_TEST_UNREGISTER(jitterbuffer_lost_voice); + AST_TEST_UNREGISTER(jitterbuffer_lost_control); + AST_TEST_UNREGISTER(jitterbuffer_late_voice); + AST_TEST_UNREGISTER(jitterbuffer_late_control); + AST_TEST_UNREGISTER(jitterbuffer_overflow_voice); + AST_TEST_UNREGISTER(jitterbuffer_overflow_control); + AST_TEST_UNREGISTER(jitterbuffer_resynch_voice); + AST_TEST_UNREGISTER(jitterbuffer_resynch_control); + return 0; +} + +static int load_module(void) +{ + /* Nominal - put / get frames */ + AST_TEST_REGISTER(jitterbuffer_nominal_voice_frames); + AST_TEST_REGISTER(jitterbuffer_nominal_control_frames); + + /* Out of order frame arrival */ + AST_TEST_REGISTER(jitterbuffer_out_of_order_voice); + AST_TEST_REGISTER(jitterbuffer_out_of_order_control); + + /* Lost frame arrival */ + AST_TEST_REGISTER(jitterbuffer_lost_voice); + AST_TEST_REGISTER(jitterbuffer_lost_control); + + /* Late frame arrival */ + AST_TEST_REGISTER(jitterbuffer_late_voice); + AST_TEST_REGISTER(jitterbuffer_late_control); + + /* Buffer overflow */ + AST_TEST_REGISTER(jitterbuffer_overflow_voice); + AST_TEST_REGISTER(jitterbuffer_overflow_control); + + /* Buffer resynch */ + AST_TEST_REGISTER(jitterbuffer_resynch_voice); + AST_TEST_REGISTER(jitterbuffer_resynch_control); + + return AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Jitter Buffer Tests"); |