congestiondetector.c 7.72 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
/*
 * The oRTP library is an RTP (Realtime Transport Protocol - rfc3550) implementation with additional features.
 * Copyright (C) 2017 Belledonne Communications SARL
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "congestiondetector.h"

#include <ortp/logging.h>
#include <math.h>

#include <ortp/rtpsession.h>

static const unsigned int congestion_pending_duration_ms = 5000;
28 29 30
static const float return_from_suspected_max_loss_rate = 5.0;
static const float absolute_congested_clock_ratio = 0.93f;
static const float relative_congested_clock_ratio = 0.96f;
31
static const float rls_forgetting_factor = 0.97f;
32

33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
const char *ortp_congestion_detector_state_to_string(OrtpCongestionState state){
	switch (state){
		case CongestionStateNormal:
			return "CongestionStateNormal";
		break;
		case CongestionStateSuspected:
			return "CongestionStatePending";
		break;
		case CongestionStateDetected:
			return "CongestionStateDetected";
		break;
		case CongestionStateResolving:
			return "CongestionStateResolving";
		break;
	}
	return "invalid state";
}
50

51 52 53 54 55 56 57
static bool_t ortp_congestion_detector_set_state(OrtpCongestionDetector *cd, OrtpCongestionState state){
	bool_t binary_state_changed = FALSE;
	if (state == cd->state) return FALSE;
	ortp_message("OrtpCongestionDetector: moving from state %s to state %s", 
		     ortp_congestion_detector_state_to_string(cd->state),
		     ortp_congestion_detector_state_to_string(state));
	cd->state = state;
58
	cd->too_much_loss = FALSE;
59 60 61 62 63 64 65 66 67 68 69
	if (state == CongestionStateDetected){
		if (!cd->is_in_congestion){
			cd->is_in_congestion = TRUE;
			binary_state_changed = TRUE;
		}
	}else if (state == CongestionStateNormal){
		cd->start_ms = (uint64_t)-1;
		if (cd->is_in_congestion){
			cd->is_in_congestion = FALSE;
			binary_state_changed = TRUE;
		}
70
	}
71 72 73 74 75 76 77
	return binary_state_changed;
}

void ortp_congestion_detector_reset(OrtpCongestionDetector *cd) {
	cd->initialized = FALSE;
	cd->skip = FALSE;
	ortp_congestion_detector_set_state(cd, CongestionStateNormal);
78 79 80 81 82
}

OrtpCongestionDetector * ortp_congestion_detector_new(RtpSession *session) {
	OrtpCongestionDetector *cd = (OrtpCongestionDetector*)ortp_malloc0(sizeof(OrtpCongestionDetector));
	cd->session = session;
83
	ortp_congestion_detector_reset(cd);
84 85 86
	return cd;
}

87 88 89 90 91 92
/*
static uint32_t local_ts_to_remote_ts_rls(double clock_ratio, double offset, uint32_t local_ts){
	return (uint32_t)( (int64_t)(clock_ratio*(double)local_ts) + (int64_t)offset);
}
*/

93
static float ortp_congestion_detector_get_loss_rate(OrtpCongestionDetector *cd){
94
	uint32_t cur_loss = (uint32_t)cd->session->stats.cum_packet_loss;
95 96 97 98
	uint32_t cur_seq = rtp_session_get_rcv_ext_seq_number(cd->session);
	uint32_t expected = cur_seq - cd->seq_begin;
	
	if (expected == 0) return 0;
99
	return 100.0f*(float)(cur_loss - cd->loss_begin) / (float)expected;
100 101
}

