Qos stateful analyzer: compute bandwidth differently. Instead of doing...

Qos stateful analyzer: compute bandwidth differently. Instead of doing bandwidth average since last RTCP report received, store bandwidth consumption regularly. Find the interval matching the sequence number contained in the received RTCP report, and do the average only on this subinterval to get (hopefully) more accurate bandwidth estimation.
parent 6be5e976
......@@ -110,10 +110,7 @@ struct _MSQosAnalyzer{
* @param argv array containing various algorithm dependent information
**/
void (*on_action_suggested)(void* userpointer, int argc, const char** argv);
/**
* User pointer used in #on_action_suggested . Be careful: This pointer is
* automatically freed by the QosAnalyzer on change or on destroy
**/
/** User pointer given at #on_action_suggested callback **/
void *on_action_suggested_user_pointer;
int refcnt;
MSQosAnalyzerAlgorithm type;
......
......@@ -335,8 +335,11 @@ static int bandwidth_inc_video_bitrate(MSBandwidthBitrateDriver *obj, const MSRa
}
newbr= (float)obj->cur_bitrate*(100.0+(float)action->value)/100.0;
if (newbr>obj->nom_bitrate){
if (obj->cur_bitrate==obj->nom_bitrate){
ms_message("MSBandwidthBitrateDriver: bitrate already at maximum level.");
return -1;
}
newbr=obj->nom_bitrate;
ret=-1;
}
ms_message("MSBandwidthBitrateDriver: increasing bitrate from %i to %i bps for video encoder.",obj->cur_bitrate,newbr);
obj->cur_bitrate=newbr;
......
......@@ -56,15 +56,12 @@ bool_t ms_qos_analyzer_has_improved(MSQosAnalyzer *obj){
if (obj->desc->has_improved){
return obj->desc->has_improved(obj);
}
ms_error("Unimplemented has_improved() call.");
ms_error("MSQosAnalyzer: Unimplemented has_improved() call.");
return TRUE;
}
void ms_qos_analyzer_set_on_action_suggested(MSQosAnalyzer *obj,
void (*on_action_suggested)(void*, int, const char**), void* u){
if (obj->on_action_suggested_user_pointer!=NULL){
ms_free(obj->on_action_suggested_user_pointer);
}
obj->on_action_suggested=on_action_suggested;
obj->on_action_suggested_user_pointer=u;
}
......@@ -90,7 +87,7 @@ const MSQosAnalyzerAlgorithm ms_qos_analyzer_algorithm_from_string(const char* a
else if (strcasecmp(alg, "Stateful")==0)
return MSQosAnalyzerAlgorithmStateful;
ms_error("Invalid qos analyzer: %s", alg);
ms_error("MSQosAnalyzer: Invalid QoS analyzer: %s", alg);
return MSQosAnalyzerAlgorithmSimple;
}
......@@ -110,7 +107,6 @@ void ms_qos_analyzer_unref(MSQosAnalyzer *obj){
obj->desc->uninit(obj);
if (obj->label) ms_free(obj->label);
if (obj->lre) ortp_loss_rate_estimator_destroy(obj->lre);
if (obj->on_action_suggested_user_pointer) ms_free(obj->on_action_suggested_user_pointer);
ms_free(obj);
}
......@@ -284,34 +280,59 @@ static int earlier_than(rtcpstatspoint_t *p, const time_t * now){
}
return TRUE;
}
static int sort_points(const rtcpstatspoint_t *p1, const rtcpstatspoint_t *p2){
static int sort_by_bandwidth(const rtcpstatspoint_t *p1, const rtcpstatspoint_t *p2){
return p1->bandwidth > p2->bandwidth;
}
static float stateful_qos_analyzer_upload_bandwidth(MSStatefulQosAnalyzer *obj, uint32_t seq_num){
int latest_bw;
float last_action_bw;
float bw_per_seqnum=0.f;
float bw_per_avg=0.f;
/*First method to compute bandwidth*/
if (obj->upload_bandwidth_count){
obj->upload_bandwidth_latest=obj->upload_bandwidth_sum/obj->upload_bandwidth_count;
bw_per_avg=obj->upload_bandwidth_sum/obj->upload_bandwidth_count;
}
obj->upload_bandwidth_count=0;
obj->upload_bandwidth_sum=0;
latest_bw=obj->upload_bandwidth_cur;
while (obj->upload_bandwidth[latest_bw].seq_number<seq_num){
latest_bw = (latest_bw+1)%BW_HISTORY;
if (latest_bw==obj->upload_bandwidth_cur) break;
for (latest_bw=0;latest_bw<BW_HISTORY;++latest_bw){
ms_debug("MSStatefulQosAnalyzer[%p]:\t%u\t-->\t%f", obj,
obj->upload_bandwidth[latest_bw].seq_number,
obj->upload_bandwidth[latest_bw].up_bandwidth);
}
last_action_bw=obj->upload_bandwidth[latest_bw].up_bandwidth;
if (obj->upload_bandwidth[(obj->upload_bandwidth_cur+1)%BW_HISTORY].seq_number>seq_num){
ms_warning("MSStatefulQosAnalyzer[%p]: saved to much points - seq_number lower "
"than oldest measure! Increase BW_HISTORY or reduce ptime!", obj);
}else{
int count = 0;
latest_bw=obj->upload_bandwidth_cur;
/*Get the average of all measures with seq number lower than the one from the report*/
for (latest_bw=0; latest_bw<BW_HISTORY; ++latest_bw){
if (obj->upload_bandwidth[latest_bw].seq_number>0
&& obj->upload_bandwidth[latest_bw].seq_number<seq_num){
count++;
bw_per_seqnum+=obj->upload_bandwidth[latest_bw].up_bandwidth;
}
}
// invalid, no measures available
if (count==0){
ms_error("MSStatefulQosAnalyzer[%p]: no measures available to compute bandwidth for ext_seq=%u", obj, seq_num);
bw_per_seqnum = rtp_session_get_send_bandwidth(obj->session)/1000.0;
}else{
bw_per_seqnum /= count;//((BW_HISTORY + obj->upload_bandwidth_cur - latest_bw) % BW_HISTORY);
ms_debug("MSStatefulQosAnalyzer[%p]: found average bandwidth for seq_num=%u", obj, seq_num);
}
}
ms_message("MSStatefulQosAnalyzer[%p]: estimate_bw=%f vs sum_up_bw=%f vs last_action_bw=%f"
ms_message("MSStatefulQosAnalyzer[%p]: bw_curent=%f vs bw_per_avg=%f vs bw_per_seqnum=%f"
, obj
, rtp_session_get_send_bandwidth(obj->session)/1000.0
, obj->upload_bandwidth_latest
, last_action_bw);
, bw_per_avg
, bw_per_seqnum);
obj->upload_bandwidth_latest = bw_per_seqnum;
return obj->upload_bandwidth_latest;
}
......@@ -326,16 +347,20 @@ static bool_t stateful_analyzer_process_rtcp(MSQosAnalyzer *objbase, mblk_t *rtc
if (rb && report_block_get_ssrc(rb)==rtp_session_get_send_ssrc(obj->session)){
/* Save bandwidth used at this time */
obj->upload_bandwidth[obj->upload_bandwidth_cur].seq_number = rb->ext_high_seq_num_rec;
obj->upload_bandwidth[obj->upload_bandwidth_cur].up_bandwidth = rtp_session_get_send_bandwidth(obj->session)/1000.0;
obj->upload_bandwidth_cur = (obj->upload_bandwidth_cur+1)%BW_HISTORY;
if (ortp_loss_rate_estimator_process_report_block(objbase->lre,&obj->session->rtp,rb)){
int i;
float loss_rate = ortp_loss_rate_estimator_get_value(objbase->lre);
float up_bw = stateful_qos_analyzer_upload_bandwidth(obj,rb->ext_high_seq_num_rec);
float up_bw = stateful_qos_analyzer_upload_bandwidth(obj,report_block_get_high_ext_seq(rb));
obj->curindex++;
/*flush bandwidth estimation measures for seq number lower than remote report block received*/
for (i=0;i<BW_HISTORY;i++){
if (obj->upload_bandwidth[i].seq_number<report_block_get_high_ext_seq(rb)){
obj->upload_bandwidth[i].seq_number=0;
obj->upload_bandwidth[i].up_bandwidth=0.f;
}
}
/* Always skip the first report, since values might be erroneous due
to initialization of multiples objects (encoder/decoder/stats computing..)
Instead assume loss rate is a good estimation of network capacity */
......@@ -350,7 +375,8 @@ static bool_t stateful_analyzer_process_rtcp(MSQosAnalyzer *objbase, mblk_t *rtc
obj->latest->loss_percent=loss_rate;
obj->latest->rtt=rtp_session_get_round_trip_propagation(obj->session);
obj->rtcpstatspoint=ms_list_insert_sorted(obj->rtcpstatspoint, obj->latest, (MSCompareFunc)sort_points);
obj->rtcpstatspoint=ms_list_insert_sorted(obj->rtcpstatspoint,
obj->latest, (MSCompareFunc)sort_by_bandwidth);
/*if the measure was 0% loss, reset to 0% every measures below it*/
if (obj->latest->loss_percent < 1e-5){
......@@ -369,8 +395,10 @@ static bool_t stateful_analyzer_process_rtcp(MSQosAnalyzer *objbase, mblk_t *rtc
/*clean everything which occurred 60 sec or more ago*/
time_t clear_time = ms_time(0) - 60;
obj->rtcpstatspoint = ms_list_remove_custom(obj->rtcpstatspoint, (MSCompareFunc)earlier_than, &clear_time);
ms_message("MSStatefulQosAnalyzer[%p]: Reached list maximum capacity (count=%d) --> Cleaned list (count=%d)",
obj->rtcpstatspoint = ms_list_remove_custom(obj->rtcpstatspoint,
(MSCompareFunc)earlier_than, &clear_time);
ms_message("MSStatefulQosAnalyzer[%p]: reached list maximum capacity "
"(count=%d) --> Cleaned list (count=%d)",
obj, prev_size, ms_list_size(obj->rtcpstatspoint));
}
return TRUE;
......@@ -400,7 +428,8 @@ static void smooth_values(MSStatefulQosAnalyzer *obj){
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);
curr->loss_percent = lerp(curr->loss_percent,
((rtcpstatspoint_t *)it->next->data)->loss_percent, .25);
it = it->next;
}else{
it = first_loss;
......@@ -435,7 +464,7 @@ static float compute_available_bw(MSStatefulQosAnalyzer *obj){
MSList *last = current;
int size = ms_list_size(obj->rtcpstatspoint);
if (current == NULL){
ms_message("MSStatefulQosAnalyzer[%p]: Not points available for computation.", obj);
ms_message("MSStatefulQosAnalyzer[%p]: no points available for estimation", obj);
return -1;
}
......@@ -461,7 +490,7 @@ static float compute_available_bw(MSStatefulQosAnalyzer *obj){
if (size == 1){
rtcpstatspoint_t *p = (rtcpstatspoint_t *)current->data;
ms_message("MSStatefulQosAnalyzer[%p]: One single point", obj);
ms_message("MSStatefulQosAnalyzer[%p]: one single point", obj);
mean_bw = p->bandwidth * ((p->loss_percent>1e-5) ? (100-p->loss_percent)/100.f:2);
}else{
while (current!=NULL && ((rtcpstatspoint_t*)current->data)->loss_percent<3+constant_network_loss){
......@@ -491,7 +520,7 @@ static float compute_available_bw(MSStatefulQosAnalyzer *obj){
mean_bw = .5*(((rtcpstatspoint_t*)current->prev->data)->bandwidth+((rtcpstatspoint_t*)current->data)->bandwidth);
}
ms_message("MSStatefulQosAnalyzer[%p]: [0->%d] Last stable is %d(%f;%f)"
ms_message("MSStatefulQosAnalyzer[%p]: [0->%d] last stable is %d(%f;%f)"
, obj
, ms_list_position(obj->rtcpstatspoint, last)
, ms_list_position(obj->rtcpstatspoint, (current ? current->prev : last))
......@@ -537,7 +566,7 @@ static void stateful_analyzer_suggest_action(MSQosAnalyzer *objbase, MSRateContr
: NULL;
/*try a burst every 50 seconds (10 RTCP packets)*/
if (obj->curindex % 10 == 0){
if (obj->curindex % 10 == 6){
ms_message("MSStatefulQosAnalyzer[%p]: try burst!", obj);
obj->burst_state = MSStatefulQosAnalyzerBurstEnable;
}
......@@ -601,9 +630,18 @@ static void stateful_analyzer_update(MSQosAnalyzer *objbase){
MSStatefulQosAnalyzer *obj=(MSStatefulQosAnalyzer*)objbase;
static time_t last_measure;
/* Every seconds, save the bandwidth used. This is needed to know how much
bandwidth was used when receiving a receiver report. Since the report contains
the "last sequence number", it allows us to precisely know which interval to
consider */
if (last_measure != ms_time(0)){
obj->upload_bandwidth_count++;
obj->upload_bandwidth_sum+=rtp_session_get_send_bandwidth(obj->session)/1000.0;
/* Save bandwidth used at this time */
obj->upload_bandwidth[obj->upload_bandwidth_cur].seq_number = rtp_session_get_seq_number(obj->session);
obj->upload_bandwidth[obj->upload_bandwidth_cur].up_bandwidth = rtp_session_get_send_bandwidth(obj->session)/1000.0;
obj->upload_bandwidth_cur = (obj->upload_bandwidth_cur+1)%BW_HISTORY;
}
last_measure = ms_time(0);
......
......@@ -60,7 +60,7 @@ extern "C" {
/**************************************************************************/
/************************* Stateful QoS analyzer **************************/
/**************************************************************************/
#define BW_HISTORY 5
#define BW_HISTORY 10
typedef struct {
time_t timestamp;
......@@ -94,8 +94,8 @@ extern "C" {
MSStatefulQosAnalyzerBurstState burst_state;
struct timeval start_time;
uint32_t upload_bandwidth_count;
double upload_bandwidth_sum;
uint32_t upload_bandwidth_count; /*deprecated*/
double upload_bandwidth_sum; /*deprecated*/
double upload_bandwidth_latest;
int upload_bandwidth_cur;
bandwidthseqnum upload_bandwidth[BW_HISTORY];
......@@ -108,5 +108,3 @@ extern "C" {
#endif
#endif
......@@ -78,8 +78,6 @@ typedef struct _stream_manager_t {
int local_rtp;
int local_rtcp;
OrtpEvQueue * evq;
union{
AudioStream* audio_stream;
VideoStream* video_stream;
......@@ -100,15 +98,11 @@ stream_manager_t * stream_manager_new(MSFormatType type) {
mgr->local_rtp=(rand() % ((2^16)-1024) + 1024) & ~0x1;
mgr->local_rtcp=mgr->local_rtp+1;
mgr->evq=ortp_ev_queue_new();
if (mgr->type==MSAudio){
mgr->audio_stream=audio_stream_new (mgr->local_rtp, mgr->local_rtcp,FALSE);
rtp_session_register_event_queue(mgr->audio_stream->ms.sessions.rtp_session,mgr->evq);
}else{
#if VIDEO_ENABLED
mgr->video_stream=video_stream_new (mgr->local_rtp, mgr->local_rtcp,FALSE);
rtp_session_register_event_queue(mgr->video_stream->ms.sessions.rtp_session,mgr->evq);
#else
ms_fatal("Unsupported stream type [%s]",ms_format_type_to_string(mgr->type));
#endif
......@@ -121,17 +115,15 @@ static void stream_manager_delete(stream_manager_t * mgr) {
if (mgr->type==MSAudio){
unlink(RECORDED_16K_1S_FILE);
rtp_session_unregister_event_queue(mgr->audio_stream->ms.sessions.rtp_session,mgr->evq);
audio_stream_stop(mgr->audio_stream);
}else{
#if VIDEO_ENABLED
rtp_session_unregister_event_queue(mgr->video_stream->ms.sessions.rtp_session,mgr->evq);
video_stream_stop(mgr->video_stream);
#else
ms_fatal("Unsupported stream type [%s]",ms_format_type_to_string(mgr->type));
#endif
}
ortp_ev_queue_destroy(mgr->evq);
ms_free(mgr);
}
......@@ -181,34 +173,17 @@ static void video_manager_start( stream_manager_t * mgr
}
#endif
static void handle_queue_events(stream_manager_t * stream_mgr) {
OrtpEvent *ev;
while (NULL != (ev=ortp_ev_queue_get(stream_mgr->evq))){
OrtpEventType evt=ortp_event_get_type(ev);
OrtpEventData *evd=ortp_event_get_data(ev);
if (evt == ORTP_EVENT_RTCP_PACKET_RECEIVED) {
const report_block_t *rb=NULL;
if (rtcp_is_SR(evd->packet)){
rb=rtcp_SR_get_report_block(evd->packet,0);
}else if (rtcp_is_RR(evd->packet)){
rb=rtcp_RR_get_report_block(evd->packet,0);
}
if (rb){
stream_mgr->rtcp_count++;
if (stream_mgr->type==MSVideo && stream_mgr->video_stream->ms.rc_enable){
const MSQosAnalyzer *analyzer=ms_bitrate_controller_get_qos_analyzer(stream_mgr->video_stream->ms.rc);
if (analyzer->type==MSQosAnalyzerAlgorithmStateful){
const MSStatefulQosAnalyzer *stateful_analyzer=((const MSStatefulQosAnalyzer*)analyzer);
stream_mgr->adaptive_stats.loss_estim=stateful_analyzer->network_loss_rate;
stream_mgr->adaptive_stats.congestion_bw_estim=stateful_analyzer->congestion_bandwidth;
}
}
}
static void qos_analyzer_on_action_suggested(void *user_data, int datac, const char** datav){
stream_manager_t *mgr = (stream_manager_t*)user_data;
mgr->rtcp_count++;
ms_error("MSStatefulQosAnalyzer bw_per_seqnum one more %d", mgr->rtcp_count);
if (mgr->type==MSVideo && mgr->video_stream->ms.rc_enable){
const MSQosAnalyzer *analyzer=ms_bitrate_controller_get_qos_analyzer(mgr->video_stream->ms.rc);
if (analyzer->type==MSQosAnalyzerAlgorithmStateful){
const MSStatefulQosAnalyzer *stateful_analyzer=((const MSStatefulQosAnalyzer*)analyzer);
mgr->adaptive_stats.loss_estim=stateful_analyzer->network_loss_rate;
mgr->adaptive_stats.congestion_bw_estim=stateful_analyzer->congestion_bandwidth;
}
ortp_event_destroy(ev);
}
}
......@@ -262,6 +237,9 @@ void start_adaptive_stream(MSFormatType type, stream_manager_t ** pmarielle, str
#endif
}
ms_qos_analyzer_set_on_action_suggested(ms_bitrate_controller_get_qos_analyzer(marielle_ms->rc),
qos_analyzer_on_action_suggested,
*pmarielle);
rtp_session_enable_network_simulation(margaux_ms->sessions.rtp_session,&params);
}
......@@ -281,7 +259,7 @@ static void iterate_adaptive_stream(stream_manager_t * marielle, stream_manager_
while ((!current||*current<expected) && retry++ <timeout_ms/100) {
media_stream_iterate(marielle_ms);
media_stream_iterate(margaux_ms);
handle_queue_events(marielle);
// handle_queue_events(marielle);
if (retry%10==0) {
ms_message("stream [%p] bandwidth usage: [d=%.1f,u=%.1f] kbit/sec" ,
marielle_ms, media_stream_get_down_bw(marielle_ms)/1000, media_stream_get_up_bw(marielle_ms)/1000);
......@@ -427,7 +405,7 @@ static void adaptive_speex16_audio_stream() {
stream_manager_t * marielle, * margaux;
start_adaptive_stream(MSAudio, &marielle, &margaux, SPEEX16_PAYLOAD_TYPE, 32000, EDGE_BW, 0, 0, 0);
iterate_adaptive_stream(marielle, margaux, 15000, NULL, 0);
iterate_adaptive_stream(marielle, margaux, 15000, &marielle->rtcp_count, 7);
bw_usage=media_stream_get_up_bw(&marielle->audio_stream->ms)*1./EDGE_BW;
CU_ASSERT_IN_RANGE(bw_usage, 1.f, 5.f);
stop_adaptive_stream(marielle,margaux);
......@@ -443,13 +421,13 @@ static void adaptive_pcma_audio_stream() {
// yet non-adaptive codecs cannot respect low throughput limitations
start_adaptive_stream(MSAudio, &marielle, &margaux, PCMA8_PAYLOAD_TYPE, 8000, EDGE_BW, 0, 0, 0);
iterate_adaptive_stream(marielle, margaux, 10000, NULL, 0);
iterate_adaptive_stream(marielle, margaux, 10000, &marielle->rtcp_count, 7);
bw_usage=media_stream_get_up_bw(&marielle->audio_stream->ms)*1./EDGE_BW;
CU_ASSERT_IN_RANGE(bw_usage,6.f, 8.f); // this is bad!
stop_adaptive_stream(marielle,margaux);
start_adaptive_stream(MSAudio, &marielle, &margaux, PCMA8_PAYLOAD_TYPE, 8000, THIRDGENERATION_BW, 0, 0, 0);
iterate_adaptive_stream(marielle, margaux, 10000, NULL, 0);
iterate_adaptive_stream(marielle, margaux, 10000, &marielle->rtcp_count, 7);
bw_usage=media_stream_get_up_bw(&marielle->audio_stream->ms)*1./THIRDGENERATION_BW;
CU_ASSERT_IN_RANGE(bw_usage, .3f, .5f);
stop_adaptive_stream(marielle,margaux);
......@@ -467,7 +445,7 @@ static void upload_bandwidth_computation() {
for (i = 0; i < 5; i++){
rtp_session_set_duplication_ratio(marielle->audio_stream->ms.sessions.rtp_session, i);
iterate_adaptive_stream(marielle, margaux, 100000, &marielle->rtcp_count, 2*(i+1));
iterate_adaptive_stream(marielle, margaux, 5000, NULL, 0);
/*since PCMA uses 80kbit/s, upload bandwidth should just be 80+80*duplication_ratio kbit/s */
CU_ASSERT_TRUE(fabs(rtp_session_get_send_bandwidth(marielle->audio_stream->ms.sessions.rtp_session)/1000. - 80.*(i+1)) < 1.f);
}
......@@ -482,25 +460,25 @@ static void adaptive_vp8() {
stream_manager_t * marielle, * margaux;
start_adaptive_stream(MSVideo, &marielle, &margaux, VP8_PAYLOAD_TYPE, 300000, 0, 0, 50,0);
iterate_adaptive_stream(marielle, margaux, 100000, &marielle->rtcp_count, 12);
iterate_adaptive_stream(marielle, margaux, 100000, &marielle->rtcp_count, 7);
CU_ASSERT_IN_RANGE(marielle->adaptive_stats.loss_estim, 0, 1);
CU_ASSERT_TRUE(marielle->adaptive_stats.congestion_bw_estim > 200);
stop_adaptive_stream(marielle,margaux);
start_adaptive_stream(MSVideo, &marielle, &margaux, VP8_PAYLOAD_TYPE, 300000, 0, 25, 50, 0);
iterate_adaptive_stream(marielle, margaux, 100000, &marielle->rtcp_count, 12);
iterate_adaptive_stream(marielle, margaux, 100000, &marielle->rtcp_count, 7);
CU_ASSERT_IN_RANGE(marielle->adaptive_stats.loss_estim, 20, 30);
CU_ASSERT_TRUE(marielle->adaptive_stats.congestion_bw_estim > 200);
stop_adaptive_stream(marielle,margaux);
start_adaptive_stream(MSVideo, &marielle, &margaux, VP8_PAYLOAD_TYPE, 300000, 70000,0, 50,0);
iterate_adaptive_stream(marielle, margaux, 100000, &marielle->rtcp_count, 12);
iterate_adaptive_stream(marielle, margaux, 100000, &marielle->rtcp_count, 7);
CU_ASSERT_IN_RANGE(marielle->adaptive_stats.loss_estim, 0, 2);
CU_ASSERT_IN_RANGE(marielle->adaptive_stats.congestion_bw_estim, 50, 95);
stop_adaptive_stream(marielle,margaux);
start_adaptive_stream(MSVideo, &marielle, &margaux, VP8_PAYLOAD_TYPE, 300000, 100000,15, 50,0);
iterate_adaptive_stream(marielle, margaux, 100000, &marielle->rtcp_count, 12);
iterate_adaptive_stream(marielle, margaux, 100000, &marielle->rtcp_count, 7);
CU_ASSERT_IN_RANGE(marielle->adaptive_stats.loss_estim, 10, 20);
CU_ASSERT_IN_RANGE(marielle->adaptive_stats.congestion_bw_estim, 80, 125);
stop_adaptive_stream(marielle,margaux);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment