OS_THREAD_ROUTINE adaptive_bitrate_thread()

in libftl/media.c [1660:1987]


OS_THREAD_ROUTINE adaptive_bitrate_thread(void* data)
{
  ftl_adaptive_bitrate_thread_params_t *params = (ftl_adaptive_bitrate_thread_params_t *)data;
  ftl_stream_configuration_private_t* ftl = (ftl_stream_configuration_private_t*)params->handle->priv;

  // We choose a duration of BW_CHECK_DURATION_MS milliseconds to estimate bandiwidth conditions. The duration is sampled at a period of 
  // STREAM_STATS_CAPTURE_MS. Hence the number of stats we save in our circular buffer is MAX_STAT_SIZE.
  assert(MAX_STAT_SIZE == BW_CHECK_DURATION_MS / STREAM_STATS_CAPTURE_MS);

  FTL_LOG(params->handle->priv, FTL_LOG_INFO, "Starting adaptive bitrate thread");

  // Circular buffers to hold bw stats data queried via ftl_get_video_params.
  // The stats data shows data aggregated over the last c_ulBwCheckDurationMs milliseconds.
  uint64_t nacks_received[MAX_STAT_SIZE] = {0};
  uint64_t frames_sent[MAX_STAT_SIZE] = {0};
  uint64_t rtts_received[MAX_STAT_SIZE] = {0};
  uint64_t frames_dropped[MAX_STAT_SIZE] = {0};
  float queue_fullness = 0; // queue fullness doesnt need to be aggregated. If it goes above a threshold value we deem bw unstable.

  uint32_t current_position_of_circular_buffer = 0;
  BOOL circular_buffer_is_full = FALSE;

  uint64_t last_frames_sent_recorded = 0;
  uint64_t last_nacks_received_recorded = 0;
  uint64_t last_frames_dropped_recorded = 0;
  uint64_t last_rtt_received = 0;

  ftl_get_video_stats(
    params->handle,
    &last_frames_sent_recorded,
    &last_nacks_received_recorded,
    &last_rtt_received,
    &last_frames_dropped_recorded,
    &queue_fullness
  );
  // Current bitrate encoding as a percentage of the original encoding bitrate.
  uint64_t current_encoding_bitrate = params->initial_encoding_bitrate;

  struct timeval last_bitrate_upgrade_time, bw_upgrade_freeze_start_time;
  gettimeofday(&last_bitrate_upgrade_time, NULL);
  bw_upgrade_freeze_start_time = (struct timeval) { 0 };

  BOOL bitrate_changed = FALSE;
  BOOL attempt_to_revert_to_stable_bandwidth_first = FALSE;

  // If a bitrate is deemed as stable after update, we report it to telemetry.
  //  bitrate is only deemed stable when we reach back to original bitrate, or when we revert to a bitrate after an excessive upgrade.
  BOOL check_bitrate_for_stability = FALSE;

  while (1)
  {
    os_semaphore_pend(&ftl->bitrate_thread_shutdown, 0);
    if (!ftl_get_state(params->handle->priv, FTL_BITRATE_THRD))
    {
      break;
    }

    uint64_t nacks_received_recorded = 0;
    uint64_t frames_sent_recorded = 0;
    uint64_t rtt_received = 0;
    uint64_t frames_dropped_recorded = 0;

    ftl_get_video_stats(params->handle, &frames_sent_recorded, &nacks_received_recorded, &rtt_received, &frames_dropped_recorded, &queue_fullness);

    uint64_t nacks_received_since_last_check = nacks_received_recorded - last_nacks_received_recorded;
    uint64_t frames_sent_since_last_check = frames_sent_recorded - last_frames_sent_recorded;
    uint64_t frames_dropped_since_last_check = frames_dropped_recorded - last_frames_dropped_recorded;

    last_nacks_received_recorded = nacks_received_recorded;
    last_frames_sent_recorded = frames_sent_recorded;
    last_frames_dropped_recorded = frames_dropped_recorded;

    nacks_received[current_position_of_circular_buffer] = nacks_received_since_last_check;
    frames_sent[current_position_of_circular_buffer] = frames_sent_since_last_check;
    rtts_received[current_position_of_circular_buffer] = rtt_received;
    frames_dropped[current_position_of_circular_buffer] = frames_dropped_since_last_check;

    // Once circular buffer is full, set the flag as we now have enough data to check bandwidth is constrained.
    if (current_position_of_circular_buffer + 1 >= MAX_STAT_SIZE)
    {
      circular_buffer_is_full = TRUE;
    }
    // Update position in circular buffer.
    current_position_of_circular_buffer = (current_position_of_circular_buffer + 1) % MAX_STAT_SIZE;

    // This conditions ensures that we have stats of the last c_ulBwCheckDurationMs milliseconds.
    if (circular_buffer_is_full)
    {
      uint64_t nacks_received_total = 0;
      uint64_t frames_sent_total = 0;
      uint64_t total_rtt = 0;
      float avg_rtt = 0;
      uint64_t frames_dropped_total = 0;
      uint64_t avg_frames_dropped_per_second = 0;
      float nacks_to_frames_ratio = 0;
      int i;

      // Count all nacks received for the last c_ulBwCheckDurationMs milliseconds
      for (i = 0; i < MAX_STAT_SIZE; i++)
      {
        nacks_received_total += nacks_received[i];
      }
      // Count all frames sent over the last c_ulBwCheckDurationMs milliseconds
      for (i = 0; i< MAX_STAT_SIZE; i++)
      {
        frames_sent_total += frames_sent[i];
      }

      if (frames_sent_total != 0)
      {
        nacks_to_frames_ratio = (float)nacks_received_total / (float)frames_sent_total;
      }

      for (i = 0; i< MAX_STAT_SIZE; i++)
      {
        total_rtt += rtts_received[i];
      }
      avg_rtt = (float)total_rtt / (float)MAX_STAT_SIZE;

      for (i = 0; i < MAX_STAT_SIZE; i++)
      {
        frames_dropped_total += frames_dropped[i];
      }
      avg_frames_dropped_per_second = (frames_dropped_total / (BW_CHECK_DURATION_MS / 1000));

      // Check if bandwidth is constrained and bitrate reduction is required. The bandwidth can be constrained for two reasons.
      // Either the available bandwidth has decreased, or we tried to upgrade the bitrate and its too excessive.
      if (is_bitrate_reduction_required(nacks_to_frames_ratio, avg_rtt, queue_fullness))
      {
        FTL_LOG(params->handle->priv, FTL_LOG_INFO, "Bitrate reduction required. Nacks Received %ull , Frames Sent %ull rtt %4.2f queue_fullness %4.2f",
          nacks_received_total,
          frames_sent_total,
          avg_rtt,
          queue_fullness
        );

        // If we had previously upgraded the bitrate and we started seeing the 
        // constrain within c_iMaxSecondsToDeemBitrateUpgradeExcessive seconds, it means our bitrate upgrade was excessiver. 
        // we should revert back to a lower bitrate and freeze all upgrades for c_uBitrateUpgradeFreezeTimeMs milliseconds.
        if (attempt_to_revert_to_stable_bandwidth_first && get_ms_elapsed_since(&last_bitrate_upgrade_time) < MAX_MS_TO_DEEM_UPGRADE_EXCESSIVE)
        {
          FTL_LOG(params->handle->priv, FTL_LOG_INFO, "Reverting to a stable bitrate and freezing upgrade");
          uint64_t recommended_bitrate = compute_recommended_bitrate(current_encoding_bitrate, params->max_encoding_bitrate, params->min_encoding_bitrate, FTL_UPGRADE_EXCESSIVE);

          BOOL changeBitrateResult = params->change_bitrate_callback(params->context, recommended_bitrate);

          if (changeBitrateResult)
          {
            ftl_bitrate_changed_msg_t msg =
            {
              FTL_BITRATE_DECREASED,
              FTL_UPGRADE_EXCESSIVE,
              recommended_bitrate,
              current_encoding_bitrate,
              0.f,
              avg_rtt,
              avg_frames_dropped_per_second,
              queue_fullness
            };
            ftl_status_msg_t status_msg;
            status_msg.type = FTL_BITRATE_CHANGED;
            status_msg.msg.bitrate_changed_msg = msg;
            enqueue_status_msg(params->handle->priv, &status_msg);

            bitrate_changed = TRUE;
            check_bitrate_for_stability = TRUE;
            attempt_to_revert_to_stable_bandwidth_first = FALSE;
            current_encoding_bitrate = recommended_bitrate;
            gettimeofday(&bw_upgrade_freeze_start_time, NULL);
          }
        }
        // This means the available bandiwdth seems to have decreased and we need to reduce our bitrate to comply.
        else
        {
          uint64_t recommended_bitrate = compute_recommended_bitrate(current_encoding_bitrate, params->max_encoding_bitrate, params->min_encoding_bitrate, FTL_BANDWIDTH_CONSTRAINED);
          BOOL changeBitrateResult = params->change_bitrate_callback(params->context, recommended_bitrate);
          // We had to lower bitrate. Bitrate is not stable.
          check_bitrate_for_stability = FALSE;
          if (changeBitrateResult)
          {
            ftl_bitrate_changed_msg_t msg =
            {
              FTL_BITRATE_DECREASED,
              FTL_BANDWIDTH_CONSTRAINED,
              recommended_bitrate,
              current_encoding_bitrate,
              nacks_to_frames_ratio,
              avg_rtt,
              avg_frames_dropped_per_second,
              queue_fullness
            };
            ftl_status_msg_t status_msg;
            status_msg.type = FTL_BITRATE_CHANGED;
            status_msg.msg.bitrate_changed_msg = msg;
            enqueue_status_msg(params->handle->priv, &status_msg);

            bitrate_changed = TRUE;
            current_encoding_bitrate = recommended_bitrate;
          }
        }
      }
      // If bandwidth is stable and we are haven't frozen bitrate upgrades due to excessive 
      // bitrate upgrade in the last BwUpgradeFreezeTime millisecods, we upgrade the bitrate.
      else if (is_bw_stable(nacks_to_frames_ratio, avg_rtt, avg_frames_dropped_per_second, queue_fullness))
      {
        if (get_ms_elapsed_since(&bw_upgrade_freeze_start_time) > 180000)
        {
          uint64_t recommended_bitrate = compute_recommended_bitrate(current_encoding_bitrate, params->max_encoding_bitrate, params->min_encoding_bitrate, FTL_BANDWIDTH_AVAILABLE);
          if (recommended_bitrate != current_encoding_bitrate)
          {
            attempt_to_revert_to_stable_bandwidth_first = TRUE;

            BOOL changeBitrateResult = params->change_bitrate_callback(params->context, recommended_bitrate);
            if (changeBitrateResult)
            {
              ftl_bitrate_changed_msg_t msg =
              {
                FTL_BITRATE_INCREASED,
                FTL_BANDWIDTH_AVAILABLE,
                recommended_bitrate,
                current_encoding_bitrate,
                nacks_to_frames_ratio,
                avg_rtt,
                avg_frames_dropped_per_second,
                queue_fullness
              };
              ftl_status_msg_t status_msg;
              status_msg.type = FTL_BITRATE_CHANGED;
              status_msg.msg.bitrate_changed_msg = msg;
              enqueue_status_msg(params->handle->priv, &status_msg);

              bitrate_changed = TRUE;
              current_encoding_bitrate = recommended_bitrate;
              // We have reached a MAX_SUPPORTED_BITRATE_BPS. Check for stbility.
              if (recommended_bitrate == params->max_encoding_bitrate)
              {
                check_bitrate_for_stability = TRUE;
              }
              gettimeofday(&last_bitrate_upgrade_time, NULL);
            }
          }
        }
      }
      // If bitrate was changed by one of the steps above, 
      // we sleep for a cooldown period and clear our circular buffers.
      if (bitrate_changed)
      {
        // Clear out the circular buffer as we dont want it to impact our calculations further.
        circular_buffer_is_full = FALSE;
        current_position_of_circular_buffer = 0;

        // set the peak kbps for throttling
        ftl_media_component_common_t *video = &ftl->video.media_component;
        video->peak_kbps = (int)(5 * current_encoding_bitrate / 1000);

        // Sleep for a c_ulBitrateChangedCooldownIntervalMs period. If hBroadcastTerminated signal is received exit.
        os_semaphore_pend(&ftl->bitrate_thread_shutdown, BITRATE_CHANGED_COOLDOWN_INTERVAL_MS);
        if (!ftl_get_state(params->handle->priv, FTL_BITRATE_THRD))
        {
          break;
        }
        // Update ullLastFramesSentRecorded and ullLastNacksReceivedRecorded, so the cool down has no impact on our calculations.
        ftl_get_video_stats(params->handle, &last_frames_sent_recorded, &last_nacks_received_recorded, &rtt_received, &last_frames_dropped_recorded, &queue_fullness);
        bitrate_changed = FALSE;
      }
      else
      {
        if (check_bitrate_for_stability)
        {
          FTL_LOG(params->handle->priv, FTL_LOG_INFO, "Stable Bitrate acheived");

          check_bitrate_for_stability = FALSE;
          if (current_encoding_bitrate == params->max_encoding_bitrate)
          {
            //Uncomment the following 3 lines to demo adaptive bitrate
            //FTL_LOG(params->handle->priv, FTL_LOG_INFO, "Zapping back to low value for demo.");
            //current_encoding_bitrate = 512 * 1000;
            //params->change_bitrate_callback(params->context, 512 * 1000);

            ftl_bitrate_changed_msg_t msg =
            {
              FTL_BITRATE_STABILIZED,
              FTL_STABILIZE_ON_ORIGINAL_BITRATE,
              params->max_encoding_bitrate,
              current_encoding_bitrate,
              nacks_to_frames_ratio,
              avg_rtt,
              avg_frames_dropped_per_second,
              queue_fullness
            };
            ftl_status_msg_t status_msg;
            status_msg.type = FTL_BITRATE_CHANGED;
            status_msg.msg.bitrate_changed_msg = msg;
            enqueue_status_msg(params->handle->priv, &status_msg);
          }
          else
          {
            ftl_bitrate_changed_msg_t msg =
            {
              FTL_BITRATE_STABILIZED,
              FTL_STABILIZE_ON_LOWER_BITRATE,
              current_encoding_bitrate,
              current_encoding_bitrate,
              nacks_to_frames_ratio,
              avg_rtt,
              avg_frames_dropped_per_second,
              queue_fullness
            };
            ftl_status_msg_t status_msg;
            status_msg.type = FTL_BITRATE_CHANGED;
            status_msg.msg.bitrate_changed_msg = msg;
            enqueue_status_msg(params->handle->priv, &status_msg);
          }
        }
      }
    }

    // Sleep for c_ulStreamStatsCaptureMs before capturing the next stats
    os_semaphore_pend(&ftl->bitrate_thread_shutdown, STREAM_STATS_CAPTURE_MS);
    if (!ftl_get_state(params->handle->priv, FTL_BITRATE_THRD))
    {
      break;
    }
  }
  FTL_LOG(params->handle->priv, FTL_LOG_INFO, "Shutting down bitrate thread");
  free(params);
  return 0;
}