// Copyright 2016 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 "content/renderer/media/webrtc/webrtc_audio_sink.h"

#include <algorithm>
#include <limits>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/threading/thread_task_runner_handle.h"

namespace content {

WebRtcAudioSink::WebRtcAudioSink(
    const std::string& label,
    scoped_refptr<webrtc::AudioSourceInterface> track_source,
    scoped_refptr<base::SingleThreadTaskRunner> signaling_task_runner)
    : adapter_(new rtc::RefCountedObject<Adapter>(
          label, std::move(track_source), std::move(signaling_task_runner))),
      fifo_(base::Bind(&WebRtcAudioSink::DeliverRebufferedAudio,
                       base::Unretained(this))) {
  DVLOG(1) << "WebRtcAudioSink::WebRtcAudioSink()";
}

WebRtcAudioSink::~WebRtcAudioSink() {
  DCHECK(thread_checker_.CalledOnValidThread());
  DVLOG(1) << "WebRtcAudioSink::~WebRtcAudioSink()";
}

void WebRtcAudioSink::SetAudioProcessor(
    scoped_refptr<MediaStreamAudioProcessor> processor) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(processor.get());
  adapter_->set_processor(std::move(processor));
}

void WebRtcAudioSink::SetLevel(
    scoped_refptr<MediaStreamAudioLevelCalculator::Level> level) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(level.get());
  adapter_->set_level(std::move(level));
}

void WebRtcAudioSink::OnEnabledChanged(bool enabled) {
  DCHECK(thread_checker_.CalledOnValidThread());
  adapter_->signaling_task_runner()->PostTask(
      FROM_HERE,
      base::Bind(
          base::IgnoreResult(&WebRtcAudioSink::Adapter::set_enabled),
          adapter_, enabled));
}

void WebRtcAudioSink::OnData(const media::AudioBus& audio_bus,
                             base::TimeTicks estimated_capture_time) {
  DCHECK(audio_thread_checker_.CalledOnValidThread());
  // The following will result in zero, one, or multiple synchronous calls to
  // DeliverRebufferedAudio().
  fifo_.Push(audio_bus);
}

void WebRtcAudioSink::OnSetFormat(const media::AudioParameters& params) {
  // On a format change, the thread delivering audio might have also changed.
  audio_thread_checker_.DetachFromThread();
  DCHECK(audio_thread_checker_.CalledOnValidThread());

  DCHECK(params.IsValid());
  params_ = params;
  // Make sure that our params always reflect a buffer size of 10ms.
  params_.set_frames_per_buffer(params_.sample_rate() / 100);
  fifo_.Reset(params_.frames_per_buffer());
  const int num_pcm16_data_elements =
      params_.frames_per_buffer() * params_.channels();
  interleaved_data_.reset(new int16_t[num_pcm16_data_elements]);
}

void WebRtcAudioSink::DeliverRebufferedAudio(const media::AudioBus& audio_bus,
                                             int frame_delay) {
  DCHECK(audio_thread_checker_.CalledOnValidThread());
  DCHECK(params_.IsValid());

  // TODO(miu): Why doesn't a WebRTC sink care about reference time passed to
  // OnData(), and the |frame_delay| here?  How is AV sync achieved otherwise?

  // TODO(henrika): Remove this conversion once the interface in libjingle
  // supports float vectors.
  audio_bus.ToInterleaved(audio_bus.frames(),
                          sizeof(interleaved_data_[0]),
                          interleaved_data_.get());
  adapter_->DeliverPCMToWebRtcSinks(interleaved_data_.get(),
                                    params_.sample_rate(),
                                    audio_bus.channels(),
                                    audio_bus.frames());
}

namespace {
// TODO(miu): MediaStreamAudioProcessor destructor requires this nonsense.
void DereferenceOnMainThread(
    const scoped_refptr<MediaStreamAudioProcessor>& processor) {}
}  // namespace

