// 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 "content/renderer/media/webrtc_audio_device_impl.h"

#include "base/bind.h"
#include "base/metrics/histogram.h"
#include "base/string_util.h"
#include "base/win/windows_version.h"
#include "content/renderer/media/audio_hardware.h"
#include "content/renderer/render_thread_impl.h"
#include "media/audio/audio_util.h"
#include "media/audio/audio_parameters.h"
#include "media/audio/sample_rates.h"

using media::AudioParameters;

static const int64 kMillisecondsBetweenProcessCalls = 5000;
static const double kMaxVolumeLevel = 255.0;

// Supported hardware sample rates for input and output sides.
#if defined(OS_WIN) || defined(OS_MACOSX)
// media::GetAudioInput[Output]HardwareSampleRate() asks the audio layer
// for its current sample rate (set by the user) on Windows and Mac OS X.
// The listed rates below adds restrictions and WebRtcAudioDeviceImpl::Init()
// will fail if the user selects any rate outside these ranges.
static int kValidInputRates[] = {96000, 48000, 44100, 32000, 16000, 8000};
static int kValidOutputRates[] = {96000, 48000, 44100};
#elif defined(OS_LINUX) || defined(OS_OPENBSD)
// media::GetAudioInput[Output]HardwareSampleRate() is hardcoded to return
// 48000 in both directions on Linux.
static int kValidInputRates[] = {48000};
static int kValidOutputRates[] = {48000};
#endif

namespace {

// Helper enum used for histogramming buffer sizes expressed in number of
// audio frames. This enumerator covers all supported sizes for all platforms.
// Example: k480 <=> 480 audio frames <=> 10ms@48kHz.
// TODO(henrika): can be moved to the media namespace if more clients need it.
// TODO(henrika): add support for k80 as well. Will be listed as unexpected for
// now. Very rare case though and most likeley only on Mac OS X.
enum AudioFramesPerBuffer {
  k160,
  k320,
  k440,  // WebRTC works internally with 440 audio frames at 44.1kHz.
  k480,
  k640,
  k880,
  k960,
  k1440,
  k1920,
  kUnexpectedAudioBufferSize  // Must always be last!
};

enum HistogramDirection {
  kAudioOutput,
  kAudioInput
};

}  // anonymous namespace

// Helper method to convert integral values to their respective enum values
// above, or kUnexpectedAudioBufferSize if no match exists.
// TODO(henrika): add support for k80 as well given that 8000Hz input now has
// been added.
static AudioFramesPerBuffer AsAudioFramesPerBuffer(int frames_per_buffer) {
  switch (frames_per_buffer) {
    case 160: return k160;
    case 320: return k320;
    case 440: return k440;
    case 480: return k480;
    case 640: return k640;
    case 880: return k880;
    case 960: return k960;
    case 1440: return k1440;
    case 1920: return k1920;
  }
  return kUnexpectedAudioBufferSize;
}

// Helper method which adds histogram data to be uploaded as part of an
// UMA logging event. Names: "WebRTC.Audio[Output|Input]SampleRate".
static void AddHistogramSampleRate(HistogramDirection dir, int param) {
  media::AudioSampleRate asr = media::AsAudioSampleRate(param);
  if (asr != media::kUnexpectedAudioSampleRate) {
    if (dir == kAudioOutput) {
      UMA_HISTOGRAM_ENUMERATION("WebRTC.AudioOutputSampleRate",
                                asr, media::kUnexpectedAudioSampleRate);
    } else {
      UMA_HISTOGRAM_ENUMERATION("WebRTC.AudioInputSampleRate",
                                asr, media::kUnexpectedAudioSampleRate);
    }
  } else {
    // Report unexpected sample rates using a unique histogram name.
    if (dir == kAudioOutput) {
      UMA_HISTOGRAM_COUNTS("WebRTC.AudioOutputSampleRateUnexpected", param);
    } else {
      UMA_HISTOGRAM_COUNTS("WebRTC.AudioInputSampleRateUnexpected", param);
    }
  }
}

// Helper method which adds histogram data to be uploaded as part of an
// UMA logging event. Names: "WebRTC.Audio[Output|Input]FramesPerBuffer".
static void AddHistogramFramesPerBuffer(HistogramDirection dir, int param) {
  AudioFramesPerBuffer afpb = AsAudioFramesPerBuffer(param);
  if (afpb != kUnexpectedAudioBufferSize) {
    if (dir == kAudioOutput) {
      UMA_HISTOGRAM_ENUMERATION("WebRTC.AudioOutputFramesPerBuffer",
                                afpb, kUnexpectedAudioBufferSize);
    } else {
      UMA_HISTOGRAM_ENUMERATION("WebRTC.AudioInputFramesPerBuffer",
                                afpb, kUnexpectedAudioBufferSize);
    }
  } else {
    // Report unexpected sample rates using a unique histogram name.
    if (dir == kAudioOutput) {
      UMA_HISTOGRAM_COUNTS("WebRTC.AudioOutputFramesPerBufferUnexpected",
                           param);
    } else {
      UMA_HISTOGRAM_COUNTS("WebRTC.AudioInputFramesPerBufferUnexpected", param);
    }
  }
}

WebRtcAudioDeviceImpl::WebRtcAudioDeviceImpl()
    : ref_count_(0),
      render_loop_(base::MessageLoopProxy::current()),
      audio_transport_callback_(NULL),
      input_delay_ms_(0),
      output_delay_ms_(0),
      last_error_(AudioDeviceModule::kAdmErrNone),
      last_process_time_(base::TimeTicks::Now()),
      session_id_(0),
      bytes_per_sample_(0),
      initialized_(false),
      playing_(false),
      recording_(false),
      agc_is_enabled_(false) {
    DVLOG(1) << "WebRtcAudioDeviceImpl::WebRtcAudioDeviceImpl()";
    DCHECK(RenderThreadImpl::current()) <<
        "WebRtcAudioDeviceImpl must be constructed on the render thread";
}

