// Copyright (c) 2012 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 "media/base/audio_renderer_mixer_input.h"

#include <cmath>

#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/trace_event/trace_event.h"
#include "media/base/audio_renderer_mixer.h"
#include "media/base/audio_renderer_mixer_pool.h"
#include "media/base/audio_timestamp_helper.h"

namespace media {

AudioRendererMixerInput::AudioRendererMixerInput(
    AudioRendererMixerPool* mixer_pool,
    int owner_id,
    const std::string& device_id,
    const url::Origin& security_origin,
    AudioLatency::LatencyType latency)
    : mixer_pool_(mixer_pool),
      started_(false),
      playing_(false),
      volume_(1.0f),
      owner_id_(owner_id),
      device_id_(device_id),
      security_origin_(security_origin),
      latency_(latency),
      mixer_(nullptr),
      callback_(nullptr),
      error_cb_(base::Bind(&AudioRendererMixerInput::OnRenderError,
                           base::Unretained(this))) {
  DCHECK(mixer_pool_);
}

AudioRendererMixerInput::~AudioRendererMixerInput() {
  DCHECK(!started_);
  DCHECK(!mixer_);
}

void AudioRendererMixerInput::Initialize(
    const AudioParameters& params,
    AudioRendererSink::RenderCallback* callback) {
  DCHECK(!started_);
  DCHECK(!mixer_);
  DCHECK(callback);

  params_ = params;
  callback_ = callback;
}

void AudioRendererMixerInput::Start() {
  DCHECK(!started_);
  DCHECK(!mixer_);
  DCHECK(callback_);  // Initialized.

  started_ = true;
  mixer_ = mixer_pool_->GetMixer(owner_id_, params_, latency_, device_id_,
                                 security_origin_, nullptr);
  if (!mixer_) {
    callback_->OnRenderError();
    return;
  }

  // Note: OnRenderError() may be called immediately after this call returns.
  mixer_->AddErrorCallback(error_cb_);
}

void AudioRendererMixerInput::Stop() {
  // Stop() may be called at any time, if Pause() hasn't been called we need to
  // remove our mixer input before shutdown.
  Pause();

  if (mixer_) {
    // TODO(dalecurtis): This is required so that |callback_| isn't called after
    // Stop() by an error event since it may outlive this ref-counted object. We
    // should instead have sane ownership semantics: http://crbug.com/151051
    mixer_->RemoveErrorCallback(error_cb_);
    mixer_pool_->ReturnMixer(mixer_);
    mixer_ = nullptr;
  }

  started_ = false;
}

void AudioRendererMixerInput::Play() {
  if (playing_ || !mixer_)
    return;

  mixer_->AddMixerInput(params_, this);
  playing_ = true;
}

void AudioRendererMixerInput::Pause() {
  if (!playing_ || !mixer_)
    return;

  mixer_->RemoveMixerInput(params_, this);
  playing_ = false;
}

bool AudioRendererMixerInput::SetVolume(double volume) {
  base::AutoLock auto_lock(volume_lock_);
  volume_ = volume;
  return true;
}

OutputDeviceInfo AudioRendererMixerInput::GetOutputDeviceInfo() {
  return mixer_
             ? mixer_->GetOutputDeviceInfo()
             : mixer_pool_->GetOutputDeviceInfo(owner_id_, 0 /* session_id */,
                                                device_id_, security_origin_);
}

bool AudioRendererMixerInput::IsOptimizedForHardwareParameters() {
  return true;
}

bool AudioRendererMixerInput::CurrentThreadIsRenderingThread() {
  return mixer_->CurrentThreadIsRenderingThread();
}

void AudioRendererMixerInput::SwitchOutputDevice(
    const std::string& device_id,
    const url::Origin& security_origin,
    const OutputDeviceStatusCB& callback) {
  if (device_id == device_id_) {
    callback.Run(OUTPUT_DEVICE_STATUS_OK);
    return;
  }

  if (mixer_) {
    OutputDeviceStatus new_mixer_status = OUTPUT_DEVICE_STATUS_ERROR_INTERNAL;
    AudioRendererMixer* new_mixer =
        mixer_pool_->GetMixer(owner_id_, params_, latency_, device_id,
                              security_origin, &new_mixer_status);
    if (new_mixer_status != OUTPUT_DEVICE_STATUS_OK) {
      callback.Run(new_mixer_status);
      return;
    }

    bool was_playing = playing_;
    Stop();
    device_id_ = device_id;
    security_origin_ = security_origin;
    mixer_ = new_mixer;
    mixer_->AddErrorCallback(error_cb_);
    started_ = true;

    if (was_playing)
      Play();

  } else {
    OutputDeviceStatus new_mixer_status =
        mixer_pool_
            ->GetOutputDeviceInfo(owner_id_, 0 /* session_id */, device_id,
                                  security_origin)
            .device_status();
    if (new_mixer_status != OUTPUT_DEVICE_STATUS_OK) {
      callback.Run(new_mixer_status);
      return;
    }
    device_id_ = device_id;
    security_origin_ = security_origin;
  }

  callback.Run(OUTPUT_DEVICE_STATUS_OK);
}

double AudioRendererMixerInput::ProvideInput(AudioBus* audio_bus,
                                             uint32_t frames_delayed) {
  TRACE_EVENT0("audio", "AudioRendererMixerInput::ProvideInput");
  const base::TimeDelta delay =
      AudioTimestampHelper::FramesToTime(frames_delayed, params_.sample_rate());

  int frames_filled =
      callback_->Render(delay, base::TimeTicks::Now(), 0, audio_bus);

  // AudioConverter expects unfilled frames to be zeroed.
  if (frames_filled < audio_bus->frames()) {
    audio_bus->ZeroFramesPartial(
        frames_filled, audio_bus->frames() - frames_filled);
  }

  // We're reading |volume_| from the audio device thread and must avoid racing
  // with the main/media thread calls to SetVolume(). See thread safety comment
  // in the header file.
  {
    base::AutoLock auto_lock(volume_lock_);
    return frames_filled > 0 ? volume_ : 0;
  }
}

void AudioRendererMixerInput::OnRenderError() {
  callback_->OnRenderError();
}

}  // namespace media