102
bool_t ortp_congestion_detector_record(OrtpCongestionDetector *cd, uint32_t packet_ts, uint32_t cur_str_ts) {
103
	bool_t binary_state_changed = FALSE;
104 105
	bool_t clock_drift;
	JitterControl *jitterctl = &cd->session->rtp.jittctl;
106
	//float deviation;
107

108 109 110 111 112
	if (cd->skip) return FALSE;
	
	packet_ts -= jitterctl->remote_ts_start;
	cur_str_ts -= jitterctl->local_ts_start;
	
113 114
	if (!cd->initialized) {
		cd->initialized = TRUE;
115
		ortp_kalman_rls_init(&cd->rls, 1, packet_ts - cur_str_ts);
116
		cd->rls.lambda = rls_forgetting_factor;
117 118 119 120
		if (jitterctl->params.buffer_algorithm != OrtpJitterBufferRecursiveLeastSquare){
			ortp_error("ortp congestion detection requires RLS jitter buffer algorithm.");
			cd->skip = TRUE;
		}
121 122 123 124
	}

	ortp_kalman_rls_record(&cd->rls, cur_str_ts, packet_ts);

125 126 127 128 129 130 131 132 133 134
	if (cd->rls.m < 0) {
		/*
		 * This can arrive when packets arrive in a very chaotic way during the first seconds of a call.
		 * There is no usable information as long as the rls hasn't yet converged.
		 */
		return binary_state_changed;
	}
	
	clock_drift = cd->rls.m < absolute_congested_clock_ratio || jitterctl->capped_clock_ratio < absolute_congested_clock_ratio
		|| cd->rls.m < relative_congested_clock_ratio * jitterctl->capped_clock_ratio ;
135 136 137
	//deviation = ((int32_t)(packet_ts - local_ts_to_remote_ts_rls(cd->rls.m, cd->rls.b, cur_str_ts))) / (float)jitterctl->clock_rate;
	//deviation = ortp_extremum_get_current(&jitterctl->max_ts_deviation)/(float)jitterctl->clock_rate;
	//has_jitter = deviation > acceptable_deviation;
138

139 140 141 142 143 144 145 146 147 148 149 150 151
	/*
	if (jitterctl->clock_rate == 90000){
		ortp_message(
			"OrtpCongestionDetector state=%s clock=%f"
			", jb->capped_clock_ratio=%f"
			", down_bw=%0.f, up_bw=%0.f kbits/s"
			, ortp_congestion_detector_state_to_string(cd->state)
			, cd->rls.m
			, jitterctl->capped_clock_ratio
			, rtp_session_get_recv_bandwidth_smooth(cd->session)*1e-3, rtp_session_get_send_bandwidth_smooth(cd->session)*1e-3
		);
	}
	*/
152 153 154 155 156

	switch (cd->state) {
		case CongestionStateNormal:
			if (clock_drift) {
				cd->start_ms = ortp_get_cur_time_ms();
157
				cd->loss_begin = (uint32_t)cd->session->stats.cum_packet_loss;
158
				cd->seq_begin = rtp_session_get_rcv_ext_seq_number(cd->session);
159
				cd->last_packet_recv = cd->start_ms;
160
				binary_state_changed = ortp_congestion_detector_set_state(cd, CongestionStateSuspected);
161
			}
162 163
		break;
		case CongestionStateSuspected:
164 165
		{
			uint64_t curtime = ortp_get_cur_time_ms();
166
			if (!clock_drift) {
167 168
				float loss_rate = ortp_congestion_detector_get_loss_rate(cd);
				if (loss_rate >= return_from_suspected_max_loss_rate){
169 170 171 172
					if (!cd->too_much_loss){
						ortp_message("OrtpCongestionDetector: loss rate is [%f], too much for returning to CongestionStateNormal state.", loss_rate);
						cd->too_much_loss = TRUE;
					}
173 174 175 176
				}else{
					// congestion has maybe stopped 
					binary_state_changed = ortp_congestion_detector_set_state(cd, CongestionStateNormal);
				}
177
			} else {
178 179 180 181 182 183 184 185 186 187 188 189
				
				if (curtime - cd->last_packet_recv >= 1000){
					/*no packet received during last second ! 
					 It means that the drift measure is not very significant, and futhermore the banwdith computation will be 
					 near to zero. It makes no sense to trigger a congestion detection in this case; the network is simply not working.
					 */
					binary_state_changed = ortp_congestion_detector_set_state(cd, CongestionStateNormal);
				}else{
					// congestion continues - if it has been for longer enough, trigger congestion flag
					if (curtime - cd->start_ms > congestion_pending_duration_ms) {
						binary_state_changed = ortp_congestion_detector_set_state(cd, CongestionStateDetected);
					}
190 191
				}
			}
192 193
			cd->last_packet_recv = curtime;
		}
194
		break;
195
		case CongestionStateDetected:
196 197 198 199 200 201 202 203 204 205 206 207 208
			if (!clock_drift) {
				// congestion is maybe terminated, go resolving state
				binary_state_changed = ortp_congestion_detector_set_state(cd, CongestionStateResolving);
				cd->start_ms = ortp_get_cur_time_ms();
			}
		break;
		case CongestionStateResolving:
			if (clock_drift) {
				binary_state_changed = ortp_congestion_detector_set_state(cd, CongestionStateDetected);
			} else {
				if (ortp_get_cur_time_ms() - cd->start_ms > congestion_pending_duration_ms) {
					binary_state_changed = ortp_congestion_detector_set_state(cd, CongestionStateNormal);
				}
209
			}
210
		break;
211
	}
212
	return binary_state_changed;
213 214 215 216 217 218
}

void ortp_congestion_detector_destroy(OrtpCongestionDetector *obj){
	ortp_free(obj);
}