WebRtcAudioDeviceImpl::~WebRtcAudioDeviceImpl() {
  DVLOG(1) << "WebRtcAudioDeviceImpl::~WebRtcAudioDeviceImpl()";
  if (playing_)
    StopPlayout();
  if (recording_)
    StopRecording();
  if (initialized_)
    Terminate();
}

int32_t WebRtcAudioDeviceImpl::AddRef() {
  return base::subtle::Barrier_AtomicIncrement(&ref_count_, 1);
}

int32_t WebRtcAudioDeviceImpl::Release() {
  int ret = base::subtle::Barrier_AtomicIncrement(&ref_count_, -1);
  if (ret == 0) {
    delete this;
  }
  return ret;
}

int WebRtcAudioDeviceImpl::Render(
    const std::vector<float*>& audio_data,
    int number_of_frames,
    int audio_delay_milliseconds) {
  DCHECK_LE(number_of_frames, output_buffer_size());

  {
    base::AutoLock auto_lock(lock_);
    // Store the reported audio delay locally.
    output_delay_ms_ = audio_delay_milliseconds;
  }

  const int channels = audio_data.size();
  DCHECK_LE(channels, output_channels());

  int samples_per_sec = output_sample_rate();
  if (samples_per_sec == 44100) {
    // Even if the hardware runs at 44.1kHz, we use 44.0 internally.
    samples_per_sec = 44000;
  }
  int samples_per_10_msec = (samples_per_sec / 100);
  const int bytes_per_10_msec =
      channels * samples_per_10_msec * bytes_per_sample_;

  uint32_t num_audio_samples = 0;
  int accumulated_audio_samples = 0;

  char* audio_byte_buffer = reinterpret_cast<char*>(output_buffer_.get());

  // Get audio samples in blocks of 10 milliseconds from the registered
  // webrtc::AudioTransport source. Keep reading until our internal buffer
  // is full.
  while (accumulated_audio_samples < number_of_frames) {
    // Get 10ms and append output to temporary byte buffer.
    audio_transport_callback_->NeedMorePlayData(samples_per_10_msec,
                                                bytes_per_sample_,
                                                channels,
                                                samples_per_sec,
                                                audio_byte_buffer,
                                                num_audio_samples);
    accumulated_audio_samples += num_audio_samples;
    audio_byte_buffer += bytes_per_10_msec;
  }

  // Deinterleave each channel and convert to 32-bit floating-point
  // with nominal range -1.0 -> +1.0 to match the callback format.
  for (int channel_index = 0; channel_index < channels; ++channel_index) {
    media::DeinterleaveAudioChannel(
        output_buffer_.get(),
        audio_data[channel_index],
        channels,
        channel_index,
        bytes_per_sample_,
        number_of_frames);
  }
  return number_of_frames;
}

void WebRtcAudioDeviceImpl::OnRenderError() {
  DCHECK_EQ(MessageLoop::current(), ChildProcess::current()->io_message_loop());
  // TODO(henrika): Implement error handling.
  LOG(ERROR) << "OnRenderError()";
}

void WebRtcAudioDeviceImpl::Capture(const std::vector<float*>& audio_data,
                                    int number_of_frames,
                                    int audio_delay_milliseconds,
                                    double volume) {
  DCHECK_LE(number_of_frames, input_buffer_size());
#if defined(OS_WIN) || defined(OS_MACOSX)
  DCHECK_LE(volume, 1.0);
#elif defined(OS_LINUX) || defined(OS_OPENBSD)
  // We have a special situation on Linux where the microphone volume can be
  // "higher than maximum". The input volume slider in the sound preference
  // allows the user to set a scaling that is higher than 100%. It means that
  // even if the reported maximum levels is N, the actual microphone level can
  // go up to 1.5*N and that corresponds to a normalized |volume| of 1.5.
  DCHECK_LE(volume, 1.5);
#endif

  int output_delay_ms = 0;
  {
    base::AutoLock auto_lock(lock_);
    // Store the reported audio delay locally.
    input_delay_ms_ = audio_delay_milliseconds;
    output_delay_ms = output_delay_ms_;
  }

  const int channels = audio_data.size();
  DCHECK_LE(channels, input_channels());
  uint32_t new_mic_level = 0;

  // Interleave, scale, and clip input to int and store result in
  // a local byte buffer.
  media::InterleaveFloatToInt(audio_data,
                              input_buffer_.get(),
                              number_of_frames,
                              input_audio_parameters_.bits_per_sample() / 8);

  int samples_per_sec = input_sample_rate();
  if (samples_per_sec == 44100) {
    // Even if the hardware runs at 44.1kHz, we use 44.0 internally.
    samples_per_sec = 44000;
  }
  const int samples_per_10_msec = (samples_per_sec / 100);
  const int bytes_per_10_msec =
      channels * samples_per_10_msec * bytes_per_sample_;
  int accumulated_audio_samples = 0;

  char* audio_byte_buffer = reinterpret_cast<char*>(input_buffer_.get());

  // Map internal volume range of [0.0, 1.0] into [0, 255] used by the
  // webrtc::VoiceEngine.
  uint32_t current_mic_level = static_cast<uint32_t>(volume * kMaxVolumeLevel);

  // Write audio samples in blocks of 10 milliseconds to the registered
  // webrtc::AudioTransport sink. Keep writing until our internal byte
  // buffer is empty.
  while (accumulated_audio_samples < number_of_frames) {
    // Deliver 10ms of recorded 16-bit linear PCM audio.
    audio_transport_callback_->RecordedDataIsAvailable(
        audio_byte_buffer,
        samples_per_10_msec,
        bytes_per_sample_,
        channels,
        samples_per_sec,
        input_delay_ms_ + output_delay_ms,
        0,  // TODO(henrika): |clock_drift| parameter is not utilized today.
        current_mic_level,
        new_mic_level);

    accumulated_audio_samples += samples_per_10_msec;
    audio_byte_buffer += bytes_per_10_msec;
  }

  // The AGC returns a non-zero microphone level if it has been decided
  // that a new level should be set.
  if (new_mic_level != 0) {
    // Use IPC and set the new level. Note that, it will take some time
    // before the new level is effective due to the IPC scheme.
    // During this time, |current_mic_level| will contain "non-valid" values
    // and it might reduce the AGC performance. Measurements on Windows 7 have
    // shown that we might receive old volume levels for one or two callbacks.
    SetMicrophoneVolume(new_mic_level);
  }
}

