qosanalyzer.c 20.8 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
/*
mediastreamer2 library - modular sound and video processing and streaming

 * Copyright (C) 2011  Belledonne Communications, Grenoble, France

	 Author: Simon Morlat <simon.morlat@linphone.org>

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 "mediastreamer2/bitratecontrol.h"
24
#include "qosanalyzer.h"
25

26
#include <math.h>
27

28 29
#define LOSS_RATE_MIN_INTERVAL 120

30 31 32
/**
 * Analyses a received RTCP packet.
 * Returns TRUE is relevant information has been found in the rtcp message, FALSE otherwise.
33
**/
34
bool_t ms_qos_analyzer_process_rtcp(MSQosAnalyzer *obj,mblk_t *msg){
35 36 37
	if (obj->desc->process_rtcp){
		return obj->desc->process_rtcp(obj,msg);
	}
38
	ms_error("MSQosAnalyzer: Unimplemented process_rtcp() call.");
39 40 41
	return FALSE;
}

42
void ms_qos_analyzer_suggest_action(MSQosAnalyzer *obj, MSRateControlAction *action){
43 44 45 46 47
	if (obj->desc->suggest_action){
		obj->desc->suggest_action(obj,action);
	}
}

48
void ms_qos_analyzer_update(MSQosAnalyzer *obj){
49 50 51
	if (obj->desc->update){
		obj->desc->update(obj);
	}
52 53
}

54
bool_t ms_qos_analyzer_has_improved(MSQosAnalyzer *obj){
55
	if (obj->desc->has_improved){
56
		return obj->desc->has_improved(obj);
57 58 59 60 61
	}
	ms_error("Unimplemented has_improved() call.");
	return TRUE;
}

62
void ms_qos_analyzer_set_on_action_suggested(MSQosAnalyzer *obj,
63
	void (*on_action_suggested)(void*, int, const char**), void* u){
64 65 66 67
	obj->on_action_suggested=on_action_suggested;
	obj->on_action_suggested_user_pointer=u;
}

68 69 70 71 72 73 74 75
void ms_qos_analyser_set_label(MSQosAnalyzer *obj, const char *label){
	if (obj->label){
		ms_free(obj->label);
		obj->label=NULL;
	}
	if (label) obj->label=ms_strdup(label);
}

76
MSQosAnalyzer *ms_qos_analyzer_ref(MSQosAnalyzer *obj){
77 78 79 80
	obj->refcnt++;
	return obj;
}

81
void ms_qos_analyzer_unref(MSQosAnalyzer *obj){
82 83 84 85
	obj->refcnt--;
	if (obj->refcnt<=0){
		if (obj->desc->uninit)
			obj->desc->uninit(obj);
86
		if (obj->label) ms_free(obj->label);
87 88
		ms_free(obj);
	}
89 90
}

91
const char *ms_rate_control_action_type_name(MSRateControlActionType t){
92 93 94 95 96 97 98 99 100 101 102 103 104
	switch(t){
		case MSRateControlActionDoNothing:
			return "DoNothing";
		case MSRateControlActionIncreaseQuality:
			return "IncreaseQuality";
		case MSRateControlActionDecreaseBitrate:
			return "DecreaseBitrate";
		case MSRateControlActionDecreasePacketRate:
			return "DecreasePacketRate";
	}
	return "bad action type";
}

105
/******************************************************************************/
106
/***************************** Simple QoS analyzer ****************************/
107
/******************************************************************************/
108 109
static bool_t rt_prop_doubled(rtpstats_t *cur,rtpstats_t *prev){
	//ms_message("AudioBitrateController: cur=%f, prev=%f",cur->rt_prop,prev->rt_prop);
110
	if (cur->rt_prop>=significant_delay && prev->rt_prop>0){
111 112 113 114 115 116 117 118
		if (cur->rt_prop>=(prev->rt_prop*2.0)){
			/*propagation doubled since last report */
			return TRUE;
		}
	}
	return FALSE;
}

119
static bool_t simple_rt_prop_increased(MSSimpleQosAnalyzer *obj){
120 121 122 123 124 125 126 127 128 129
	rtpstats_t *cur=&obj->stats[obj->curindex % STATS_HISTORY];
	rtpstats_t *prev=&obj->stats[(STATS_HISTORY+obj->curindex-1) % STATS_HISTORY];

	if (rt_prop_doubled(cur,prev)){
		obj->rt_prop_doubled=TRUE;
		return TRUE;
	}
	return FALSE;
}

