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