void WebRtcAudioDeviceImpl::OnCaptureError() {
  DCHECK_EQ(MessageLoop::current(), ChildProcess::current()->io_message_loop());
  // TODO(henrika): Implement error handling.
  LOG(ERROR) << "OnCaptureError()";
}

void WebRtcAudioDeviceImpl::OnDeviceStarted(const std::string& device_id) {
  DVLOG(1) << "OnDeviceStarted (device_id=" << device_id << ")";
  // Empty string is an invalid device id. Do nothing if a valid device has
  // been started. Otherwise update the |recording_| state to false.
  if (!device_id.empty())
    return;

  base::AutoLock auto_lock(lock_);
  if (recording_)
    recording_ = false;
}

void WebRtcAudioDeviceImpl::OnDeviceStopped() {
  DVLOG(1) << "OnDeviceStopped";
  base::AutoLock auto_lock(lock_);
  if (recording_)
    recording_ = false;
}

int32_t WebRtcAudioDeviceImpl::ChangeUniqueId(const int32_t id) {
  NOTIMPLEMENTED();
  return -1;
}

int32_t WebRtcAudioDeviceImpl::TimeUntilNextProcess() {
  // Calculate the number of milliseconds until this module wants its
  // Process method to be called.
  base::TimeDelta delta_time = (base::TimeTicks::Now() - last_process_time_);
  int64 time_until_next =
      kMillisecondsBetweenProcessCalls - delta_time.InMilliseconds();
  return static_cast<int32_t>(time_until_next);
}

int32_t WebRtcAudioDeviceImpl::Process() {
  // TODO(henrika): it is possible to add functionality in this method, which
  // is called periodically. The idea is that we should call one of the methods
  // in the registered AudioDeviceObserver to inform the user about warnings
  // or error states. Leave it empty for now.
  last_process_time_ = base::TimeTicks::Now();
  return 0;
}

int32_t WebRtcAudioDeviceImpl::ActiveAudioLayer(AudioLayer* audio_layer) const {
  NOTIMPLEMENTED();
  return -1;
}

webrtc::AudioDeviceModule::ErrorCode WebRtcAudioDeviceImpl::LastError() const {
  return last_error_;
}

int32_t WebRtcAudioDeviceImpl::RegisterEventObserver(
    webrtc::AudioDeviceObserver* event_callback) {
  DVLOG(2) << "WARNING: WebRtcAudioDeviceImpl::RegisterEventObserver() "
           << "NOT IMPLEMENTED";
  return -1;
}

int32_t WebRtcAudioDeviceImpl::RegisterAudioCallback(
    webrtc::AudioTransport* audio_callback) {
  DVLOG(1) << "RegisterAudioCallback()";
  if (playing_ || recording_)  {
    LOG(ERROR) << "Unable to (de)register transport during active media";
    return -1;
  }
  audio_transport_callback_ = audio_callback;
  return 0;
}