130 131
static bool_t simple_analyzer_process_rtcp(MSQosAnalyzer *objbase, mblk_t *rtcp){
	MSSimpleQosAnalyzer *obj=(MSSimpleQosAnalyzer*)objbase;
132 133 134 135 136 137 138
	rtpstats_t *cur;
	const report_block_t *rb=NULL;
	if (rtcp_is_SR(rtcp)){
		rb=rtcp_SR_get_report_block(rtcp,0);
	}else if (rtcp_is_RR(rtcp)){
		rb=rtcp_RR_get_report_block(rtcp,0);
	}
139
	if (rb && report_block_get_ssrc(rb)==rtp_session_get_send_ssrc(obj->session)){
140

141 142
		obj->curindex++;
		cur=&obj->stats[obj->curindex % STATS_HISTORY];
143

144 145 146 147 148
		if (obj->clockrate==0){
			PayloadType *pt=rtp_profile_get_payload(rtp_session_get_send_profile(obj->session),rtp_session_get_send_payload_type(obj->session));
			if (pt!=NULL) obj->clockrate=pt->clock_rate;
			else return FALSE;
		}
149 150 151 152 153 154 155 156 157
		if (ortp_loss_rate_estimator_process_report_block(obj->lre,rb)){
			cur->lost_percentage=ortp_loss_rate_estimator_get_value(obj->lre);
			cur->int_jitter=1000.0*(float)report_block_get_interarrival_jitter(rb)/(float)obj->clockrate;
			cur->rt_prop=rtp_session_get_round_trip_propagation(obj->session);
			/*
			ms_message("MSQosAnalyzer[%s]: lost_percentage=%f, int_jitter=%f ms, rt_prop=%f sec",
				objbase->label ? objbase->label : "", cur->lost_percentage,cur->int_jitter,cur->rt_prop);
			*/
		}
158 159 160 161
	}
	return rb!=NULL;
}

162 163
static void simple_analyzer_suggest_action(MSQosAnalyzer *objbase, MSRateControlAction *action){
	MSSimpleQosAnalyzer *obj=(MSSimpleQosAnalyzer*)objbase;
164
	rtpstats_t *cur=&obj->stats[obj->curindex % STATS_HISTORY];
165

166
	/*big losses and big jitter */
167
	if (cur->lost_percentage>=unacceptable_loss_rate && cur->int_jitter>=big_jitter){
168 169
		action->type=MSRateControlActionDecreaseBitrate;
		action->value=MIN(cur->lost_percentage,50);
170
		ms_message("MSSimpleQosAnalyzer: loss rate unacceptable and big jitter");
171
	}else if (simple_rt_prop_increased(obj)){
172 173
		action->type=MSRateControlActionDecreaseBitrate;
		action->value=20;
174
		ms_message("MSSimpleQosAnalyzer: rt_prop doubled.");
175 176 177
	}else if (cur->lost_percentage>=unacceptable_loss_rate){
		/*big loss rate but no jitter, and no big rtp_prop: pure lossy network*/
		action->type=MSRateControlActionDecreasePacketRate;
178
		ms_message("MSSimpleQosAnalyzer: loss rate unacceptable.");
179 180
	}else{
		action->type=MSRateControlActionDoNothing;
181
		ms_message("MSSimpleQosAnalyzer: everything is fine.");
182
	}
183 184

	if (objbase->on_action_suggested!=NULL){
185 186 187 188 189
		int i;
		const int datac=4;
		char *data[datac];
		data[0]=ms_strdup("lost_percentage rt_prop_increased int_jitter_ms rt_prop_ms");
		data[1]=ms_strdup_printf("%d %d %d %d"
190
			, (int)cur->lost_percentage
191
			, (simple_rt_prop_increased(obj)==TRUE)
192 193
			, (int)cur->int_jitter
			, (int)(1000*cur->rt_prop));
194 195
		data[2]=ms_strdup("action_type action_value");
		data[3]=ms_strdup_printf("%s %d"
196 197 198
			, ms_rate_control_action_type_name(action->type)
			, action->value);

199
		objbase->on_action_suggested(objbase->on_action_suggested_user_pointer, datac, (const char**)data);
200

201 202 203
		for (i=0;i<datac;++i){
			ms_free(data[i]);
		}
204
	}
205 206
}

