// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "cast/standalone_sender/streaming_video_encoder.h"

#include "util/chrono_helpers.h"

namespace openscreen {
namespace cast {

StreamingVideoEncoder::StreamingVideoEncoder(const Parameters& params,
                                             TaskRunner* task_runner,
                                             Sender* sender)
    : params_(params), main_task_runner_(task_runner), sender_(sender) {
  OSP_DCHECK_LE(1, params_.num_encode_threads);
  OSP_DCHECK_LE(kMinQuantizer, params_.min_quantizer);
  OSP_DCHECK_LE(params_.min_quantizer, params_.max_cpu_saver_quantizer);
  OSP_DCHECK_LE(params_.max_cpu_saver_quantizer, params_.max_quantizer);
  OSP_DCHECK_LE(params_.max_quantizer, kMaxQuantizer);
  OSP_DCHECK_LT(0.0, params_.max_time_utilization);
  OSP_DCHECK_LE(params_.max_time_utilization, 1.0);
  OSP_DCHECK(main_task_runner_);
  OSP_DCHECK(sender_);
}

StreamingVideoEncoder::~StreamingVideoEncoder() {}

void StreamingVideoEncoder::UpdateSpeedSettingForNextFrame(const Stats& stats) {
  OSP_DCHECK_EQ(std::this_thread::get_id(), encode_thread_.get_id());

  // Combine the speed setting that was used to encode the last frame, and the
  // quantizer the encoder chose into a single speed metric.
  const double speed = current_speed_setting_ +
                       kEquivalentEncodingSpeedStepPerQuantizerStep *
                           std::max(0, stats.quantizer - params_.min_quantizer);

  // Like |Stats::perfect_quantizer|, this computes a "hindsight" speed setting
  // for the last frame, one that may have potentially allowed for a
  // better-quality quantizer choice by the encoder, while also keeping CPU
  // utilization within budget.
  const double perfect_speed =
      speed * stats.time_utilization() / params_.max_time_utilization;

  // Update the ideal speed setting, to be used for the next frame. An
  // exponentially-decaying weighted average is used here to smooth-out noise.
  // The weight is based on the duration of the frame that was encoded.
  constexpr Clock::duration kDecayHalfLife = milliseconds(120);
  const double ticks = stats.frame_duration.count();
  const double weight = ticks / (ticks + kDecayHalfLife.count());
  ideal_speed_setting_ =
      weight * perfect_speed + (1.0 - weight) * ideal_speed_setting_;
  OSP_DCHECK(std::isfinite(ideal_speed_setting_));
}

}  // namespace cast
}  // namespace openscreen