int32_t WebRtcAudioDeviceImpl::Init() {
  DVLOG(1) << "Init()";

  if (!render_loop_->BelongsToCurrentThread()) {
    int32_t error = 0;
    base::WaitableEvent event(false, false);
    // Ensure that we call Init() from the main render thread since
    // the audio clients can only be created on this thread.
    render_loop_->PostTask(
        FROM_HERE,
        base::Bind(&WebRtcAudioDeviceImpl::InitOnRenderThread,
                   this, &error, &event));
    event.Wait();
    return error;
  }

  // Calling Init() multiple times in a row is OK.
  if (initialized_)
    return 0;

  DCHECK(!audio_input_device_);
  DCHECK(!audio_output_device_);
  DCHECK(!input_buffer_.get());
  DCHECK(!output_buffer_.get());

  // TODO(henrika): it could be possible to allow one of the directions (input
  // or output) to use a non-supported rate. As an example: if only the
  // output rate is OK, we could finalize Init() and only set up an AudioDevice.

  // Ask the browser for the default audio output hardware sample-rate.
  // This request is based on a synchronous IPC message.
  int out_sample_rate = audio_hardware::GetOutputSampleRate();
  DVLOG(1) << "Audio output hardware sample rate: " << out_sample_rate;
  AddHistogramSampleRate(kAudioOutput, out_sample_rate);

  // Verify that the reported output hardware sample rate is supported
  // on the current platform.
  if (std::find(&kValidOutputRates[0],
                &kValidOutputRates[0] + arraysize(kValidOutputRates),
                out_sample_rate) ==
      &kValidOutputRates[arraysize(kValidOutputRates)]) {
    DLOG(ERROR) << out_sample_rate << " is not a supported output rate.";
    return -1;
  }

  // Ask the browser for the default audio input hardware sample-rate.
  // This request is based on a synchronous IPC message.
  int in_sample_rate = audio_hardware::GetInputSampleRate();
  DVLOG(1) << "Audio input hardware sample rate: " << in_sample_rate;
  AddHistogramSampleRate(kAudioInput, in_sample_rate);

  // Verify that the reported input hardware sample rate is supported
  // on the current platform.
  if (std::find(&kValidInputRates[0],
                &kValidInputRates[0] + arraysize(kValidInputRates),
                in_sample_rate) ==
      &kValidInputRates[arraysize(kValidInputRates)]) {
    DLOG(ERROR) << in_sample_rate << " is not a supported input rate.";
    return -1;
  }

  // Ask the browser for the default number of audio input channels.
  // This request is based on a synchronous IPC message.
  ChannelLayout in_channel_layout = audio_hardware::GetInputChannelLayout();
  DVLOG(1) << "Audio input hardware channels: " << in_channel_layout;
  ChannelLayout out_channel_layout = CHANNEL_LAYOUT_MONO;

  AudioParameters::Format in_format = AudioParameters::AUDIO_PCM_LINEAR;
  int in_buffer_size = 0;
  int out_buffer_size = 0;

  // TODO(henrika): factor out all platform specific parts in separate
  // functions. Code is a bit messy right now.

// Windows
#if defined(OS_WIN)
  // Always use stereo rendering on Windows.
  out_channel_layout = CHANNEL_LAYOUT_STEREO;

  DVLOG(1) << "Using AUDIO_PCM_LOW_LATENCY as input mode on Windows.";
  in_format = AudioParameters::AUDIO_PCM_LOW_LATENCY;

  // Capture side: AUDIO_PCM_LOW_LATENCY is based on the Core Audio (WASAPI)
  // API which was introduced in Windows Vista. For lower Windows versions,
  // a callback-driven Wave implementation is used instead. An input buffer
  // size of 10ms works well for both these implementations.

  // Use different buffer sizes depending on the current hardware sample rate.
  if (in_sample_rate == 44100) {
    // We do run at 44.1kHz at the actual audio layer, but ask for frames
    // at 44.0kHz to ensure that we can feed them to the webrtc::VoiceEngine.
    in_buffer_size = 440;
  } else {
    in_buffer_size = (in_sample_rate / 100);
    DCHECK_EQ(in_buffer_size * 100, in_sample_rate) <<
        "Sample rate not supported. Should have been caught in Init().";
  }

  // Render side: AUDIO_PCM_LOW_LATENCY is based on the Core Audio (WASAPI)
  // API which was introduced in Windows Vista. For lower Windows versions,
  // a callback-driven Wave implementation is used instead. An output buffer
  // size of 10ms works well for WASAPI but 30ms is needed for Wave.

  // Use different buffer sizes depending on the current hardware sample rate.
  if (out_sample_rate == 96000 || out_sample_rate == 48000) {
    out_buffer_size = (out_sample_rate / 100);
  } else {
    // We do run at 44.1kHz at the actual audio layer, but ask for frames
    // at 44.0kHz to ensure that we can feed them to the webrtc::VoiceEngine.
    // TODO(henrika): figure out why we seem to need 20ms here for glitch-
    // free audio.
    out_buffer_size = 2 * 440;
  }

  // Windows XP and lower can't cope with 10 ms output buffer size.
  // It must be extended to 30 ms (60 ms will be used internally by WaveOut).
  if (!media::IsWASAPISupported()) {
    out_buffer_size = 3 * out_buffer_size;
    DLOG(WARNING) << "Extending the output buffer size by a factor of three "
                  << "since Windows XP has been detected.";
  }

// Mac OS X
#elif defined(OS_MACOSX)
  out_channel_layout = CHANNEL_LAYOUT_MONO;

  DVLOG(1) << "Using AUDIO_PCM_LOW_LATENCY as input mode on Mac OS X.";
  in_format = AudioParameters::AUDIO_PCM_LOW_LATENCY;

  // Capture side: AUDIO_PCM_LOW_LATENCY on Mac OS X is based on a callback-
  // driven Core Audio implementation. Tests have shown that 10ms is a suitable
  // frame size to use, both for 48kHz and 44.1kHz.

  // Use different buffer sizes depending on the current hardware sample rate.
  if (in_sample_rate == 44100) {
    // We do run at 44.1kHz at the actual audio layer, but ask for frames
    // at 44.0kHz to ensure that we can feed them to the webrtc::VoiceEngine.
    in_buffer_size = 440;
  } else {
    in_buffer_size = (in_sample_rate / 100);
    DCHECK_EQ(in_buffer_size * 100, in_sample_rate) <<
        "Sample rate not supported. Should have been caught in Init().";
  }

  // Render side: AUDIO_PCM_LOW_LATENCY on Mac OS X is based on a callback-
  // driven Core Audio implementation. Tests have shown that 10ms is a suitable
  // frame size to use, both for 48kHz and 44.1kHz.

  // Use different buffer sizes depending on the current hardware sample rate.
  if (out_sample_rate == 48000) {
    out_buffer_size = 480;
  } else {
    // We do run at 44.1kHz at the actual audio layer, but ask for frames
    // at 44.0kHz to ensure that we can feed them to the webrtc::VoiceEngine.
    out_buffer_size = 440;
  }
// Linux
#elif defined(OS_LINUX) || defined(OS_OPENBSD)
  in_channel_layout = CHANNEL_LAYOUT_STEREO;
  out_channel_layout = CHANNEL_LAYOUT_MONO;

  // Based on tests using the current ALSA implementation in Chrome, we have
  // found that the best combination is 20ms on the input side and 10ms on the
  // output side.
  // TODO(henrika): It might be possible to reduce the input buffer
  // size and reduce the delay even more.
  in_buffer_size = 2 * 480;
  out_buffer_size = 480;
#else
  DLOG(ERROR) << "Unsupported platform";
  return -1;
#endif

  // Store utilized parameters to ensure that we can check them
  // after a successful initialization.
  output_audio_parameters_.Reset(
      AudioParameters::AUDIO_PCM_LOW_LATENCY, out_channel_layout,
      out_sample_rate, 16, out_buffer_size);

  input_audio_parameters_.Reset(
      in_format, in_channel_layout, in_sample_rate,
      16, in_buffer_size);

  // Create and configure the audio capturing client.
  audio_input_device_ = new AudioInputDevice(
      input_audio_parameters_, this, this);

  UMA_HISTOGRAM_ENUMERATION("WebRTC.AudioOutputChannelLayout",
                            out_channel_layout, CHANNEL_LAYOUT_MAX);
  UMA_HISTOGRAM_ENUMERATION("WebRTC.AudioInputChannelLayout",
                            in_channel_layout, CHANNEL_LAYOUT_MAX);
  AddHistogramFramesPerBuffer(kAudioOutput, out_buffer_size);
  AddHistogramFramesPerBuffer(kAudioInput, in_buffer_size);

  // Create and configure the audio rendering client.
  audio_output_device_ = new AudioDevice(output_audio_parameters_, this);

  DCHECK(audio_input_device_);
  DCHECK(audio_output_device_);

  // Allocate local audio buffers based on the parameters above.
  // It is assumed that each audio sample contains 16 bits and each
  // audio frame contains one or two audio samples depending on the
  // number of channels.
  input_buffer_.reset(new int16[input_buffer_size() * input_channels()]);
  output_buffer_.reset(new int16[output_buffer_size() * output_channels()]);

  DCHECK(input_buffer_.get());
  DCHECK(output_buffer_.get());

  bytes_per_sample_ = sizeof(*input_buffer_.get());

  initialized_ = true;

  DVLOG(1) << "Capture parameters (size/channels/rate): ("
           << input_buffer_size() << "/" << input_channels() << "/"
           << input_sample_rate() << ")";
  DVLOG(1) << "Render parameters (size/channels/rate): ("
           << output_buffer_size() << "/" << output_channels() << "/"
           << output_sample_rate() << ")";
  return 0;
}