207 208
static bool_t simple_analyzer_has_improved(MSQosAnalyzer *objbase){
	MSSimpleQosAnalyzer *obj=(MSSimpleQosAnalyzer*)objbase;
209 210 211 212 213
	rtpstats_t *cur=&obj->stats[obj->curindex % STATS_HISTORY];
	rtpstats_t *prev=&obj->stats[(STATS_HISTORY+obj->curindex-1) % STATS_HISTORY];

	if (prev->lost_percentage>=unacceptable_loss_rate){
		if (cur->lost_percentage<prev->lost_percentage){
214
			ms_message("MSSimpleQosAnalyzer: lost percentage has improved");
215 216 217 218
			return TRUE;
		}else goto end;
	}
	if (obj->rt_prop_doubled && cur->rt_prop<prev->rt_prop){
219
		ms_message("MSSimpleQosAnalyzer: rt prop decreased");
220 221 222 223
		obj->rt_prop_doubled=FALSE;
		return TRUE;
	}

224
	end:
225
	ms_message("MSSimpleQosAnalyzer: no improvements.");
226 227 228
	return FALSE;
}

229 230 231 232 233
static void simple_analyzer_uninit(MSQosAnalyzer *objbase){
	MSSimpleQosAnalyzer *obj=(MSSimpleQosAnalyzer*)objbase;
	ortp_loss_rate_estimator_destroy(obj->lre);
}

234 235 236
static MSQosAnalyzerDesc simple_analyzer_desc={
	simple_analyzer_process_rtcp,
	simple_analyzer_suggest_action,
237 238 239
	simple_analyzer_has_improved,
	NULL,
	simple_analyzer_uninit
240 241
};

242 243
MSQosAnalyzer * ms_simple_qos_analyzer_new(RtpSession *session){
	MSSimpleQosAnalyzer *obj=ms_new0(MSSimpleQosAnalyzer,1);
244
	obj->session=session;
245
	obj->parent.desc=&simple_analyzer_desc;
246
	obj->parent.type=Simple;
247
	obj->lre=ortp_loss_rate_estimator_new(LOSS_RATE_MIN_INTERVAL, rtp_session_get_seq_number(session));
248
	return (MSQosAnalyzer*)obj;
249 250 251 252 253 254
}




/******************************************************************************/
255
/***************************** Stateful QoS analyzer ****************************/
256
/******************************************************************************/
257
const char *ms_qos_analyzer_network_state_name(MSQosAnalyzerNetworkState state){
258
	switch(state){
259
		case MSQosAnalyzerNetworkFine:
260
			return "fine";
261
		case MSQosAnalyzerNetworkUnstable:
262
			return "unstable";
263
		case MSQosAnalyzerNetworkCongested:
264
			return "congested";
265
		case MSQosAnalyzerNetworkLossy:
266 267 268 269 270
			return "lossy";
	}
	return "bad state type";
}

271 272 273 274 275 276
static int earlier_than(rtcpstatspoint_t *p, const time_t * now){
	if (p->timestamp < *now){
		ms_free(p);
		return FALSE;
	}
	return TRUE;
277
}
278 279 280 281
static int sort_points(const rtcpstatspoint_t *p1, const rtcpstatspoint_t *p2){
	return p1->bandwidth > p2->bandwidth;
}

282
static int stateful_qos_analyzer_get_total_emitted(const MSStatefulQosAnalyzer *obj, const report_block_t *rb){
283 284 285 286 287 288 289 290
	double dup = obj->burst_ratio;
	int burst_within_start = MAX(obj->previous_ext_high_seq_num_rec, obj->start_seq_number);
	int burst_within_end = MIN(report_block_get_high_ext_seq(rb), obj->last_seq_number);
	int uniq_emitted=report_block_get_high_ext_seq(rb) - obj->previous_ext_high_seq_num_rec;

	return uniq_emitted + MAX(0,burst_within_end - burst_within_start) * dup;
}

