in libftl/media.c [409:579]
ftl_status_t media_speed_test(ftl_stream_configuration_private_t *ftl, int speed_kbps, int duration_ms, speed_test_t *results) {
ftl_media_component_common_t *mc = &ftl->audio.media_component;
ftl_media_config_t *media = &ftl->media;
int64_t bytes_sent = 0;
int error = 0;
int effective_kbps = -1;
ftl_status_t retval = FTL_SPEED_TEST_ABORTED;
int64_t transmit_level = MAX_MTU;
unsigned char data[MAX_MTU];
int bytes_per_ms;
int64_t total_ms = 0;
struct timeval stop_tv, start_tv, delta_tv, sendToTimeLoopTime_tv;
float packet_loss = 0.f;
int64_t ms_elapsed;
int64_t total_sent = 0;
int64_t pkts_sent = 0;
ping_pkt_t *ping;
nack_slot_t slot;
uint8_t fmt = 1; //generic nack
uint8_t ptype = PING_PTYPE;
int wait_retries;
int initial_rtt, final_rtt;
ftl_set_state(ftl, FTL_SPEED_TEST);
if (!ftl_get_state(ftl, FTL_MEDIA_READY)) {
ftl_clear_state(ftl, FTL_SPEED_TEST);
return retval;
}
media_enable_nack(ftl, mc->ssrc, FALSE);
ftl_set_state(ftl, FTL_DISABLE_TX_PING_PKTS);
ftl->video.has_sent_first_frame = TRUE;
ping = (ping_pkt_t*)slot.packet;
int rtp_hdr_len = 0;
slot.len = sizeof(ping_pkt_t);
ping->header = htonl((2 << 30) | (fmt << 24) | (ptype << 16) | sizeof(ping_pkt_t));
// Send ping packet first to get an accurate estimate of rtt under ideal conditions.
// We send it multiplies times to try to ensure one makes it on poor connections.
ftl->media.last_rtt_delay = -1;
gettimeofday(&ping->xmit_time, NULL);
_media_send_slot(ftl, &slot);
_media_send_slot(ftl, &slot);
_media_send_slot(ftl, &slot);
wait_retries = 5;
while ((initial_rtt = ftl->media.last_rtt_delay) < 0 && wait_retries-- > 0) {
sleep_ms(25);
};
results->starting_rtt = (wait_retries <= 0) ? -1 : ftl->media.last_rtt_delay;
int64_t initial_nack_cnt = mc->stats.nack_requests;
gettimeofday(&start_tv, NULL);
bytes_per_ms = speed_kbps * 1000 / 8 / 1000;
while (total_ms < duration_ms && !error) {
if (transmit_level <= 0) {
sleep_ms(MAX_MTU / bytes_per_ms + 1);
}
gettimeofday(&stop_tv, NULL);
timeval_subtract(&delta_tv, &stop_tv, &start_tv);
ms_elapsed = (int64_t)timeval_to_ms(&delta_tv);
transmit_level += ms_elapsed * bytes_per_ms;
total_ms += ms_elapsed;
start_tv = stop_tv;
while (transmit_level > 0) {
pkts_sent++;
if ((bytes_sent = media_send_audio(ftl, 0, data, sizeof(data))) < sizeof(data)) {
error = 1;
break;
}
// Sendto (which is called in media_send_audio) can block if the computer's network connection is bad
// and the local OS send buffer is full. We want this behavior when streaming normally for the send thread
// to throttle the amount of data we send, but during the speed test this causes us to block the send loop and makes
// the speed test too long and return inaccurate values.
gettimeofday(&sendToTimeLoopTime_tv, NULL);
timeval_subtract(&delta_tv, &sendToTimeLoopTime_tv, &start_tv);
int64_t ms_timeSinceLoopStart = (int64_t)timeval_to_ms(&delta_tv);
if (ms_timeSinceLoopStart + ms_elapsed > duration_ms)
{
total_ms = duration_ms;
break;
}
total_sent += bytes_sent;
transmit_level -= bytes_sent;
}
}
if (!error) {
// After the test send another ping packet to detect rtt.
// We might need to send a few of these to make sure one makes it
// after we burst the network with packets in the test.
ftl->media.last_rtt_delay = -1;
wait_retries = 2000 / PING_TX_INTERVAL_MS; // waiting up to 2s for ping to come back
while (ftl->media.last_rtt_delay < 0 && wait_retries-- > 0)
{
// Send the ping packet
gettimeofday(&ping->xmit_time, NULL);
_media_send_slot(ftl, &slot);
// Sleep for a bit.
sleep_ms(PING_TX_INTERVAL_MS);
}
final_rtt = ftl->media.last_rtt_delay;
results->ending_rtt = (wait_retries <= 0) ? -1 : ftl->media.last_rtt_delay;
//if we lost a ping packet ignore rtt, if the final rtt is lower than the initial ignore
if (initial_rtt < 0 || final_rtt < 0 || final_rtt < initial_rtt) {
initial_rtt = final_rtt = 0;
}
//if we didnt get the last ping packet assume the worst for rtt
if (wait_retries <= 0) {
initial_rtt = 0;
final_rtt = 2000;
}
int64_t lost_pkts = mc->stats.nack_requests - initial_nack_cnt;
float pkt_loss_percent = (float)lost_pkts / (float)pkts_sent;
float adjusted_bytes_sent = (float)total_sent * (1.f - pkt_loss_percent);
int64_t actual_send_time = total_ms + final_rtt - initial_rtt;
effective_kbps = (int)(((float)adjusted_bytes_sent * 8.f * 1000.f / (float)actual_send_time) / 1000.f);
results->pkts_sent = (int)pkts_sent;
results->nack_requests = (int)lost_pkts;
results->lost_pkts = (int)lost_pkts;
results->bytes_sent = (int)total_sent;
results->duration_ms = (int)actual_send_time;
results->peak_kbps = effective_kbps;
FTL_LOG(ftl, FTL_LOG_ERROR, "Sent %d bytes in %d ms; send packets %d lost %d packets; (first rtt: %d, last %d). Estimated peak bitrate %d kbps\n",
results->bytes_sent, results->duration_ms, results->pkts_sent, results->lost_pkts, initial_rtt, final_rtt, results->peak_kbps);
retval = FTL_SUCCESS;
}
// Reset all vars that were effected by the test.
mc->seq_num = 0;
mc->xmit_seq_num = 0;
mc->timestamp = 0;
mc->producer = 0;
mc->consumer = 0;
mc->base_dts_usec = -1;
_clear_stats(&mc->stats);
ftl->media.sender_report_base_ntp.tv_sec = 0;
ftl->media.sender_report_base_ntp.tv_usec = 0;
ftl->video.has_sent_first_frame = FALSE;
media_enable_nack(ftl, mc->ssrc, TRUE);
ftl_clear_state(ftl, FTL_DISABLE_TX_PING_PKTS);
ftl_clear_state(ftl, FTL_SPEED_TEST);
return retval;
}