void WebRtcAudioDeviceImpl::InitOnRenderThread(int32_t* error,
                                               base::WaitableEvent* event) {
  DCHECK(render_loop_->BelongsToCurrentThread());
  *error = Init();
  event->Signal();
}

int32_t WebRtcAudioDeviceImpl::Terminate() {
  DVLOG(1) << "Terminate()";

  // Calling Terminate() multiple times in a row is OK.
  if (!initialized_)
    return 0;

  DCHECK(audio_input_device_);
  DCHECK(audio_output_device_);
  DCHECK(input_buffer_.get());
  DCHECK(output_buffer_.get());

  // Release all resources allocated in Init().
  audio_input_device_ = NULL;
  audio_output_device_ = NULL;
  input_buffer_.reset();
  output_buffer_.reset();

  initialized_ = false;
  return 0;
}

bool WebRtcAudioDeviceImpl::Initialized() const {
  return initialized_;
}

int16_t WebRtcAudioDeviceImpl::PlayoutDevices() {
  NOTIMPLEMENTED();
  return -1;
}

int16_t WebRtcAudioDeviceImpl::RecordingDevices() {
  NOTIMPLEMENTED();
  return -1;
}

int32_t WebRtcAudioDeviceImpl::PlayoutDeviceName(
    uint16_t index,
    char name[webrtc::kAdmMaxDeviceNameSize],
    char guid[webrtc::kAdmMaxGuidSize]) {
  NOTIMPLEMENTED();
  return -1;
}

int32_t WebRtcAudioDeviceImpl::RecordingDeviceName(
    uint16_t index,
    char name[webrtc::kAdmMaxDeviceNameSize],
    char guid[webrtc::kAdmMaxGuidSize]) {
  NOTIMPLEMENTED();
  return -1;
}

int32_t WebRtcAudioDeviceImpl::SetPlayoutDevice(uint16_t index) {
  DVLOG(2) << "WARNING: WebRtcAudioDeviceImpl::SetPlayoutDevice() "
           << "NOT IMPLEMENTED";
  return 0;
}

int32_t WebRtcAudioDeviceImpl::SetPlayoutDevice(WindowsDeviceType device) {
  DVLOG(2) << "WARNING: WebRtcAudioDeviceImpl::SetPlayoutDevice() "
           << "NOT IMPLEMENTED";
  return 0;
}

int32_t WebRtcAudioDeviceImpl::SetRecordingDevice(uint16_t index) {
  DVLOG(2) << "WARNING: WebRtcAudioDeviceImpl::SetRecordingDevice() "
           << "NOT IMPLEMENTED";
  return 0;
}

int32_t WebRtcAudioDeviceImpl::SetRecordingDevice(WindowsDeviceType device) {
  DVLOG(2) << "WARNING: WebRtcAudioDeviceImpl::SetRecordingDevice() "
           << "NOT IMPLEMENTED";
  return 0;
}

