// Copyright 2012 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/audio/cras/cras_input.h"

#include <inttypes.h>
#include <math.h>

#include <algorithm>
#include <ctime>

#include "base/compiler_specific.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/lock.h"
#include "base/time/time.h"
#include "media/audio/audio_device_description.h"
#include "media/audio/cras/audio_manager_cras_base.h"
#include "media/audio/cras/cras_util.h"
#include "media/base/audio_bus.h"
#include "media/base/audio_sample_types.h"
#include "media/base/audio_timestamp_helper.h"
#include "media/base/media_switches.h"

namespace media {

using Error = AudioInputStream::AudioInputCallback::Error;

namespace {

// Used to log errors in `CrasInputStream::Open`.
enum class StreamOpenResult {
  kCallbackOpenSuccess = 0,
  kCallbackOpenClientAlreadyOpen = 1,
  kCallbackOpenUnsupportedAudioFrequency = 2,
  kCallbackOpenUnsupportedAudioFormat = 3,
  kCallbackOpenCrasClientCreationFailed = 4,
  kCallbackOpenCannotConnectToCrasClient = 5,
  kCallbackOpenCannotRunCrasClient = 6,
  kCallbackOpenCannotSynchronizeData = 7,
  kCallbackOpenCannotFindLoopbackDevice = 8,
  kMaxValue = kCallbackOpenCannotFindLoopbackDevice
};

// Used to log errors in `CrasInputStream::Start`.
enum class StreamStartResult {
  kCallbackStartSuccess = 0,
  kCallbackStartErrorCreatingStreamParameters = 1,
  kCallbackStartErrorSettingUpStreamParameters = 2,
  kCallbackStartErrorSettingUpChannelLayout = 3,
  kCallbackStartFailedAddingStream = 4,
  kMaxValue = kCallbackStartFailedAddingStream
};

void ReportStreamOpenResult(StreamOpenResult result) {
  base::UmaHistogramEnumeration("Media.Audio.CrasInputStreamOpenSuccess",
                                result);
}

void ReportStreamStartResult(StreamStartResult result) {
  base::UmaHistogramEnumeration("Media.Audio.CrasInputStreamStartSuccess",
                                result);
}

void ReportNotifyStreamErrors(int err) {
  base::UmaHistogramSparse("Media.Audio.CrasInputStreamNotifyStreamError", err);
}

static constexpr char kVoiceIsolationEffectStateHistogramName[] =
    "Cras.StreamEffectState.VoiceIsolation";

// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
// Used to log stream effects in `CrasInputStream::Start`.
enum class StreamEffectState {
  kForceDisable = 0,
  kForceEnable = 1,
  kPlatformDefault = 2,
  kMaxValue = kPlatformDefault
};

void RecordVoiceIsolationState(StreamEffectState state) {
  base::UmaHistogramEnumeration(kVoiceIsolationEffectStateHistogramName, state);
}

}  // namespace

class CrasAudioInputStreamProxy {
 public:
  CrasAudioInputStreamProxy() : stream_(nullptr), active_stream_id_(0) {}

  CrasAudioInputStreamProxy(const CrasAudioInputStreamProxy&) = delete;
  CrasAudioInputStreamProxy& operator=(const CrasAudioInputStreamProxy&) =
      delete;

  void Detach() {
    base::AutoLock auto_lock(lock_);
    stream_ = nullptr;
    active_stream_id_ = 0;
  }

  // Re-attaches the proxy to its stream. Used when a stream is restarted via
  // Stop() then Start() without an intervening Close().
  void Attach(CrasInputStream* stream, cras_stream_id_t stream_id) {
    base::AutoLock auto_lock(lock_);
    stream_ = stream;
    active_stream_id_ = stream_id;
  }