291
static double stateful_qos_analyzer_upload_bandwidth(MSStatefulQosAnalyzer *obj){
292 293
	double up_bw=rtp_session_get_send_bandwidth(obj->session)/1000.0;

294 295 296 297 298
	if (obj->upload_bandwidth_count){
		obj->upload_bandwidth_latest=obj->upload_bandwidth_sum/obj->upload_bandwidth_count;
	}

	obj->upload_bandwidth_count=0;
299 300
	obj->upload_bandwidth_sum=0;

301
	ms_debug("MSQosStatefulAnalyzer[%p]: latest_up_bw=%f vs sum_up_bw=%f", obj, up_bw, obj->upload_bandwidth_latest);
302
	return obj->upload_bandwidth_latest;
303 304
}

305 306
static bool_t stateful_analyzer_process_rtcp(MSQosAnalyzer *objbase, mblk_t *rtcp){
	MSStatefulQosAnalyzer *obj=(MSStatefulQosAnalyzer*)objbase;
307 308 309 310 311 312 313 314
	rtpstats_t *cur;
	const report_block_t *rb=NULL;
	if (rtcp_is_SR(rtcp)){
		rb=rtcp_SR_get_report_block(rtcp,0);
	}else if (rtcp_is_RR(rtcp)){
		rb=rtcp_RR_get_report_block(rtcp,0);
	}
	if (rb && report_block_get_ssrc(rb)==rtp_session_get_send_ssrc(obj->session)){
315 316
		double up_bw = stateful_qos_analyzer_upload_bandwidth(obj);
		int total_emitted=stateful_qos_analyzer_get_total_emitted(obj, rb);
317 318 319 320 321 322 323 324 325 326 327 328
		obj->curindex++;
		cur=&obj->stats[obj->curindex % STATS_HISTORY];

		if (obj->clockrate==0){
			PayloadType *pt=rtp_profile_get_payload(rtp_session_get_send_profile(obj->session),rtp_session_get_send_payload_type(obj->session));
			if (pt!=NULL) obj->clockrate=pt->clock_rate;
			else return FALSE;
		}

		cur->lost_percentage=100.0*(float)report_block_get_fraction_lost(rb)/256.0;
		cur->int_jitter=1000.0*(float)report_block_get_interarrival_jitter(rb)/(float)obj->clockrate;
		cur->rt_prop=rtp_session_get_round_trip_propagation(obj->session);
329
		ms_message("MSSimpleQosAnalyzer: lost_percentage=%f, int_jitter=%f ms, rt_prop=%f sec",cur->lost_percentage,cur->int_jitter,cur->rt_prop);
330
		if (obj->curindex>2){
331 332
			double loss_rate = cur->lost_percentage/100.0;
			int cum_loss=report_block_get_cum_packet_loss(rb);
333
			int cum_loss_curr=cum_loss - obj->cum_loss_prev;
334
			int uniq_emitted=report_block_get_high_ext_seq(rb) - obj->previous_ext_high_seq_num_rec;
335

336
			if (obj->previous_ext_high_seq_num_rec > 0){
337
				loss_rate=(1. - (uniq_emitted - cum_loss_curr) * 1.f / total_emitted);
338 339
				ms_debug("MSQosStatefulAnalyzer[%p]: RECEIVE estimated loss rate=%f vs 'real'=%f",
					obj, loss_rate, report_block_get_fraction_lost(rb)/256.);
340 341
			}

342 343 344 345 346 347 348 349 350 351 352 353 354 355 356
			obj->latest=ms_new0(rtcpstatspoint_t, 1);
			obj->latest->timestamp=ms_time(0);
			obj->latest->bandwidth=up_bw;
			obj->latest->loss_percent=MAX(0,loss_rate);
			obj->latest->rtt=cur->rt_prop;

			obj->rtcpstatspoint=ms_list_insert_sorted(obj->rtcpstatspoint, obj->latest, (MSCompareFunc)sort_points);

			if (obj->latest->loss_percent < 1e-5){
				MSList *it=obj->rtcpstatspoint;
				MSList *latest_pos=ms_list_find(obj->rtcpstatspoint,obj->latest);
				while (it!=latest_pos->next){
					((rtcpstatspoint_t *)it->data)->loss_percent=0.f;
					it = it->next;
				}
357
			}
358 359
			ms_debug("MSQosStatefulAnalyzer[%p]: one more %d: %f %f",
				obj, obj->curindex-2, obj->latest->bandwidth, obj->latest->loss_percent);
360 361

			if (ms_list_size(obj->rtcpstatspoint) > ESTIM_HISTORY){
362
				int prev_size = ms_list_size(obj->rtcpstatspoint);
363
				/*clean everything which occurred 60 sec or more ago*/
364
				time_t clear_time = ms_time(0) - 60;
365
				obj->rtcpstatspoint = ms_list_remove_custom(obj->rtcpstatspoint, (MSCompareFunc)earlier_than, &clear_time);
366 367
				ms_debug("MSQosStatefulAnalyzer[%p]: Reached list maximum capacity (count=%d) --> Cleaned list (count=%d)",
					obj, prev_size, ms_list_size(obj->rtcpstatspoint));
368
			}
369
		}
370 371
		obj->cum_loss_prev=report_block_get_cum_packet_loss(rb);
		obj->previous_ext_high_seq_num_rec=report_block_get_high_ext_seq(rb);
372 373 374 375
	}
	return rb!=NULL;
}