int32_t WebRtcAudioDeviceImpl::PlayoutIsAvailable(bool* available) {
  DVLOG(1) << "PlayoutIsAvailable()";
  *available = (audio_output_device_ != NULL);
  return 0;
}

int32_t WebRtcAudioDeviceImpl::InitPlayout() {
  DVLOG(2) << "WARNING: WebRtcAudioDeviceImpl::InitPlayout() "
           << "NOT IMPLEMENTED";
  return 0;
}

bool WebRtcAudioDeviceImpl::PlayoutIsInitialized() const {
  DVLOG(1) << "PlayoutIsInitialized()";
  return (audio_output_device_ != NULL);
}

int32_t WebRtcAudioDeviceImpl::RecordingIsAvailable(bool* available) {
  DVLOG(1) << "RecordingIsAvailable()";
  *available = (audio_input_device_ != NULL);
  return 0;
}

int32_t WebRtcAudioDeviceImpl::InitRecording() {
  DVLOG(2) << "WARNING: WebRtcAudioDeviceImpl::InitRecording() "
           << "NOT IMPLEMENTED";
  return 0;
}

bool WebRtcAudioDeviceImpl::RecordingIsInitialized() const {
  DVLOG(1) << "RecordingIsInitialized()";
  return (audio_input_device_ != NULL);
}

int32_t WebRtcAudioDeviceImpl::StartPlayout() {
  DVLOG(1) << "StartPlayout()";
  LOG_IF(ERROR, !audio_transport_callback_) << "Audio transport is missing";
  if (!audio_transport_callback_) {
    return -1;
  }

  if (playing_) {
    // webrtc::VoiceEngine assumes that it is OK to call Start() twice and
    // that the call is ignored the second time.
    return 0;
  }

  start_render_time_ = base::Time::Now();

  audio_output_device_->Start();
  playing_ = true;
  return 0;
}

int32_t WebRtcAudioDeviceImpl::StopPlayout() {
  DVLOG(1) << "StopPlayout()";
  if (!playing_) {
    // webrtc::VoiceEngine assumes that it is OK to call Stop() just in case.
    return 0;
  }

  // Add histogram data to be uploaded as part of an UMA logging event.
  // This histogram keeps track of total playout times.
  if (!start_render_time_.is_null()) {
    base::TimeDelta render_time = base::Time::Now() - start_render_time_;
    UMA_HISTOGRAM_LONG_TIMES("WebRTC.AudioRenderTime", render_time);
  }

  audio_output_device_->Stop();
  playing_ = false;
  return 0;
}

bool WebRtcAudioDeviceImpl::Playing() const {
  return playing_;
}

int32_t WebRtcAudioDeviceImpl::StartRecording() {
  DVLOG(1) << "StartRecording()";
  LOG_IF(ERROR, !audio_transport_callback_) << "Audio transport is missing";
  if (!audio_transport_callback_) {
    return -1;
  }

  if (session_id_ <= 0) {
    LOG(WARNING) << session_id_ << " is an invalid session id.";
    // TODO(xians): enable the return -1 when MediaStreamManager can handle
    // AudioInputDeviceManager.
    // return -1;
  }

  base::AutoLock auto_lock(lock_);
  if (recording_) {
    // webrtc::VoiceEngine assumes that it is OK to call Start() twice and
    // that the call is ignored the second time.
    return 0;
  }

  start_capture_time_ = base::Time::Now();

  // Specify the session_id which is mapped to a certain device.
  audio_input_device_->SetDevice(session_id_);
  audio_input_device_->Start();
  recording_ = true;
  return 0;
}

int32_t WebRtcAudioDeviceImpl::StopRecording() {
  DVLOG(1) << "StopRecording()";
  {
    base::AutoLock auto_lock(lock_);
    if (!recording_) {
      // webrtc::VoiceEngine assumes that it is OK to call Stop()
      // more than once.
      return 0;
    }
  }

  // Add histogram data to be uploaded as part of an UMA logging event.
  // This histogram keeps track of total recording times.
  if (!start_capture_time_.is_null()) {
    base::TimeDelta capture_time = base::Time::Now() - start_capture_time_;
    UMA_HISTOGRAM_LONG_TIMES("WebRTC.AudioCaptureTime", capture_time);
  }

  audio_input_device_->Stop();

  base::AutoLock auto_lock(lock_);
  recording_ = false;
  return 0;
}

bool WebRtcAudioDeviceImpl::Recording() const {
  return recording_;
}

int32_t WebRtcAudioDeviceImpl::SetAGC(bool enable) {
  DVLOG(1) <<  "SetAGC(enable=" << enable << ")";
  // The current implementation does not support changing the AGC state while
  // recording. Using this approach simplifies the design and it is also
  // inline with the  latest WebRTC standard.
  DCHECK(initialized_);
  DCHECK(!recording_) << "Unable to set AGC state while recording is active.";
  if (recording_) {
    return -1;
  }

  audio_input_device_->SetAutomaticGainControl(enable);
  agc_is_enabled_ = enable;
  return 0;
}

bool WebRtcAudioDeviceImpl::AGC() const {
  // To reduce the usage of IPC messages, an internal AGC state is used.
  // TODO(henrika): investigate if there is a need for a "deeper" getter.
  return agc_is_enabled_;
}

