----------------- This commit reinstates MES with some casting fixes to the functions in time.h that convert between doubles and timeval structures. The casting issues were causing incorrect timestamps to be calculated which caused transcoding from/to G722 to produce bad or no audio. ASTERISK-30391 ----------------- This module has been updated to provide additional quality statistics in the form of an Asterisk Media Experience Score. The score is avilable using the same mechanisms you'd use to retrieve jitter, loss, and rtt statistics. For more information about the score and how to retrieve it, see https://wiki.asterisk.org/wiki/display/AST/Media+Experience+Score * Updated chan_pjsip to set quality channel variables when a call ends. * Updated channels/pjsip/dialplan_functions.c to add the ability to retrieve the MES along with the existing rtcp stats when using the CHANNEL dialplan function. * Added the ast_debug_rtp_is_allowed and ast_debug_rtcp_is_allowed checks for debugging purposes. * Added several function to time.h for manipulating time-in-samples and times represented as double seconds. * Updated rtp_engine.c to pass through the MES when stats are requested. Also debug output that dumps the stats when an rtp instance is destroyed. * Updated res_rtp_asterisk.c to implement the calculation of the MES. In the process, also had to update the calculation of jitter. Many debugging statements were also changed to be more informative. * Added a unit test for internal testing. The test should not be run during normal operation and is disabled by default. Change-Id: I4fce265965e68c3fdfeca55e614371ee69c65038
720 lines
22 KiB
C
720 lines
22 KiB
C
/*
|
|
* Asterisk -- An open source telephony toolkit.
|
|
*
|
|
* Copyright (C) 2019, Sangoma, Inc.
|
|
*
|
|
* Ben Ford <bford@sangoma.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 RTP/RTCP Unit Tests
|
|
*
|
|
* \author Ben Ford <bford@digium.com>
|
|
*
|
|
*/
|
|
|
|
/*** MODULEINFO
|
|
<depend>TEST_FRAMEWORK</depend>
|
|
<support_level>core</support_level>
|
|
***/
|
|
|
|
#include "asterisk.h"
|
|
|
|
#include "asterisk/module.h"
|
|
#include "asterisk/test.h"
|
|
#include "asterisk/rtp_engine.h"
|
|
#include "asterisk/data_buffer.h"
|
|
#include "asterisk/format_cache.h"
|
|
#include <assert.h>
|
|
#include <sched.h>
|
|
|
|
enum test_type {
|
|
TEST_TYPE_NONE = 0, /* No special setup required */
|
|
TEST_TYPE_NACK, /* Enable NACK */
|
|
TEST_TYPE_REMB, /* Enable REMB */
|
|
TEST_TYPE_STD_RTCP, /* Let the stack do RTCP */
|
|
};
|
|
|
|
static void ast_sched_context_destroy_wrapper(struct ast_sched_context *sched)
|
|
{
|
|
if (sched) {
|
|
ast_sched_context_destroy(sched);
|
|
}
|
|
}
|
|
|
|
static int test_init_rtp_instances(struct ast_rtp_instance **instance1,
|
|
struct ast_rtp_instance **instance2, struct ast_sched_context *test_sched,
|
|
enum test_type type)
|
|
{
|
|
struct ast_sockaddr addr1;
|
|
struct ast_sockaddr addr2;
|
|
enum ast_rtp_instance_rtcp rtcp_type = AST_RTP_INSTANCE_RTCP_MUX;
|
|
|
|
ast_sockaddr_parse(&addr1, "127.0.0.1", 0);
|
|
ast_sockaddr_parse(&addr2, "127.0.0.1", 0);
|
|
|
|
*instance1 = ast_rtp_instance_new("asterisk", test_sched, &addr1, "instance1");
|
|
*instance2 = ast_rtp_instance_new("asterisk", test_sched, &addr2, "instance2");
|
|
if (!instance1 || !instance2) {
|
|
return -1;
|
|
}
|
|
|
|
ast_rtp_instance_set_channel_id(*instance1, "instance1");
|
|
ast_rtp_instance_set_channel_id(*instance2, "instance2");
|
|
|
|
if (type == TEST_TYPE_STD_RTCP) {
|
|
rtcp_type = AST_RTP_INSTANCE_RTCP_STANDARD;
|
|
}
|
|
|
|
ast_rtp_instance_set_prop(*instance1,
|
|
AST_RTP_PROPERTY_RTCP, rtcp_type);
|
|
ast_rtp_instance_set_prop(*instance2,
|
|
AST_RTP_PROPERTY_RTCP, rtcp_type);
|
|
|
|
if (type == TEST_TYPE_NACK) {
|
|
ast_rtp_instance_set_prop(*instance1, AST_RTP_PROPERTY_RETRANS_RECV, 1);
|
|
ast_rtp_instance_set_prop(*instance1, AST_RTP_PROPERTY_RETRANS_SEND, 1);
|
|
ast_rtp_instance_set_prop(*instance2, AST_RTP_PROPERTY_RETRANS_RECV, 2);
|
|
ast_rtp_instance_set_prop(*instance2, AST_RTP_PROPERTY_RETRANS_SEND, 2);
|
|
} else if (type == TEST_TYPE_REMB) {
|
|
ast_rtp_instance_set_prop(*instance1, AST_RTP_PROPERTY_REMB, 1);
|
|
ast_rtp_instance_set_prop(*instance2, AST_RTP_PROPERTY_REMB, 1);
|
|
}
|
|
|
|
ast_rtp_instance_get_local_address(*instance1, &addr1);
|
|
ast_rtp_instance_set_remote_address(*instance2, &addr1);
|
|
|
|
ast_rtp_instance_get_local_address(*instance2, &addr2);
|
|
ast_rtp_instance_set_remote_address(*instance1, &addr2);
|
|
|
|
ast_rtp_instance_reset_test_engine(*instance1);
|
|
|
|
ast_rtp_instance_activate(*instance1);
|
|
ast_rtp_instance_activate(*instance2);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void test_write_frames(struct ast_rtp_instance *instance, int seqno, int num)
|
|
{
|
|
char data[320] = "";
|
|
struct ast_frame frame_out = {
|
|
.frametype = AST_FRAME_VOICE,
|
|
.subclass.format = ast_format_ulaw,
|
|
.data.ptr = data,
|
|
.datalen = 160,
|
|
};
|
|
int index;
|
|
|
|
ast_set_flag(&frame_out, AST_FRFLAG_HAS_SEQUENCE_NUMBER);
|
|
|
|
for (index = 0; index < num; index++) {
|
|
frame_out.seqno = seqno + index;
|
|
ast_rtp_instance_write(instance, &frame_out);
|
|
}
|
|
}
|
|
|
|
static void test_read_frames(struct ast_rtp_instance *instance, int num)
|
|
{
|
|
struct ast_frame *frame_in;
|
|
int index;
|
|
|
|
for (index = 0; index < num; index++) {
|
|
frame_in = ast_rtp_instance_read(instance, 0);
|
|
if (frame_in) {
|
|
ast_frfree(frame_in);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void test_write_and_read_frames(struct ast_rtp_instance *instance1,
|
|
struct ast_rtp_instance *instance2, int seqno, int num)
|
|
{
|
|
test_write_frames(instance1, seqno, num);
|
|
test_read_frames(instance2, num);
|
|
}
|
|
|
|
|
|
/*
|
|
* Unfortunately, we can't use usleep() to create
|
|
* packet spacing because there are signals in use
|
|
* which cause usleep to immediately return. Instead
|
|
* we have to spin. :(
|
|
*/
|
|
static void SLEEP_SPINNER(int ms)
|
|
{
|
|
struct timeval a = ast_tvnow();
|
|
|
|
while(1) {
|
|
sched_yield();
|
|
if (ast_remaining_ms(a, ms) <= 0) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function is NOT really a reliable implementation.
|
|
* Its purpose is only to aid in code development in res_rtp_asterisk.
|
|
*/
|
|
static void test_write_and_read_interleaved_frames(struct ast_rtp_instance *instance1,
|
|
struct ast_rtp_instance *instance2, int howlong, int rtcp_interval)
|
|
{
|
|
char data[320] = "";
|
|
int pktinterval = 20;
|
|
|
|
struct ast_frame frame_out1 = {
|
|
.frametype = AST_FRAME_VOICE,
|
|
.subclass.format = ast_format_ulaw,
|
|
.seqno = 4556,
|
|
.data.ptr = data,
|
|
.datalen = 160,
|
|
.samples = 1,
|
|
.len = pktinterval,
|
|
.ts = 4622295,
|
|
};
|
|
struct ast_frame frame_out2 = {
|
|
.frametype = AST_FRAME_VOICE,
|
|
.subclass.format = ast_format_ulaw,
|
|
.seqno = 6554,
|
|
.data.ptr = data,
|
|
.datalen = 160,
|
|
.samples = 1,
|
|
.len = pktinterval,
|
|
.ts = 8622295,
|
|
};
|
|
struct ast_frame *frame_in1;
|
|
struct ast_frame *frame_in2;
|
|
int index;
|
|
int num;
|
|
int rtcpnum;
|
|
int reverse = 1;
|
|
int send_rtcp = 0;
|
|
|
|
num = howlong / pktinterval;
|
|
|
|
rtcpnum = rtcp_interval / pktinterval;
|
|
|
|
ast_set_flag(&frame_out1, AST_FRFLAG_HAS_SEQUENCE_NUMBER);
|
|
ast_set_flag(&frame_out1, AST_FRFLAG_HAS_TIMING_INFO);
|
|
ast_set_flag(&frame_out2, AST_FRFLAG_HAS_SEQUENCE_NUMBER);
|
|
ast_set_flag(&frame_out2, AST_FRFLAG_HAS_TIMING_INFO);
|
|
|
|
for (index = 0; index < num; index++) {
|
|
struct timeval start = ast_tvnow();
|
|
time_t ms;
|
|
|
|
if (index == 1) {
|
|
ast_clear_flag(&frame_out1, AST_FRFLAG_HAS_SEQUENCE_NUMBER);
|
|
ast_clear_flag(&frame_out1, AST_FRFLAG_HAS_TIMING_INFO);
|
|
ast_clear_flag(&frame_out2, AST_FRFLAG_HAS_SEQUENCE_NUMBER);
|
|
ast_clear_flag(&frame_out2, AST_FRFLAG_HAS_TIMING_INFO);
|
|
}
|
|
frame_out1.seqno += index;
|
|
frame_out1.delivery = start;
|
|
frame_out1.ts += frame_out1.len;
|
|
ast_rtp_instance_write(instance1, &frame_out1);
|
|
|
|
if (send_rtcp && index && (index % rtcpnum == 0)) {
|
|
ast_rtp_instance_queue_report(instance1);
|
|
}
|
|
|
|
frame_in2 = ast_rtp_instance_read(instance2, 0);
|
|
ast_frfree(frame_in2);
|
|
frame_in2 = ast_rtp_instance_read(instance2, 1);
|
|
ast_frfree(frame_in2);
|
|
|
|
if (reverse) {
|
|
frame_out2.seqno += index;
|
|
frame_out2.delivery = ast_tvnow();
|
|
frame_out2.ts += frame_out2.len;
|
|
ast_rtp_instance_write(instance2, &frame_out2);
|
|
|
|
if (send_rtcp && index && (index % rtcpnum == 0)) {
|
|
ast_rtp_instance_queue_report(instance2);
|
|
}
|
|
|
|
frame_in1 = ast_rtp_instance_read(instance1, 0);
|
|
ast_frfree(frame_in1);
|
|
frame_in1 = ast_rtp_instance_read(instance1, 1);
|
|
ast_frfree(frame_in1);
|
|
|
|
}
|
|
|
|
ms = frame_out1.len - ast_tvdiff_ms(ast_tvnow(),start);
|
|
ms += (index % 2 ? 5 : 12);
|
|
ms += (index % 3 ? 2 : 30);
|
|
SLEEP_SPINNER(ms);
|
|
}
|
|
}
|
|
|
|
AST_TEST_DEFINE(nack_no_packet_loss)
|
|
{
|
|
RAII_VAR(struct ast_rtp_instance *, instance1, NULL, ast_rtp_instance_destroy);
|
|
RAII_VAR(struct ast_rtp_instance *, instance2, NULL, ast_rtp_instance_destroy);
|
|
RAII_VAR(struct ast_sched_context *, test_sched, NULL, ast_sched_context_destroy_wrapper);
|
|
|
|
switch (cmd) {
|
|
case TEST_INIT:
|
|
info->name = "nack_no_packet_loss";
|
|
info->category = "/res/res_rtp/";
|
|
info->summary = "nack no packet loss unit test";
|
|
info->description =
|
|
"Tests sending packets with no packet loss and "
|
|
"validates that the send buffer stores sent packets "
|
|
"and the receive buffer is empty";
|
|
return AST_TEST_NOT_RUN;
|
|
case TEST_EXECUTE:
|
|
break;
|
|
}
|
|
|
|
test_sched = ast_sched_context_create();
|
|
|
|
if ((test_init_rtp_instances(&instance1, &instance2, test_sched, TEST_TYPE_NACK)) < 0) {
|
|
ast_log(LOG_ERROR, "Failed to initialize test!\n");
|
|
return AST_TEST_FAIL;
|
|
}
|
|
|
|
test_write_and_read_frames(instance1, instance2, 1000, 10);
|
|
|
|
ast_test_validate(test, ast_rtp_instance_get_send_buffer_count(instance1) == 10,
|
|
"Send buffer did not have the expected count of 10");
|
|
|
|
ast_test_validate(test, ast_rtp_instance_get_recv_buffer_count(instance2) == 0,
|
|
"Receive buffer did not have the expected count of 0");
|
|
|
|
return AST_TEST_PASS;
|
|
}
|
|
|
|
AST_TEST_DEFINE(nack_nominal)
|
|
{
|
|
RAII_VAR(struct ast_rtp_instance *, instance1, NULL, ast_rtp_instance_destroy);
|
|
RAII_VAR(struct ast_rtp_instance *, instance2, NULL, ast_rtp_instance_destroy);
|
|
RAII_VAR(struct ast_sched_context *, test_sched, NULL, ast_sched_context_destroy_wrapper);
|
|
|
|
switch (cmd) {
|
|
case TEST_INIT:
|
|
info->name = "nack_nominal";
|
|
info->category = "/res/res_rtp/";
|
|
info->summary = "nack nominal unit test";
|
|
info->description =
|
|
"Tests sending packets with some packet loss and "
|
|
"validates that a NACK request is sent on reaching "
|
|
"the triggering amount of lost packets";
|
|
return AST_TEST_NOT_RUN;
|
|
case TEST_EXECUTE:
|
|
break;
|
|
}
|
|
|
|
test_sched = ast_sched_context_create();
|
|
|
|
if ((test_init_rtp_instances(&instance1, &instance2, test_sched, TEST_TYPE_NACK)) < 0) {
|
|
ast_log(LOG_ERROR, "Failed to initialize test!\n");
|
|
return AST_TEST_FAIL;
|
|
}
|
|
|
|
/* Start normally */
|
|
test_write_and_read_frames(instance1, instance2, 1000, 10);
|
|
|
|
/* Set the number of packets to drop when we send them next */
|
|
ast_rtp_instance_drop_packets(instance2, 10);
|
|
test_write_and_read_frames(instance1, instance2, 1010, 10);
|
|
|
|
/* Send enough packets to reach the NACK trigger */
|
|
test_write_and_read_frames(instance1, instance2, 1020, ast_rtp_instance_get_recv_buffer_max(instance2) / 2);
|
|
|
|
/* This needs to be read as RTCP */
|
|
test_read_frames(instance1, 1);
|
|
|
|
/* We should have the missing packets to read now */
|
|
test_read_frames(instance2, 10);
|
|
|
|
ast_test_validate(test, ast_rtp_instance_get_recv_buffer_count(instance2) == 0,
|
|
"Receive buffer did not have the expected count of 0");
|
|
|
|
return AST_TEST_PASS;
|
|
}
|
|
|
|
AST_TEST_DEFINE(nack_overflow)
|
|
{
|
|
RAII_VAR(struct ast_rtp_instance *, instance1, NULL, ast_rtp_instance_destroy);
|
|
RAII_VAR(struct ast_rtp_instance *, instance2, NULL, ast_rtp_instance_destroy);
|
|
RAII_VAR(struct ast_sched_context *, test_sched, NULL, ast_sched_context_destroy_wrapper);
|
|
int max_packets;
|
|
|
|
switch (cmd) {
|
|
case TEST_INIT:
|
|
info->name = "nack_overflow";
|
|
info->category = "/res/res_rtp/";
|
|
info->summary = "nack overflow unit test";
|
|
info->description =
|
|
"Tests that when the buffer hits its capacity, we "
|
|
"queue all the packets we currently have stored";
|
|
return AST_TEST_NOT_RUN;
|
|
case TEST_EXECUTE:
|
|
break;
|
|
}
|
|
|
|
test_sched = ast_sched_context_create();
|
|
|
|
if ((test_init_rtp_instances(&instance1, &instance2, test_sched, TEST_TYPE_NACK)) < 0) {
|
|
ast_log(LOG_ERROR, "Failed to initialize test!\n");
|
|
return AST_TEST_FAIL;
|
|
}
|
|
|
|
/* Start normally */
|
|
test_write_and_read_frames(instance1, instance2, 1000, 10);
|
|
|
|
/* Send enough packets to fill the buffer */
|
|
max_packets = ast_rtp_instance_get_recv_buffer_max(instance2);
|
|
test_write_and_read_frames(instance1, instance2, 1020, max_packets);
|
|
|
|
ast_test_validate(test, ast_rtp_instance_get_recv_buffer_count(instance2) == max_packets,
|
|
"Receive buffer did not have the expected count of max buffer size");
|
|
|
|
/* Send the packet that will overflow the buffer */
|
|
test_write_and_read_frames(instance1, instance2, 1020 + max_packets, 1);
|
|
|
|
ast_test_validate(test, ast_rtp_instance_get_recv_buffer_count(instance2) == 0,
|
|
"Receive buffer did not have the expected count of 0");
|
|
|
|
return AST_TEST_PASS;
|
|
}
|
|
|
|
AST_TEST_DEFINE(lost_packet_stats_nominal)
|
|
{
|
|
RAII_VAR(struct ast_rtp_instance *, instance1, NULL, ast_rtp_instance_destroy);
|
|
RAII_VAR(struct ast_rtp_instance *, instance2, NULL, ast_rtp_instance_destroy);
|
|
RAII_VAR(struct ast_sched_context *, test_sched, NULL, ast_sched_context_destroy_wrapper);
|
|
struct ast_rtp_instance_stats stats = { 0, };
|
|
enum ast_rtp_instance_stat stat = AST_RTP_INSTANCE_STAT_ALL;
|
|
|
|
switch (cmd) {
|
|
case TEST_INIT:
|
|
info->name = "lost_packet_stats_nominal";
|
|
info->category = "/res/res_rtp/";
|
|
info->summary = "lost packet stats nominal unit test";
|
|
info->description =
|
|
"Tests that when some packets are lost, we calculate that "
|
|
"loss correctly when doing lost packet statistics";
|
|
return AST_TEST_NOT_RUN;
|
|
case TEST_EXECUTE:
|
|
break;
|
|
}
|
|
|
|
test_sched = ast_sched_context_create();
|
|
|
|
if ((test_init_rtp_instances(&instance1, &instance2, test_sched, TEST_TYPE_NONE)) < 0) {
|
|
ast_log(LOG_ERROR, "Failed to initialize test!\n");
|
|
return AST_TEST_FAIL;
|
|
}
|
|
|
|
/* Start normally */
|
|
test_write_and_read_frames(instance1, instance2, 1000, 10);
|
|
|
|
/* Send some more packets, but with a gap */
|
|
test_write_and_read_frames(instance1, instance2, 1015, 5);
|
|
|
|
/* Send a RR to calculate lost packet statistics. We should be missing 5 packets */
|
|
ast_rtp_instance_queue_report(instance1);
|
|
test_write_frames(instance2, 1000, 1);
|
|
|
|
/* Check RTCP stats to see if we got the expected packet loss count */
|
|
ast_rtp_instance_get_stats(instance2, &stats, stat);
|
|
ast_test_validate(test, stats.rxploss == 5 && stats.local_minrxploss == 5 &&
|
|
stats.local_maxrxploss == 5, "Condition of 5 lost packets was not met");
|
|
|
|
/* Drop 3 before writing 5 more */
|
|
test_write_and_read_frames(instance1, instance2, 1023, 5);
|
|
|
|
ast_rtp_instance_queue_report(instance1);
|
|
test_write_frames(instance2, 1001, 1);
|
|
ast_rtp_instance_get_stats(instance2, &stats, stat);
|
|
|
|
/* Should now be missing 8 total packets with a change in min */
|
|
ast_test_validate(test, stats.rxploss == 8 && stats.local_minrxploss == 3 &&
|
|
stats.local_maxrxploss == 5);
|
|
|
|
/* Write 5 more with no gaps */
|
|
test_write_and_read_frames(instance1, instance2, 1028, 5);
|
|
|
|
ast_rtp_instance_queue_report(instance1);
|
|
test_write_frames(instance2, 1002, 1);
|
|
ast_rtp_instance_get_stats(instance2, &stats, stat);
|
|
|
|
/* Should still only be missing 8 total packets */
|
|
ast_test_validate(test, stats.rxploss == 8 && stats.local_minrxploss == 3 &&
|
|
stats.local_maxrxploss == 5);
|
|
|
|
/* Now drop 1, write another 5, drop 8, and then write 5 */
|
|
test_write_and_read_frames(instance1, instance2, 1034, 5);
|
|
test_write_and_read_frames(instance1, instance2, 1047, 5);
|
|
|
|
ast_rtp_instance_queue_report(instance1);
|
|
test_write_frames(instance2, 1003, 1);
|
|
ast_rtp_instance_get_stats(instance2, &stats, stat);
|
|
|
|
/* Now it should be missing 17 total packets, with a change in max */
|
|
ast_test_validate(test, stats.rxploss == 17 && stats.local_minrxploss == 3 &&
|
|
stats.local_maxrxploss == 9);
|
|
|
|
return AST_TEST_PASS;
|
|
}
|
|
|
|
AST_TEST_DEFINE(remb_nominal)
|
|
{
|
|
RAII_VAR(struct ast_rtp_instance *, instance1, NULL, ast_rtp_instance_destroy);
|
|
RAII_VAR(struct ast_rtp_instance *, instance2, NULL, ast_rtp_instance_destroy);
|
|
RAII_VAR(struct ast_sched_context *, test_sched, NULL, ast_sched_context_destroy_wrapper);
|
|
RAII_VAR(struct ast_frame *, frame_in, NULL, ast_frfree);
|
|
/* Use the structure softmix_remb_collector uses to store information for REMB */
|
|
struct ast_rtp_rtcp_feedback feedback = {
|
|
.fmt = AST_RTP_RTCP_FMT_REMB,
|
|
.remb.br_exp = 0,
|
|
.remb.br_mantissa = 1000,
|
|
};
|
|
struct ast_frame frame_out = {
|
|
.frametype = AST_FRAME_RTCP,
|
|
.subclass.integer = AST_RTP_RTCP_PSFB,
|
|
.data.ptr = &feedback,
|
|
.datalen = sizeof(feedback),
|
|
};
|
|
struct ast_rtp_rtcp_feedback *received_feedback;
|
|
|
|
switch (cmd) {
|
|
case TEST_INIT:
|
|
info->name = "remb_nominal";
|
|
info->category = "/res/res_rtp/";
|
|
info->summary = "remb nominal unit test";
|
|
info->description =
|
|
"Tests sending and receiving a REMB packet";
|
|
return AST_TEST_NOT_RUN;
|
|
case TEST_EXECUTE:
|
|
break;
|
|
}
|
|
|
|
test_sched = ast_sched_context_create();
|
|
|
|
if ((test_init_rtp_instances(&instance1, &instance2, test_sched, TEST_TYPE_REMB)) < 0) {
|
|
ast_log(LOG_ERROR, "Failed to initialize test!\n");
|
|
return AST_TEST_FAIL;
|
|
}
|
|
|
|
/* The schedid must be 0 or greater, so let's do that now */
|
|
ast_rtp_instance_set_schedid(instance1, 0);
|
|
|
|
ast_rtp_instance_write(instance1, &frame_out);
|
|
|
|
/* Verify the high level aspects of the frame */
|
|
frame_in = ast_rtp_instance_read(instance2, 0);
|
|
ast_test_validate(test, frame_in != NULL, "Did not receive a REMB frame");
|
|
ast_test_validate(test, frame_in->frametype == AST_FRAME_RTCP,
|
|
"REMB frame did not have the expected frametype");
|
|
ast_test_validate(test, frame_in->subclass.integer == AST_RTP_RTCP_PSFB,
|
|
"REMB frame did not have the expected subclass integer");
|
|
|
|
/* Verify the actual REMB information itself */
|
|
received_feedback = frame_in->data.ptr;
|
|
ast_test_validate(test, received_feedback->fmt == AST_RTP_RTCP_FMT_REMB,
|
|
"REMB frame did not have the expected feedback format");
|
|
ast_test_validate(test, received_feedback->remb.br_exp == feedback.remb.br_exp,
|
|
"REMB received exponent did not match sent exponent");
|
|
ast_test_validate(test, received_feedback->remb.br_mantissa == feedback.remb.br_mantissa,
|
|
"REMB received mantissa did not match sent mantissa");
|
|
|
|
return AST_TEST_PASS;
|
|
}
|
|
|
|
AST_TEST_DEFINE(sr_rr_nominal)
|
|
{
|
|
RAII_VAR(struct ast_rtp_instance *, instance1, NULL, ast_rtp_instance_destroy);
|
|
RAII_VAR(struct ast_rtp_instance *, instance2, NULL, ast_rtp_instance_destroy);
|
|
RAII_VAR(struct ast_sched_context *, test_sched, NULL, ast_sched_context_destroy_wrapper);
|
|
RAII_VAR(struct ast_frame *, frame_in, NULL, ast_frfree);
|
|
|
|
switch (cmd) {
|
|
case TEST_INIT:
|
|
info->name = "sr_rr_nominal";
|
|
info->category = "/res/res_rtp/";
|
|
info->summary = "SR/RR nominal unit test";
|
|
info->description =
|
|
"Tests sending SR/RR and receiving it; includes SDES";
|
|
return AST_TEST_NOT_RUN;
|
|
case TEST_EXECUTE:
|
|
break;
|
|
}
|
|
|
|
test_sched = ast_sched_context_create();
|
|
|
|
if ((test_init_rtp_instances(&instance1, &instance2, test_sched, TEST_TYPE_NONE)) < 0) {
|
|
ast_log(LOG_ERROR, "Failed to initialize test!\n");
|
|
return AST_TEST_FAIL;
|
|
}
|
|
|
|
test_write_and_read_frames(instance1, instance2, 1000, 10);
|
|
|
|
/*
|
|
* Set the send_report flag so we send a sender report instead of normal RTP. We
|
|
* also need to ensure that SDES processed.
|
|
*/
|
|
ast_rtp_instance_queue_report(instance1);
|
|
test_write_frames(instance1, 1010, 1);
|
|
|
|
frame_in = ast_rtp_instance_read(instance2, 0);
|
|
ast_test_validate(test, frame_in->frametype == AST_FRAME_RTCP,
|
|
"Sender report frame did not have the expected frametype");
|
|
ast_test_validate(test, frame_in->subclass.integer == AST_RTP_RTCP_SR,
|
|
"Sender report frame did not have the expected subclass integer");
|
|
ast_test_validate(test, ast_rtp_instance_get_sdes_received(instance2) == 1,
|
|
"SDES was never processed for sender report");
|
|
|
|
ast_frfree(frame_in);
|
|
|
|
/* Set the send_report flag so we send a receiver report instead of normal RTP */
|
|
ast_rtp_instance_queue_report(instance1);
|
|
test_write_frames(instance1, 1010, 1);
|
|
|
|
frame_in = ast_rtp_instance_read(instance2, 0);
|
|
ast_test_validate(test, frame_in->frametype == AST_FRAME_RTCP,
|
|
"Receiver report frame did not have the expected frametype");
|
|
ast_test_validate(test, frame_in->subclass.integer == AST_RTP_RTCP_RR,
|
|
"Receiver report frame did not have the expected subclass integer");
|
|
|
|
return AST_TEST_PASS;
|
|
}
|
|
|
|
AST_TEST_DEFINE(fir_nominal)
|
|
{
|
|
RAII_VAR(struct ast_rtp_instance *, instance1, NULL, ast_rtp_instance_destroy);
|
|
RAII_VAR(struct ast_rtp_instance *, instance2, NULL, ast_rtp_instance_destroy);
|
|
RAII_VAR(struct ast_sched_context *, test_sched, NULL, ast_sched_context_destroy_wrapper);
|
|
RAII_VAR(struct ast_frame *, frame_in, NULL, ast_frfree);
|
|
struct ast_frame frame_out = {
|
|
.frametype = AST_FRAME_CONTROL,
|
|
.subclass.integer = AST_CONTROL_VIDUPDATE,
|
|
};
|
|
|
|
switch (cmd) {
|
|
case TEST_INIT:
|
|
info->name = "fir_nominal";
|
|
info->category = "/res/res_rtp/";
|
|
info->summary = "fir nominal unit test";
|
|
info->description =
|
|
"Tests sending and receiving a FIR packet";
|
|
return AST_TEST_NOT_RUN;
|
|
case TEST_EXECUTE:
|
|
break;
|
|
}
|
|
|
|
test_sched = ast_sched_context_create();
|
|
|
|
if ((test_init_rtp_instances(&instance1, &instance2, test_sched, TEST_TYPE_NONE)) < 0) {
|
|
ast_log(LOG_ERROR, "Failed to initialize test!\n");
|
|
return AST_TEST_FAIL;
|
|
}
|
|
|
|
/* Send some packets to learn SSRC */
|
|
test_write_and_read_frames(instance2, instance1, 1000, 10);
|
|
|
|
/* The schedid must be 0 or greater, so let's do that now */
|
|
ast_rtp_instance_set_schedid(instance1, 0);
|
|
|
|
/*
|
|
* This will not directly write a frame out, but cause Asterisk to see it as a FIR
|
|
* request, which will then trigger rtp_write_rtcp_fir, which will send out the
|
|
* appropriate packet.
|
|
*/
|
|
ast_rtp_instance_write(instance1, &frame_out);
|
|
|
|
/*
|
|
* We only receive one frame, the FIR request. It won't have a subclass integer of
|
|
* 206 (PSFB) because ast_rtcp_interpret sets it to 18 (AST_CONTROL_VIDUPDATE), so
|
|
* check for that.
|
|
*/
|
|
frame_in = ast_rtp_instance_read(instance2, 0);
|
|
ast_test_validate(test, frame_in != NULL, "Did not receive a FIR frame");
|
|
ast_test_validate(test, frame_in->frametype == AST_FRAME_CONTROL,
|
|
"FIR frame did not have the expected frametype");
|
|
ast_test_validate(test, frame_in->subclass.integer == AST_CONTROL_VIDUPDATE,
|
|
"FIR frame did not have the expected subclass integer");
|
|
|
|
return AST_TEST_PASS;
|
|
}
|
|
|
|
/*
|
|
* This test should not normally be run. Its only purpose is to
|
|
* aid in code development.
|
|
*/
|
|
AST_TEST_DEFINE(mes)
|
|
{
|
|
RAII_VAR(struct ast_rtp_instance *, instance1, NULL, ast_rtp_instance_destroy);
|
|
RAII_VAR(struct ast_rtp_instance *, instance2, NULL, ast_rtp_instance_destroy);
|
|
RAII_VAR(struct ast_sched_context *, test_sched, NULL, ast_sched_context_destroy_wrapper);
|
|
|
|
switch (cmd) {
|
|
case TEST_INIT:
|
|
info->name = "mes";
|
|
info->category = "/res/res_rtp/";
|
|
info->summary = "Media Experience Score";
|
|
info->description =
|
|
"Tests calculation of Media Experience Score (only run by explicit request)";
|
|
info->explicit_only = 1;
|
|
return AST_TEST_NOT_RUN;
|
|
case TEST_EXECUTE:
|
|
break;
|
|
}
|
|
|
|
test_sched = ast_sched_context_create();
|
|
ast_sched_start_thread(test_sched);
|
|
|
|
if ((test_init_rtp_instances(&instance1, &instance2,
|
|
test_sched, TEST_TYPE_NONE)) < 0) {
|
|
ast_log(LOG_ERROR, "Failed to initialize test!\n");
|
|
return AST_TEST_FAIL;
|
|
}
|
|
|
|
test_write_and_read_interleaved_frames(
|
|
instance1, instance2, 1000, 5000);
|
|
|
|
return AST_TEST_PASS;
|
|
}
|
|
|
|
static int unload_module(void)
|
|
{
|
|
AST_TEST_UNREGISTER(mes);
|
|
AST_TEST_UNREGISTER(nack_no_packet_loss);
|
|
AST_TEST_UNREGISTER(nack_nominal);
|
|
AST_TEST_UNREGISTER(nack_overflow);
|
|
AST_TEST_UNREGISTER(lost_packet_stats_nominal);
|
|
AST_TEST_UNREGISTER(remb_nominal);
|
|
AST_TEST_UNREGISTER(sr_rr_nominal);
|
|
AST_TEST_UNREGISTER(fir_nominal);
|
|
return 0;
|
|
}
|
|
|
|
static int load_module(void)
|
|
{
|
|
AST_TEST_REGISTER(nack_no_packet_loss);
|
|
AST_TEST_REGISTER(nack_nominal);
|
|
AST_TEST_REGISTER(nack_overflow);
|
|
AST_TEST_REGISTER(lost_packet_stats_nominal);
|
|
AST_TEST_REGISTER(remb_nominal);
|
|
AST_TEST_REGISTER(sr_rr_nominal);
|
|
AST_TEST_REGISTER(fir_nominal);
|
|
AST_TEST_REGISTER(mes);
|
|
return AST_MODULE_LOAD_SUCCESS;
|
|
}
|
|
|
|
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "RTP/RTCP test module");
|