376 377 378 379
static float lerp(float inf, float sup, float v){
	return inf + (sup - inf) * v;
}

380 381 382 383 384 385 386 387 388
static MSList *find_first_with_loss(MSList *list){
	for(;list!=NULL;list=list->next){
		if (((rtcpstatspoint_t *)list->data)->loss_percent > 1e-5){
			return list;
		}
	}
	return NULL;
}

389
static void smooth_values(MSStatefulQosAnalyzer *obj){
390
	MSList *first_loss = find_first_with_loss(obj->rtcpstatspoint);
391 392
	MSList *it = obj->rtcpstatspoint;
	rtcpstatspoint_t *curr = (rtcpstatspoint_t *)it->data;
393 394 395 396 397 398 399 400 401
	double prev_loss = 0.;

	if (first_loss == obj->rtcpstatspoint){
		prev_loss = curr->loss_percent;
		curr->loss_percent = lerp(curr->loss_percent, ((rtcpstatspoint_t *)it->next->data)->loss_percent, .25);
		it = it->next;
	}else{
		it = first_loss;
	}
402 403 404 405 406 407

	/*nothing to smooth*/
	if (it == NULL){
		return;
	}

408
	curr = (rtcpstatspoint_t *)it->data;
409

410 411 412 413 414 415 416 417 418
	while (it->next != NULL){
		rtcpstatspoint_t *prev = ((rtcpstatspoint_t *)it->prev->data);
		rtcpstatspoint_t *next = ((rtcpstatspoint_t *)it->next->data);

		float v = (curr->bandwidth - prev->bandwidth) / (next->bandwidth - prev->bandwidth);
		float new_loss = lerp(prev_loss, next->loss_percent, v);
		prev_loss = curr->loss_percent;
		curr->loss_percent = (curr->loss_percent + new_loss) / 2.;
		it = it->next;
419
		curr = (rtcpstatspoint_t *)it->data;
420
	}
421
	curr->loss_percent = lerp(prev_loss, curr->loss_percent, .75);
422
}
423