int32_t WebRtcAudioDeviceImpl::SetWaveOutVolume(uint16_t volume_left,
                                                uint16_t volume_right) {
  NOTIMPLEMENTED();
  return -1;
}
int32_t WebRtcAudioDeviceImpl::WaveOutVolume(
    uint16_t* volume_left,
    uint16_t* volume_right) const {
  NOTIMPLEMENTED();
  return -1;
}

int32_t WebRtcAudioDeviceImpl::SpeakerIsAvailable(bool* available) {
  DVLOG(2) << "WARNING: WebRtcAudioDeviceImpl::SpeakerIsAvailable() "
           << "NOT IMPLEMENTED";
  *available = true;
  return 0;
}

int32_t WebRtcAudioDeviceImpl::InitSpeaker() {
  DVLOG(2) << "WARNING: WebRtcAudioDeviceImpl::InitSpeaker() "
           << "NOT IMPLEMENTED";
  return 0;
}

bool WebRtcAudioDeviceImpl::SpeakerIsInitialized() const {
  DVLOG(2) << "WARNING: WebRtcAudioDeviceImpl::SpeakerIsInitialized() "
           << "NOT IMPLEMENTED";
  return true;
}

int32_t WebRtcAudioDeviceImpl::MicrophoneIsAvailable(bool* available) {
  DVLOG(2) << "WARNING: WebRtcAudioDeviceImpl::MicrophoneIsAvailable() "
           << "NOT IMPLEMENTED";
  *available = true;
  return 0;
}

int32_t WebRtcAudioDeviceImpl::InitMicrophone() {
  DVLOG(2) << "WARNING: WebRtcAudioDeviceImpl::InitMicrophone() "
           << "NOT IMPLEMENTED";
  return 0;
}

bool WebRtcAudioDeviceImpl::MicrophoneIsInitialized() const {
  NOTIMPLEMENTED();
  return true;
}

int32_t WebRtcAudioDeviceImpl::SpeakerVolumeIsAvailable(
    bool* available) {
  NOTIMPLEMENTED();
  return -1;
}

int32_t WebRtcAudioDeviceImpl::SetSpeakerVolume(uint32_t volume) {
  NOTIMPLEMENTED();
  return -1;
}

int32_t WebRtcAudioDeviceImpl::SpeakerVolume(uint32_t* volume) const {
  NOTIMPLEMENTED();
  return -1;
}

int32_t WebRtcAudioDeviceImpl::MaxSpeakerVolume(uint32_t* max_volume) const {
  NOTIMPLEMENTED();
  return -1;
}

int32_t WebRtcAudioDeviceImpl::MinSpeakerVolume(uint32_t* min_volume) const {
  NOTIMPLEMENTED();
  return -1;
}

int32_t WebRtcAudioDeviceImpl::SpeakerVolumeStepSize(
    uint16_t* step_size) const {
  NOTIMPLEMENTED();
  return -1;
}

int32_t WebRtcAudioDeviceImpl::MicrophoneVolumeIsAvailable(bool* available) {
  NOTIMPLEMENTED();
  return -1;
}

int32_t WebRtcAudioDeviceImpl::SetMicrophoneVolume(uint32_t volume) {
  DVLOG(1) << "SetMicrophoneVolume(" << volume << ")";
  if (volume > kMaxVolumeLevel)
    return -1;

  // WebRTC uses a range of [0, 255] to represent the level of the microphone
  // volume. The IPC channel between the renderer and browser process works
  // with doubles in the [0.0, 1.0] range and we have to compensate for that.
  double normalized_volume = static_cast<double>(volume / kMaxVolumeLevel);
  audio_input_device_->SetVolume(normalized_volume);
  return 0;
}

int32_t WebRtcAudioDeviceImpl::MicrophoneVolume(uint32_t* volume) const {
  // The microphone level is fed to this class using the Capture() callback
  // and this external API should not be used. Additional IPC messages are
  // required if support for this API is ever needed.
  NOTREACHED();
  return -1;
}

int32_t WebRtcAudioDeviceImpl::MaxMicrophoneVolume(uint32_t* max_volume) const {
  *max_volume = kMaxVolumeLevel;
  return 0;
}

int32_t WebRtcAudioDeviceImpl::MinMicrophoneVolume(uint32_t* min_volume) const {
  *min_volume = 0;
  return 0;
}

int32_t WebRtcAudioDeviceImpl::MicrophoneVolumeStepSize(
    uint16_t* step_size) const {
  NOTREACHED();
  return -1;
}

int32_t WebRtcAudioDeviceImpl::SpeakerMuteIsAvailable(bool* available) {
  NOTIMPLEMENTED();
  return -1;
}

int32_t WebRtcAudioDeviceImpl::SetSpeakerMute(bool enable) {
  NOTIMPLEMENTED();
  return -1;
}

int32_t WebRtcAudioDeviceImpl::SpeakerMute(bool* enabled) const {
  NOTIMPLEMENTED();
  return -1;
}

int32_t WebRtcAudioDeviceImpl::MicrophoneMuteIsAvailable(
    bool* available) {
  NOTIMPLEMENTED();
  return -1;
}

int32_t WebRtcAudioDeviceImpl::SetMicrophoneMute(bool enable) {
  NOTIMPLEMENTED();
  return -1;
}

int32_t WebRtcAudioDeviceImpl::MicrophoneMute(bool* enabled) const {
  NOTIMPLEMENTED();
  return -1;
}

int32_t WebRtcAudioDeviceImpl::MicrophoneBoostIsAvailable(bool* available) {
  NOTIMPLEMENTED();
  return -1;
}

int32_t WebRtcAudioDeviceImpl::SetMicrophoneBoost(bool enable) {
  NOTIMPLEMENTED();
  return -1;
}