  int SamplesReady(struct libcras_stream_cb_data* data) {
    base::AutoLock auto_lock(lock_);
    if (stream_) {
      cras_stream_id_t cb_stream_id;
      libcras_stream_cb_data_get_stream_id(data, &cb_stream_id);
      if (cb_stream_id == active_stream_id_) {
        return stream_->OnSamplesReady(data);
      }
    }
    unsigned int frames = 0;
    libcras_stream_cb_data_get_frames(data, &frames);
    return frames;
  }

  int StreamError(cras_client* client, cras_stream_id_t stream_id, int err) {
    base::AutoLock auto_lock(lock_);
    if (stream_ && stream_id == active_stream_id_) {
      return stream_->OnStreamError(client, stream_id, err);
    }
    return 0;
  }

 private:
  base::Lock lock_;
  raw_ptr<CrasInputStream> stream_ GUARDED_BY(lock_);
  cras_stream_id_t active_stream_id_ GUARDED_BY(lock_);
};

CrasInputStream::CrasInputStream(const AudioParameters& params,
                                 AudioManagerCrasBase* manager,
                                 const std::string& device_id,
                                 const AudioManager::LogCallback& log_callback)
    : audio_manager_(manager),
      params_(params),
      is_loopback_(AudioDeviceDescription::IsLoopbackDevice(device_id)),
      is_loopback_without_chrome_(
          device_id == AudioDeviceDescription::kLoopbackWithoutChromeId),
      mute_system_audio_(device_id ==
                         AudioDeviceDescription::kLoopbackWithMuteDeviceId),
#if DCHECK_IS_ON()
      recording_enabled_(false),
#endif
      glitch_reporter_(SystemGlitchReporter::StreamType::kCapture),
      log_callback_(std::move(log_callback)),
      peak_detector_(base::BindRepeating(&AudioManager::TraceAmplitudePeak,
                                         base::Unretained(audio_manager_),
                                         /*trace_start=*/true)) {
  DCHECK(audio_manager_);
  audio_bus_ = AudioBus::Create(params_);
  if (!audio_manager_->IsDefault(device_id, true)) {
    uint64_t cras_node_id;
    base::StringToUint64(device_id, &cras_node_id);
    pin_device_ = dev_index_of(cras_node_id);
  }
  // The proxy lives for the entire lifetime of this stream. Recreating it per
  // Start() would be unsafe: streams can be restarted in place (Stop() then
  // Start() without Close()), and a late libcras callback from a previous run
  // could still reference the old proxy.
  proxy_ = std::make_unique<CrasAudioInputStreamProxy>();
}

CrasInputStream::~CrasInputStream() {
  DCHECK(!client_);
}

AudioInputStream::OpenOutcome CrasInputStream::Open() {
  if (client_) {
    NOTREACHED() << "CrasInputStream already open";
  }

  // Sanity check input values.
  if (params_.sample_rate() <= 0) {
    DLOG(WARNING) << "Unsupported audio frequency.";
    ReportStreamOpenResult(
        StreamOpenResult::kCallbackOpenUnsupportedAudioFrequency);
    return OpenOutcome::kFailed;
  }

  if (AudioParameters::AUDIO_PCM_LINEAR != params_.format() &&
      AudioParameters::AUDIO_PCM_LOW_LATENCY != params_.format()) {
    DLOG(WARNING) << "Unsupported audio format.";
    ReportStreamOpenResult(
        StreamOpenResult::kCallbackOpenUnsupportedAudioFormat);
    return OpenOutcome::kFailed;
  }

  // Create the client and connect to the CRAS server.
  client_ = libcras_client_create();
  if (!client_) {
    DLOG(WARNING) << "Couldn't create CRAS client.\n";
    ReportStreamOpenResult(
        StreamOpenResult::kCallbackOpenCrasClientCreationFailed);
    client_ = nullptr;
    return OpenOutcome::kFailed;
  }

  if (libcras_client_connect_timeout(client_, kCrasConnectTimeoutMs)) {
    DLOG(WARNING) << "Couldn't connect CRAS client.\n";
    ReportStreamOpenResult(
        StreamOpenResult::kCallbackOpenCannotConnectToCrasClient);
    libcras_client_destroy(client_.ExtractAsDangling());
    client_ = nullptr;
    return OpenOutcome::kFailed;
  }

  // Then start running the client.
  if (libcras_client_run_thread(client_)) {
    DLOG(WARNING) << "Couldn't run CRAS client.\n";
    ReportStreamOpenResult(StreamOpenResult::kCallbackOpenCannotRunCrasClient);
    libcras_client_destroy(client_.ExtractAsDangling());
    client_ = nullptr;
    return OpenOutcome::kFailed;
  }

  if (is_loopback_) {
    if (libcras_client_connected_wait(client_) < 0) {
      DLOG(WARNING) << "Couldn't synchronize data.";
      // TODO(chinyue): Add a DestroyClientOnError method to de-duplicate the
      // cleanup code.
      ReportStreamOpenResult(
          StreamOpenResult::kCallbackOpenCannotSynchronizeData);
      libcras_client_destroy(client_.ExtractAsDangling());
      client_ = nullptr;
      return OpenOutcome::kFailed;
    }

    int rc;
    if (is_loopback_without_chrome_) {
      uint32_t client_types = 0;
      client_types |= 1 << CRAS_CLIENT_TYPE_CHROME;
      client_types = ~client_types;
      rc = pin_device_ = libcras_client_get_floop_dev_idx_by_client_types(
          client_, client_types);
    } else {
      if (base::FeatureList::IsEnabled(
              media::kAudioFlexibleLoopbackForSystemLoopback)) {
        rc = pin_device_ = libcras_client_get_floop_dev_idx_by_client_types(
            client_, ~(uint32_t)0);
      } else {
        rc = libcras_client_get_loopback_dev_idx(client_, &pin_device_);
      }
    }
    if (rc < 0) {
      DLOG(WARNING) << "Couldn't find CRAS loopback device "
                    << (is_loopback_without_chrome_
                            ? " for non-chrome loopback."
                            : " for full loopback.");
      ReportStreamOpenResult(
          StreamOpenResult::kCallbackOpenCannotFindLoopbackDevice);
      libcras_client_destroy(client_.ExtractAsDangling());
      client_ = nullptr;
      return OpenOutcome::kFailed;
    }
  }

  ReportStreamOpenResult(StreamOpenResult::kCallbackOpenSuccess);
  return OpenOutcome::kSuccess;
}

void CrasInputStream::Close() {
  Stop();

  if (client_) {
    libcras_client_stop(client_);
    libcras_client_destroy(client_.ExtractAsDangling());
    client_ = nullptr;
  }

  if (proxy_) {
    proxy_.reset();
  }

  // Signal to the manager that we're closed and can be removed.
  // Should be last call in the method as it deletes "this".
  audio_manager_->ReleaseInputStream(this);
}

inline bool CrasInputStream::UseCrasAec() const {
  return params_.effects() & AudioParameters::ECHO_CANCELLER;
}

inline bool CrasInputStream::UseCrasNs() const {
  return params_.effects() & AudioParameters::NOISE_SUPPRESSION;
}

inline bool CrasInputStream::UseCrasAgc() const {
  return params_.effects() & AudioParameters::AUTOMATIC_GAIN_CONTROL;
}

inline bool CrasInputStream::UseClientControlledVoiceIsolation() const {
  return params_.effects() & AudioParameters::CLIENT_CONTROLLED_VOICE_ISOLATION;
}

inline bool CrasInputStream::UseCrasVoiceIsolation() const {
  return params_.effects() & AudioParameters::VOICE_ISOLATION;
}

inline bool CrasInputStream::DspBasedAecIsAllowed() const {
  return params_.effects() & AudioParameters::ALLOW_DSP_ECHO_CANCELLER;
}

inline bool CrasInputStream::DspBasedNsIsAllowed() const {
  return params_.effects() & AudioParameters::ALLOW_DSP_NOISE_SUPPRESSION;
}

inline bool CrasInputStream::DspBasedAgcIsAllowed() const {
  return params_.effects() & AudioParameters::ALLOW_DSP_AUTOMATIC_GAIN_CONTROL;
}

inline bool CrasInputStream::IgnoreUiGains() const {
  return params_.effects() & AudioParameters::IGNORE_UI_GAINS;
}

void CrasInputStream::Start(AudioInputCallback* callback) {
  DCHECK(client_);
  DCHECK(callback);

  // Channel map to CRAS_CHANNEL, values in the same order of
  // corresponding source in Chromium defined Channels.
  static constexpr int kUnsupportedChannel = -4;
  static const int kChannelMap[] = {
      CRAS_CH_FL, CRAS_CH_FR, CRAS_CH_FC, CRAS_CH_LFE, CRAS_CH_RL, CRAS_CH_RR,
      CRAS_CH_FLC, CRAS_CH_FRC, CRAS_CH_RC, CRAS_CH_SL, CRAS_CH_SR,
      // CRAS doesn't currently define explicit mappings for all channels.
      kUnsupportedChannel, kUnsupportedChannel, kUnsupportedChannel,
      kUnsupportedChannel, kUnsupportedChannel, kUnsupportedChannel,
      kUnsupportedChannel};
  static_assert(std::size(kChannelMap) == CHANNELS_MAX + 1,
                "kChannelMap array size should match");

  // If already playing, stop before re-starting.
  if (started_) {
    return;
  }

  StartAgc();

  callback_ = callback;

  CRAS_STREAM_TYPE type = CRAS_STREAM_TYPE_DEFAULT;
  uint32_t flags = 0;
  if (params_.effects() & AudioParameters::PlatformEffectsMask::HOTWORD) {
    flags = HOTWORD_STREAM;
    type = CRAS_STREAM_TYPE_SPEECH_RECOGNITION;
  }

  unsigned int frames_per_packet = params_.frames_per_buffer();
  struct libcras_stream_params* stream_params = libcras_stream_params_create();
  if (!stream_params) {
    DLOG(ERROR) << "Error creating stream params";
    ReportStreamStartResult(
        StreamStartResult::kCallbackStartErrorCreatingStreamParameters);
    callback_->OnError(Error::kStartupFailed);
    callback_ = nullptr;
    return;
  }

  int rc = libcras_stream_params_set(
      stream_params, stream_direction_, frames_per_packet, frames_per_packet,
      type, audio_manager_->GetClientType(), flags, proxy_.get(),
      CrasInputStream::SamplesReady, CrasInputStream::StreamError,
      params_.sample_rate(), SND_PCM_FORMAT_S16, params_.channels());

  if (rc) {
    DLOG(WARNING) << "Error setting up stream parameters.";
    ReportStreamStartResult(
        StreamStartResult::kCallbackStartErrorSettingUpStreamParameters);
    callback_->OnError(Error::kStartupFailed);
    callback_ = nullptr;
    libcras_stream_params_destroy(stream_params);
    return;
  }

  // Initialize channel layout to all -1 to indicate that none of
  // the channels is set in the layout.
  int8_t layout[CRAS_CH_MAX];
  for (int8_t& channel : layout) {
    channel = -1;
  }

  // Converts to CRAS defined channels. ChannelOrder will return -1
  // for channels that are not present in params_.channel_layout().
  for (size_t i = 0; i < std::size(kChannelMap); ++i) {
    if (kChannelMap[i] != kUnsupportedChannel) {
      UNSAFE_TODO(layout[kChannelMap[i]]) =
          ChannelOrder(params_.channel_layout(), static_cast<Channels>(i));
    }
  }

  rc = libcras_stream_params_set_channel_layout(stream_params, CRAS_CH_MAX,
                                                layout);
  if (rc) {
    DLOG(WARNING) << "Error setting up the channel layout.";
    ReportStreamStartResult(
        StreamStartResult::kCallbackStartErrorSettingUpChannelLayout);
    callback_->OnError(Error::kStartupFailed);
    callback_ = nullptr;
    libcras_stream_params_destroy(stream_params);
    return;
  }

  if (UseCrasAec()) {
    libcras_stream_params_enable_aec(stream_params);
  }

  if (UseCrasNs()) {
    libcras_stream_params_enable_ns(stream_params);
  }

  if (UseCrasAgc()) {
    libcras_stream_params_enable_agc(stream_params);
  }

  if (base::FeatureList::IsEnabled(media::kCrOSSystemVoiceIsolationOption)) {
    if (UseClientControlledVoiceIsolation()) {
      if (UseCrasVoiceIsolation()) {
        libcras_stream_params_enable_voice_isolation(stream_params);
        RecordVoiceIsolationState(StreamEffectState::kForceEnable);
      } else {
        libcras_stream_params_disable_voice_isolation(stream_params);
        RecordVoiceIsolationState(StreamEffectState::kForceDisable);
      }
    } else {
      RecordVoiceIsolationState(StreamEffectState::kPlatformDefault);
    }
  }

  if (DspBasedAecIsAllowed()) {
    libcras_stream_params_allow_aec_on_dsp(stream_params);
  }

  if (DspBasedNsIsAllowed()) {
    libcras_stream_params_allow_ns_on_dsp(stream_params);
  }

  if (DspBasedAgcIsAllowed()) {
    libcras_stream_params_allow_agc_on_dsp(stream_params);
  }

  if (IgnoreUiGains()) {
    libcras_stream_params_ignore_ui_gains(stream_params);
  }

  // Adding the stream will start the audio callbacks.
  if (libcras_client_add_pinned_stream(client_, pin_device_, &stream_id_,
                                       stream_params)) {
    DLOG(WARNING) << "Failed to add the stream.";
    ReportStreamStartResult(
        StreamStartResult::kCallbackStartFailedAddingStream);
    callback_->OnError(Error::kStartupFailed);
    callback_ = nullptr;
    libcras_stream_params_destroy(stream_params);
    return;
  }

  // The stream is now fully (re)initialized, so attach the proxy right before
  // the callbacks start. A previous Stop() may have detached it. Attaching
  // only here guarantees that any forwarded callback observes a consistent
  // stream; late callbacks from the previous run were already dropped while the
  // proxy was detached.
  proxy_->Attach(this, stream_id_);

  // Mute system audio if requested.
  if (mute_system_audio_) {
    int muted;
    libcras_client_get_system_muted(client_, &muted);
    if (!muted) {
      libcras_client_set_system_mute(client_, 1);
    }
    mute_done_ = true;
  }

  // Done with config params.
  libcras_stream_params_destroy(stream_params);

  started_ = true;

  audio_manager_->RegisterSystemAecDumpSource(this);

  ReportStreamStartResult(StreamStartResult::kCallbackStartSuccess);
}

void CrasInputStream::Stop() {
  if (!client_) {
    return;
  }

  if (!callback_ || !started_) {
    return;
  }

  // Instantly fence off the CRAS thread.
  if (proxy_) {
    proxy_->Detach();
  }

  audio_manager_->DeregisterSystemAecDumpSource(this);

  if (mute_system_audio_ && mute_done_) {
    libcras_client_set_system_mute(client_, 0);
    mute_done_ = false;
  }

  StopAgc();

  // Removing the stream from the client stops audio.
  libcras_client_rm_stream(client_, stream_id_);
  stream_id_ = 0;

  ReportAndResetStats();

  started_ = false;
  callback_ = nullptr;
}

// Static callback asking for samples.  Run on high priority thread.
int CrasInputStream::SamplesReady(struct libcras_stream_cb_data* data) {
  void* usr_arg;
  libcras_stream_cb_data_get_usr_arg(data, &usr_arg);
  CrasAudioInputStreamProxy* proxy =
      static_cast<CrasAudioInputStreamProxy*>(usr_arg);
  return proxy->SamplesReady(data);
}

// Static callback for stream errors.
int CrasInputStream::StreamError(cras_client* client,
                                 cras_stream_id_t stream_id,
                                 int err,
                                 void* arg) {
  CrasAudioInputStreamProxy* proxy =
      static_cast<CrasAudioInputStreamProxy*>(arg);
  return proxy->StreamError(client, stream_id, err);
}

int CrasInputStream::OnSamplesReady(struct libcras_stream_cb_data* data) {
  unsigned int frames;
  uint8_t* buf;
  struct timespec latency;
  uint32_t overrun_frames = 0;
  struct timespec dropped_samples_duration_ts;
  base::TimeDelta dropped_samples_duration;

  libcras_stream_cb_data_get_frames(data, &frames);
  libcras_stream_cb_data_get_buf(data, &buf);
  libcras_stream_cb_data_get_latency(data, &latency);

  // The frames should be equal to CRAS actually delivered.
  CHECK_EQ(static_cast<size_t>(audio_bus_->frames()), frames);

  const size_t sample_count = static_cast<size_t>(params_.channels()) * frames;
  // SAFETY: buffer is guaranteed to be at least as large as the number of
  // frames to read.
  auto source_data =
      UNSAFE_BUFFERS(base::span(reinterpret_cast<int16_t*>(buf), sample_count));
  ReadAudio(source_data, &latency);

  // Audio glitches are checked every callback.
  libcras_stream_cb_data_get_overrun_frames(data, &overrun_frames);
  libcras_stream_cb_data_get_dropped_samples_duration(
      data, &dropped_samples_duration_ts);
  dropped_samples_duration =
      base::TimeDelta::FromTimeSpec(dropped_samples_duration_ts);
  CalculateAudioGlitches(overrun_frames, dropped_samples_duration);
  return frames;
}

int CrasInputStream::OnStreamError(cras_client* client,
                                   cras_stream_id_t stream_id,
                                   int err) {
  NotifyStreamError(err);
  return 0;
}

void CrasInputStream::ReadAudio(base::span<const int16_t> source_data,
                                const timespec* latency_ts) {
  DCHECK(callback_);

  // Update the AGC volume level once every second. Note that, |volume| is
  // also updated each time SetVolume() is called through IPC by the
  // render-side AGC.
  double normalized_volume = 0.0;
  GetAgcVolume(&normalized_volume);

  const base::TimeDelta delay =
      std::max(base::TimeDelta::FromTimeSpec(*latency_ts), base::TimeDelta());

  // The delay says how long ago the capture was, so we subtract the delay from
  // Now() to find the capture time.
  const base::TimeTicks capture_time = base::TimeTicks::Now() - delay;

  audio_bus_->FromInterleaved<SignedInt16SampleTypeTraits>(source_data);

  peak_detector_.FindPeak(audio_bus_.get());

  callback_->OnData(audio_bus_.get(), capture_time, normalized_volume,
                    glitch_info_accumulator_.GetAndReset());
}

void CrasInputStream::NotifyStreamError(int err) {
  ReportNotifyStreamErrors(err);
  if (callback_) {
    callback_->OnError(Error::kRuntimeError);
  }
}

double CrasInputStream::GetMaxVolume() {
  return 1.0f;
}

void CrasInputStream::SetVolume(double volume) {
  DCHECK(client_);

  // Set the volume ratio to CRAS's softare and stream specific gain.
  input_volume_ = volume;
  libcras_client_set_stream_volume(client_, stream_id_, input_volume_);

  // Update the AGC volume level based on the last setting above. Note that,
  // the volume-level resolution is not infinite and it is therefore not
  // possible to assume that the volume provided as input parameter can be
  // used directly. Instead, a new query to the audio hardware is required.
  // This method does nothing if AGC is disabled.
  UpdateAgcVolume();
}

double CrasInputStream::GetVolume() {
  if (!client_) {
    return 0.0;
  }

  return input_volume_;
}

bool CrasInputStream::IsMuted() {
  int muted = 0;
  libcras_client_get_system_capture_muted(client_, &muted);
  return static_cast<bool>(muted);
}

void CrasInputStream::SetOutputDeviceForAec(
    const std::string& output_device_id) {
  DCHECK(client_);

  int echo_ref_id;

  // Default device means to just use the system default output as AEC
  // reference. CRAS server side requires passing NO_DEVICE in that case.
  if (AudioDeviceDescription::IsDefaultDevice(output_device_id)) {
    echo_ref_id = NO_DEVICE;
  } else {
    uint64_t cras_node_id;
    base::StringToUint64(output_device_id, &cras_node_id);
    echo_ref_id = dev_index_of(cras_node_id);
  }
  libcras_client_set_aec_ref(client_, stream_id_, echo_ref_id);
}

void CrasInputStream::StartAecdump(base::File file) {
  FILE* stream = base::FileToFILE(std::move(file), "w");
  if (!client_) {
    return;
  }
#if DCHECK_IS_ON()
  DCHECK(!recording_enabled_);
  recording_enabled_ = true;
#endif

  libcras_client_set_aec_dump(client_, stream_id_, /*start=*/1, fileno(stream));
}

void CrasInputStream::StopAecdump() {
  if (!client_) {
    return;
  }
#if DCHECK_IS_ON()
  DCHECK(recording_enabled_);
  recording_enabled_ = false;
#endif
  libcras_client_set_aec_dump(client_, stream_id_, /*start=*/0, /*fd=*/-1);
}

void CrasInputStream::ReportAndResetStats() {
  SystemGlitchReporter::Stats stats =
      glitch_reporter_.GetLongTermStatsAndReset();

  if (!log_callback_.is_null()) {
    std::string log_message = base::StringPrintf(
        "CRAS in: (num_glitches_detected=[%d], cumulative_audio_lost=[%" PRId64
        " ms],largest_glitch=[%" PRId64 " ms])",
        stats.glitches_detected, stats.total_glitch_duration.InMilliseconds(),
        stats.largest_glitch_duration.InMilliseconds());

    log_callback_.Run(log_message);
    if (stats.glitches_detected != 0) {
      DLOG(WARNING) << log_message;
    }
  }
  last_overrun_frames_ = 0;
  last_dropped_samples_duration_ = base::TimeDelta();
}

void CrasInputStream::CalculateAudioGlitches(
    uint32_t overrun_frames,
    base::TimeDelta dropped_samples_duration) {
  // |overrun_frames| obtained from callback is the cumulative value of the
  // overwritten frames of the whole stream. Calculate the overrun frames this
  // callback and convert it to base::TimeDelta.
  DCHECK_GE(overrun_frames, last_overrun_frames_);
  uint32_t overrun_frames_this_callback = overrun_frames - last_overrun_frames_;
  base::TimeDelta overrun_glitch_duration = AudioTimestampHelper::FramesToTime(
      overrun_frames_this_callback, params_.sample_rate());

  // |dropped_samples_duration| obtained from callback is the cumulative value
  // of the dropped audio samples of the whole stream. Calculate the dropped
  // audio sample duration this callback.
  DCHECK_GE(dropped_samples_duration, last_dropped_samples_duration_);
  base::TimeDelta dropped_samples_glitch_duration =
      dropped_samples_duration - last_dropped_samples_duration_;

  base::TimeDelta glitch_duration =
      overrun_glitch_duration + dropped_samples_glitch_duration;
  glitch_reporter_.UpdateStats(glitch_duration);
  if (glitch_duration.is_positive()) {
    glitch_info_accumulator_.Add(AudioGlitchInfo::SingleBoundedSystemGlitch(
        glitch_duration, AudioGlitchInfo::Direction::kCapture));
  }

  last_overrun_frames_ = overrun_frames;
  last_dropped_samples_duration_ = dropped_samples_duration;
}

}  // namespace media
