ftl_status_t media_speed_test()

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;
}