424
static float compute_available_bw(MSStatefulQosAnalyzer *obj){
425
	MSList *it;
426
	double constant_network_loss = 0.;
427
	double mean_bw = 0.;
428 429 430 431
	MSList *current = obj->rtcpstatspoint;
	MSList *last = current;
	int size = ms_list_size(obj->rtcpstatspoint);
	if (current == NULL){
432
		ms_debug("MSQosStatefulAnalyzer[%p]: Not points available for computation.", obj);
433
		return -1;
434 435
	}

436 437 438
	while (last->next){
		last = last->next;
	}
439

440 441
	if (size > 3){
		smooth_values(obj);
442
	}
443
	/*suppose that first point is a reliable estimation of the constant network loss rate*/
444
	constant_network_loss = ((rtcpstatspoint_t *)obj->rtcpstatspoint->data)->loss_percent;
445

446

447
	ms_debug("MSQosStatefulAnalyzer[%p]:\tconstant_network_loss=%f", obj, constant_network_loss);
448 449
	for (it = obj->rtcpstatspoint; it != NULL; it=it->next){
		rtcpstatspoint_t * point = (rtcpstatspoint_t *)it->data;
450 451
		ms_debug("MSQosStatefulAnalyzer[%p]:\t\tsorted values %d: %f %f",
			obj, ms_list_position(obj->rtcpstatspoint, it), point->bandwidth, point->loss_percent);
452 453
	}

454
	if (size == 1){
455
		ms_debug("MSQosStatefulAnalyzer[%p]: One single point", obj);
456 457 458 459
		rtcpstatspoint_t *p = (rtcpstatspoint_t *)current->data;
		mean_bw = p->bandwidth * ((p->loss_percent>1e-5) ? (1-p->loss_percent):2);
	}else{
		while (current!=NULL && ((rtcpstatspoint_t*)current->data)->loss_percent<0.03+constant_network_loss){
460
			ms_debug("MSQosStatefulAnalyzer[%p]:\t%d is stable", obj, ms_list_position(obj->rtcpstatspoint, current));
461 462 463

			for (it=last;it!=current;it=it->prev){
				if (((rtcpstatspoint_t *)it->data)->loss_percent <= 0.03 + ((rtcpstatspoint_t*)current->data)->loss_percent){
464 465
					ms_debug("MSQosStatefulAnalyzer[%p]:\t%d is less than %d",
						obj, ms_list_position(obj->rtcpstatspoint, it), ms_list_position(obj->rtcpstatspoint, current));
466 467 468
					current = it;
					break;
				}
469 470
			}

471 472
			current = current->next;
		}
473

474 475 476 477 478 479 480 481 482 483 484
		if (current == NULL){
			/*constant loss rate - bad network conditions but no congestion*/
			mean_bw = 2 * ((rtcpstatspoint_t*)last->data)->bandwidth;
		}else if (current->prev == obj->rtcpstatspoint){
			/*only first packet is stable - might still be above real bandwidth*/
			rtcpstatspoint_t *p = (rtcpstatspoint_t *)current->prev->data;
			mean_bw = p->bandwidth * (1 - p->loss_percent);
		}else{
			/*there is some congestion*/
			mean_bw = .5*(((rtcpstatspoint_t*)current->prev->data)->bandwidth+((rtcpstatspoint_t*)current->data)->bandwidth);
		}
485

486 487
		ms_debug("MSQosStatefulAnalyzer[%p]: [0->%d] Last stable is %d(%f;%f)"
			, obj
488 489 490
			, ms_list_position(obj->rtcpstatspoint, last)
			, ms_list_position(obj->rtcpstatspoint, (current ? current->prev : last))
			, ((rtcpstatspoint_t*) (current ? current->prev->data : last->data))->bandwidth
491
			, ((rtcpstatspoint_t*) (current ? current->prev->data : last->data))->loss_percent);
492
		if (current!=NULL){
493 494
			ms_debug("MSQosStatefulAnalyzer[%p]: , first unstable is %d(%f;%f)"
				, obj
495 496
				, ms_list_position(obj->rtcpstatspoint, current)
				, ((rtcpstatspoint_t*) current->data)->bandwidth
497
				, ((rtcpstatspoint_t*) current->data)->loss_percent);
498
		}
499
	}
500
	ms_debug("MSQosStatefulAnalyzer[%p]:  --> estimated_available_bw=%f", obj, mean_bw);
501

502
	obj->network_loss_rate = constant_network_loss;
503
	obj->congestion_bandwidth = mean_bw;
504
	obj->network_state =
505 506 507
		(current==NULL && constant_network_loss < .1) ?	MSQosAnalyzerNetworkFine
		: (constant_network_loss > .1) ?				MSQosAnalyzerNetworkLossy
		:												MSQosAnalyzerNetworkCongested;
508

509
	return mean_bw;
510 511
}