WebRtcAudioSink::Adapter::Adapter(
    const std::string& label,
    scoped_refptr<webrtc::AudioSourceInterface> source,
    scoped_refptr<base::SingleThreadTaskRunner> signaling_task_runner)
    : webrtc::MediaStreamTrack<webrtc::AudioTrackInterface>(label),
      source_(std::move(source)),
      signaling_task_runner_(std::move(signaling_task_runner)),
      main_task_runner_(base::ThreadTaskRunnerHandle::Get()) {
  DCHECK(signaling_task_runner_);
}

WebRtcAudioSink::Adapter::~Adapter() {
  if (audio_processor_) {
    main_task_runner_->PostTask(
        FROM_HERE,
        base::Bind(&DereferenceOnMainThread, std::move(audio_processor_)));
  }
}

void WebRtcAudioSink::Adapter::DeliverPCMToWebRtcSinks(
    const int16_t* audio_data,
    int sample_rate,
    size_t number_of_channels,
    size_t number_of_frames) {
  base::AutoLock auto_lock(lock_);
  for (webrtc::AudioTrackSinkInterface* sink : sinks_) {
    sink->OnData(audio_data, sizeof(int16_t) * 8, sample_rate,
                 number_of_channels, number_of_frames);
  }
}

std::string WebRtcAudioSink::Adapter::kind() const {
  return webrtc::MediaStreamTrackInterface::kAudioKind;
}

bool WebRtcAudioSink::Adapter::set_enabled(bool enable) {
  DCHECK(!signaling_task_runner_ ||
         signaling_task_runner_->RunsTasksInCurrentSequence());
  return webrtc::MediaStreamTrack<webrtc::AudioTrackInterface>::
      set_enabled(enable);
}

void WebRtcAudioSink::Adapter::AddSink(webrtc::AudioTrackSinkInterface* sink) {
  DCHECK(!signaling_task_runner_ ||
         signaling_task_runner_->RunsTasksInCurrentSequence());
  DCHECK(sink);
  base::AutoLock auto_lock(lock_);
  DCHECK(std::find(sinks_.begin(), sinks_.end(), sink) == sinks_.end());
  sinks_.push_back(sink);
}

void WebRtcAudioSink::Adapter::RemoveSink(
    webrtc::AudioTrackSinkInterface* sink) {
  DCHECK(!signaling_task_runner_ ||
         signaling_task_runner_->RunsTasksInCurrentSequence());
  base::AutoLock auto_lock(lock_);
  const auto it = std::find(sinks_.begin(), sinks_.end(), sink);
  if (it != sinks_.end())
    sinks_.erase(it);
}

bool WebRtcAudioSink::Adapter::GetSignalLevel(int* level) {
  DCHECK(!signaling_task_runner_ ||
         signaling_task_runner_->RunsTasksInCurrentSequence());

  // |level_| is only set once, so it's safe to read without first acquiring a
  // mutex.
  if (!level_)
    return false;
  const float signal_level = level_->GetCurrent();
  DCHECK_GE(signal_level, 0.0f);
  DCHECK_LE(signal_level, 1.0f);
  // Convert from float in range [0.0,1.0] to an int in range [0,32767].
  *level = static_cast<int>(signal_level * std::numeric_limits<int16_t>::max() +
                            0.5f /* rounding to nearest int */);
  return true;
}

rtc::scoped_refptr<webrtc::AudioProcessorInterface>
WebRtcAudioSink::Adapter::GetAudioProcessor() {
  DCHECK(!signaling_task_runner_ ||
         signaling_task_runner_->RunsTasksInCurrentSequence());
  return audio_processor_.get();
}

webrtc::AudioSourceInterface* WebRtcAudioSink::Adapter::GetSource() const {
  DCHECK(!signaling_task_runner_ ||
         signaling_task_runner_->RunsTasksInCurrentSequence());
  return source_.get();
}

}  // namespace content