int32_t WebRtcAudioDeviceImpl::MicrophoneBoost(bool* enabled) const {
  NOTIMPLEMENTED();
  return -1;
}

int32_t WebRtcAudioDeviceImpl::StereoPlayoutIsAvailable(bool* available) const {
  DCHECK(initialized_) << "Init() must be called first.";
  *available = (output_channels() == 2);
  return 0;
}

int32_t WebRtcAudioDeviceImpl::SetStereoPlayout(bool enable) {
  DVLOG(2) << "WARNING: WebRtcAudioDeviceImpl::SetStereoPlayout() "
           << "NOT IMPLEMENTED";
  return 0;
}

int32_t WebRtcAudioDeviceImpl::StereoPlayout(bool* enabled) const {
  DVLOG(2) << "WARNING: WebRtcAudioDeviceImpl::StereoPlayout() "
           << "NOT IMPLEMENTED";
  return 0;
}

int32_t WebRtcAudioDeviceImpl::StereoRecordingIsAvailable(
    bool* available) const {
  DCHECK(initialized_) << "Init() must be called first.";
  *available = (input_channels() == 2);
  return 0;
}

int32_t WebRtcAudioDeviceImpl::SetStereoRecording(bool enable) {
  DVLOG(2) << "WARNING: WebRtcAudioDeviceImpl::SetStereoRecording() "
           << "NOT IMPLEMENTED";
  return -1;
}

int32_t WebRtcAudioDeviceImpl::StereoRecording(bool* enabled) const {
  DVLOG(2) << "WARNING: WebRtcAudioDeviceImpl::StereoRecording() "
           << "NOT IMPLEMENTED";
  return -1;
}

int32_t WebRtcAudioDeviceImpl::SetRecordingChannel(const ChannelType channel) {
  DVLOG(2) << "WARNING: WebRtcAudioDeviceImpl::SetRecordingChannel() "
           << "NOT IMPLEMENTED";
  return -1;
}

int32_t WebRtcAudioDeviceImpl::RecordingChannel(ChannelType* channel) const {
  DVLOG(2) << "WARNING: WebRtcAudioDeviceImpl::RecordingChannel() "
           << "NOT IMPLEMENTED";
  return -1;
}

int32_t WebRtcAudioDeviceImpl::SetPlayoutBuffer(const BufferType type,
                                                uint16_t size_ms) {
  NOTIMPLEMENTED();
  return -1;
}

int32_t WebRtcAudioDeviceImpl::PlayoutBuffer(BufferType* type,
                                             uint16_t* size_ms) const {
  NOTIMPLEMENTED();
  return -1;
}

int32_t WebRtcAudioDeviceImpl::PlayoutDelay(uint16_t* delay_ms) const {
  // Report the cached output delay value.
  base::AutoLock auto_lock(lock_);
  *delay_ms = static_cast<uint16_t>(output_delay_ms_);
  return 0;
}

int32_t WebRtcAudioDeviceImpl::RecordingDelay(uint16_t* delay_ms) const {
  // Report the cached output delay value.
  base::AutoLock auto_lock(lock_);
  *delay_ms = static_cast<uint16_t>(input_delay_ms_);
  return 0;
}

int32_t WebRtcAudioDeviceImpl::CPULoad(uint16_t* load) const {
  NOTIMPLEMENTED();
  return -1;
}

int32_t WebRtcAudioDeviceImpl::StartRawOutputFileRecording(
    const char pcm_file_name_utf8[webrtc::kAdmMaxFileNameSize]) {
  NOTIMPLEMENTED();
  return -1;
}

int32_t WebRtcAudioDeviceImpl::StopRawOutputFileRecording() {
  NOTIMPLEMENTED();
  return -1;
}

int32_t WebRtcAudioDeviceImpl::StartRawInputFileRecording(
    const char pcm_file_name_utf8[webrtc::kAdmMaxFileNameSize]) {
  NOTIMPLEMENTED();
  return -1;
}

int32_t WebRtcAudioDeviceImpl::StopRawInputFileRecording() {
  NOTIMPLEMENTED();
  return -1;
}

int32_t WebRtcAudioDeviceImpl::SetRecordingSampleRate(
    const uint32_t samples_per_sec) {
  // Sample rate should only be set at construction.
  NOTIMPLEMENTED();
  return -1;
}

int32_t WebRtcAudioDeviceImpl::RecordingSampleRate(
    uint32_t* samples_per_sec) const {
  // Returns the sample rate set at construction.
  *samples_per_sec = static_cast<uint32_t>(input_sample_rate());
  return 0;
}

int32_t WebRtcAudioDeviceImpl::SetPlayoutSampleRate(
    const uint32_t samples_per_sec) {
  // Sample rate should only be set at construction.
  NOTIMPLEMENTED();
  return -1;
}

int32_t WebRtcAudioDeviceImpl::PlayoutSampleRate(
    uint32_t* samples_per_sec) const {
  // Returns the sample rate set at construction.
  *samples_per_sec = static_cast<uint32_t>(output_sample_rate());
  return 0;
}

int32_t WebRtcAudioDeviceImpl::ResetAudioDevice() {
  NOTIMPLEMENTED();
  return -1;
}

int32_t WebRtcAudioDeviceImpl::SetLoudspeakerStatus(bool enable) {
  NOTIMPLEMENTED();
  return -1;
}

int32_t WebRtcAudioDeviceImpl::GetLoudspeakerStatus(bool* enabled) const {
  NOTIMPLEMENTED();
  return -1;
}

void WebRtcAudioDeviceImpl::SetSessionId(int session_id) {
  session_id_ = session_id;
}