512 513
static void stateful_analyzer_suggest_action(MSQosAnalyzer *objbase, MSRateControlAction *action){
	MSStatefulQosAnalyzer *obj=(MSStatefulQosAnalyzer*)objbase;
514

515
	float curbw = obj->latest ? obj->latest->bandwidth : 0.f;
516
	float bw = compute_available_bw(obj);
517

518
	/*try a burst every 50 seconds (10 RTCP packets)*/
519
	if (obj->curindex % 10 == 0){
520
		ms_debug("MSQosStatefulAnalyzer[%p]: try burst!", obj);
521
		obj->burst_state = MSStatefulQosAnalyzerBurstEnable;
522
	}
523
	/*test a min burst to avoid overestimation of available bandwidth*/
524
	else if (obj->curindex % 10 == 2 || obj->curindex % 10 == 3){
525
		ms_debug("MSQosStatefulAnalyzer[%p]: try minimal burst!", obj);
526
		bw *= .33;
527
	}
528

529
	/*no bandwidth estimation computed*/
530
	if (bw <= 0 || curbw <= 0){
531
		action->type=MSRateControlActionDoNothing;
532
		action->value=0;
533
	}else if (bw > curbw){
534
		action->type=MSRateControlActionIncreaseQuality;
535
		action->value=MAX(0, 100. * (bw / curbw - 1));
536 537
	}else{
		action->type=MSRateControlActionDecreaseBitrate;
538
		action->value=MAX(10, -100. * (bw / curbw - 1));
539
	}
540

541 542
	ms_debug("MSQosStatefulAnalyzer[%p]: %s of value %d",
		obj, ms_rate_control_action_type_name(action->type), action->value);
543 544
}

545
static bool_t stateful_analyzer_has_improved(MSQosAnalyzer *objbase){
546
	/*never tell the controller that situation has improved to avoid 'Stable' state
547
	which is not necessary for this analyzer*/
548 549 550
	return FALSE;
}

551 552
static void stateful_analyzer_update(MSQosAnalyzer *objbase){
	MSStatefulQosAnalyzer *obj=(MSStatefulQosAnalyzer*)objbase;
553 554 555
	static time_t last_measure;

	if (last_measure != ms_time(0)){
556
		obj->upload_bandwidth_count++;
557 558 559 560
		obj->upload_bandwidth_sum+=rtp_session_get_send_bandwidth(obj->session)/1000.0;
	}
	last_measure = ms_time(0);

561 562
	if (obj->burst_duration_ms>0){
		switch (obj->burst_state){
563 564
		case MSStatefulQosAnalyzerBurstEnable:{
			obj->burst_state=MSStatefulQosAnalyzerBurstInProgress;
565 566 567
			ortp_gettimeofday(&obj->start_time, NULL);
			rtp_session_set_duplication_ratio(obj->session, obj->burst_ratio);
			obj->start_seq_number=obj->last_seq_number=obj->session->rtp.snd_seq;
568
		} case MSStatefulQosAnalyzerBurstInProgress: {
569 570 571 572 573 574 575 576 577
			struct timeval now;
			double elapsed;

			ortp_gettimeofday(&now,NULL);
			elapsed=((now.tv_sec-obj->start_time.tv_sec)*1000.0) +  ((now.tv_usec-obj->start_time.tv_usec)/1000.0);

			obj->last_seq_number=obj->session->rtp.snd_seq;

			if (elapsed > obj->burst_duration_ms){
578
				obj->burst_state=MSStatefulQosAnalyzerBurstDisable;
579 580
				rtp_session_set_duplication_ratio(obj->session, 0);
			}
581
		} case MSStatefulQosAnalyzerBurstDisable: {
582
		}
583 584 585
		}
	}
}
586

587 588
static void stateful_analyzer_uninit(MSQosAnalyzer *objbase){
	MSStatefulQosAnalyzer *obj=(MSStatefulQosAnalyzer*)objbase;
589 590 591
	ms_list_for_each(obj->rtcpstatspoint, ms_free);
}

592 593 594 595 596 597
static MSQosAnalyzerDesc stateful_analyzer_desc={
	stateful_analyzer_process_rtcp,
	stateful_analyzer_suggest_action,
	stateful_analyzer_has_improved,
	stateful_analyzer_update,
	stateful_analyzer_uninit,
598 599
};

600 601
MSQosAnalyzer * ms_stateful_qos_analyzer_new(RtpSession *session){
	MSStatefulQosAnalyzer *obj=ms_new0(MSStatefulQosAnalyzer,1);
602
	obj->session=session;
603
	obj->parent.desc=&stateful_analyzer_desc;
604
	obj->parent.type=Stateful;
605
	/*double the upload bandwidth based on a 5 sec RTCP reports interval*/
606
	obj->burst_duration_ms=1000;
607
	obj->burst_ratio=9;
608
	return (MSQosAnalyzer*)obj;
609 610
}

611