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

#include "media/filters/audio_clock.h"

#include <stdint.h>
#include <stddef.h>

#include <algorithm>
#include <cmath>

#include "base/check_op.h"
#include "base/numerics/safe_conversions.h"

namespace media {

AudioClock::AudioClock(base::TimeDelta start_timestamp, int sample_rate)
    : start_timestamp_(start_timestamp),
      microseconds_per_frame_(
          static_cast<double>(base::Time::kMicrosecondsPerSecond) /
          sample_rate),
      front_timestamp_micros_(start_timestamp.InMicrosecondsF()),
      back_timestamp_micros_(start_timestamp.InMicrosecondsF()) {}

AudioClock::~AudioClock() = default;

void AudioClock::WroteAudio(int frames_written,
                            int frames_requested,
                            int delay_frames,
                            double playback_rate) {
  CHECK_GE(frames_written, 0);
  CHECK_LE(frames_written, frames_requested);
  CHECK_GE(delay_frames, 0);
  CHECK_GE(playback_rate, 0);

  // First write: initialize buffer with silence.
  if (start_timestamp_.InMicrosecondsF() == front_timestamp_micros_ &&
      buffered_.empty()) {
    PushBufferedAudioData(delay_frames, 0.0);
  }

  // Move frames from |buffered_| into the computed timestamp based on
  // |delay_frames|.
  int64_t frames_played =
      std::max(INT64_C(0), total_buffered_frames_ - delay_frames);
  PopBufferedAudioData(frames_played);

  PushBufferedAudioData(frames_written, playback_rate);
  PushBufferedAudioData(frames_requested - frames_written, 0.0);

  // Update our front and back timestamps.  The back timestamp is considered the
  // authoritative source of truth, so base the front timestamp on range of data
  // buffered.  Doing so avoids accumulation errors on the front timestamp.
  back_timestamp_micros_ +=
      frames_written * playback_rate * microseconds_per_frame_;

  // Don't let front timestamp move earlier in time, as could occur due to delay
  // frames pushed in the first write, above.
  front_timestamp_micros_ =
      std::max(front_timestamp_micros_,
               back_timestamp_micros_ - ComputeBufferedMediaDurationMicros());
  DCHECK_GE(front_timestamp_micros_, start_timestamp_.InMicrosecondsF());
  DCHECK_LE(front_timestamp_micros_, back_timestamp_micros_);
}

void AudioClock::CompensateForSuspendedWrites(base::TimeDelta elapsed,
                                              int delay_frames) {
  const int64_t frames_elapsed = base::ClampRound<int64_t>(
      elapsed.InMicrosecondsF() / microseconds_per_frame_);

  // No need to do anything if we're within the limits of our played out audio
  // or there are no delay frames, the next WroteAudio() call will expire
  // everything correctly.
  if (frames_elapsed < total_buffered_frames_ || !delay_frames)
    return;

  // Otherwise, flush everything and prime with the delay frames.
  WroteAudio(0, 0, 0, 0);
  DCHECK(buffered_.empty());
  PushBufferedAudioData(delay_frames, 0.0);
}

base::TimeDelta AudioClock::TimeUntilPlayback(base::TimeDelta timestamp) const {
  // Use front/back_timestamp() methods rather than internal members. The public
  // methods round to the nearest microsecond for conversion to TimeDelta and
  // the rounded value will likely be used by the caller.
  DCHECK_GE(timestamp, front_timestamp());
  DCHECK_LE(timestamp, back_timestamp());

  int64_t frames_until_timestamp = 0;
  double timestamp_us = timestamp.InMicrosecondsF();
  double media_time_us = front_timestamp().InMicrosecondsF();

  for (const auto& buffer : buffered_) {
    // Leading silence is always accounted prior to anything else.
    if (buffer.playback_rate == 0) {
      frames_until_timestamp += buffer.frames;
      continue;
    }

    // Calculate upper bound on media time for current block of buffered frames.
    double delta_us =
        buffer.frames * buffer.playback_rate * microseconds_per_frame_;
    double max_media_time_us = media_time_us + delta_us;

    // Determine amount of media time to convert to frames for current block. If
    // target timestamp falls within current block, scale the amount of frames
    // based on remaining amount of media time.
    if (timestamp_us <= max_media_time_us) {
      frames_until_timestamp +=
          buffer.frames * (timestamp_us - media_time_us) / delta_us;
      break;
    }

    media_time_us = max_media_time_us;
    frames_until_timestamp += buffer.frames;
  }

  return base::Microseconds(
      std::round(frames_until_timestamp * microseconds_per_frame_));
}

void AudioClock::ContiguousAudioDataBufferedForTesting(
    base::TimeDelta* total,
    base::TimeDelta* same_rate_total) const {
  double scaled_frames = 0;
  double scaled_frames_at_same_rate = 0;
  bool found_silence = false;
  for (size_t i = 0; i < buffered_.size(); ++i) {
    if (buffered_[i].playback_rate == 0) {
      found_silence = true;
      continue;
    }

    // Any buffered silence breaks our contiguous stretch of audio data.
    if (found_silence)
      break;

    scaled_frames += (buffered_[i].frames * buffered_[i].playback_rate);

    if (i == 0)
      scaled_frames_at_same_rate = scaled_frames;
  }

  *total = base::Microseconds(scaled_frames * microseconds_per_frame_);
  *same_rate_total =
      base::Microseconds(scaled_frames_at_same_rate * microseconds_per_frame_);
}

AudioClock::AudioData::AudioData(int64_t frames, double playback_rate)
    : frames(frames), playback_rate(playback_rate) {
}

void AudioClock::PushBufferedAudioData(int64_t frames, double playback_rate) {
  if (frames == 0)
    return;

  total_buffered_frames_ += frames;

  // Avoid creating extra elements where possible.
  if (!buffered_.empty() && buffered_.back().playback_rate == playback_rate) {
    buffered_.back().frames += frames;
    return;
  }

  buffered_.push_back(AudioData(frames, playback_rate));
}

void AudioClock::PopBufferedAudioData(int64_t frames) {
  CHECK_LE(frames, total_buffered_frames_);

  total_buffered_frames_ -= frames;

  while (frames > 0 && !buffered_.empty()) {
    int64_t frames_to_pop = std::min(buffered_.front().frames, frames);
    buffered_.front().frames -= frames_to_pop;
    if (buffered_.front().frames == 0)
      buffered_.pop_front();

    frames -= frames_to_pop;
  }

  // We can get in a bad state on some platforms. We're unsure what conditions
  // lead to this, but resetting `total_buffered_frames_` here is reasonable.
  // See crbug.com/413474566.
  if (buffered_.empty()) {
    total_buffered_frames_ = 0;
  }
}

double AudioClock::ComputeBufferedMediaDurationMicros() const {
  double scaled_frames = 0;
  for (const auto& buffer : buffered_) {
    scaled_frames += buffer.frames * buffer.playback_rate;
  }
  return scaled_frames * microseconds_per_frame_;
}

}  // namespace media
