// Copyright 2013 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/audio/pulse/audio_manager_pulse.h"

#include "base/command_line.h"
#include "base/environment.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/nix/xdg_util.h"
#include "base/stl_util.h"
#if defined(USE_ALSA)
#include "media/audio/alsa/audio_manager_alsa.h"
#endif
#include "media/audio/audio_parameters.h"
#include "media/audio/pulse/pulse_input.h"
#include "media/audio/pulse/pulse_output.h"
#include "media/audio/pulse/pulse_util.h"
#include "media/base/channel_layout.h"

#if defined(DLOPEN_PULSEAUDIO)
#include "media/audio/pulse/pulse_stubs.h"

using media_audio_pulse::kModulePulse;
using media_audio_pulse::InitializeStubs;
using media_audio_pulse::StubPathMap;
#endif  // defined(DLOPEN_PULSEAUDIO)

namespace media {

using pulse::AutoPulseLock;
using pulse::WaitForOperationCompletion;

// Maximum number of output streams that can be open simultaneously.
static const int kMaxOutputStreams = 50;

// Define bounds for the output buffer size.
static const int kMinimumOutputBufferSize = 512;
static const int kMaximumOutputBufferSize = 8192;

// Default input buffer size.
static const int kDefaultInputBufferSize = 1024;

#if defined(DLOPEN_PULSEAUDIO)
static const base::FilePath::CharType kPulseLib[] =
    FILE_PATH_LITERAL("libpulse.so.0");
#endif

// static
AudioManager* AudioManagerPulse::Create(AudioLogFactory* audio_log_factory) {
  scoped_ptr<AudioManagerPulse> ret(new AudioManagerPulse(audio_log_factory));
  if (ret->Init())
    return ret.release();

  DVLOG(1) << "PulseAudio is not available on the OS";
  return NULL;
}

AudioManagerPulse::AudioManagerPulse(AudioLogFactory* audio_log_factory)
    : AudioManagerBase(audio_log_factory),
      input_mainloop_(NULL),
      input_context_(NULL),
      devices_(NULL),
      native_input_sample_rate_(0) {
  SetMaxOutputStreamsAllowed(kMaxOutputStreams);
}

AudioManagerPulse::~AudioManagerPulse() {
  Shutdown();

  // The Pulse objects are the last things to be destroyed since Shutdown()
  // needs them.
  DestroyPulse();
}

// Implementation of AudioManager.
bool AudioManagerPulse::HasAudioOutputDevices() {
  AudioDeviceNames devices;
  GetAudioOutputDeviceNames(&devices);
  return !devices.empty();
}

bool AudioManagerPulse::HasAudioInputDevices() {
  AudioDeviceNames devices;
  GetAudioInputDeviceNames(&devices);
  return !devices.empty();
}

void AudioManagerPulse::ShowAudioInputSettings() {
#if defined(USE_ALSA)
  AudioManagerAlsa::ShowLinuxAudioInputSettings();
#endif
}

void AudioManagerPulse::GetAudioDeviceNames(
    bool input, media::AudioDeviceNames* device_names) {
  DCHECK(device_names->empty());
  DCHECK(input_mainloop_);
  DCHECK(input_context_);
  AutoPulseLock auto_lock(input_mainloop_);
  devices_ = device_names;
  pa_operation* operation = NULL;
  if (input) {
    operation = pa_context_get_source_info_list(
      input_context_, InputDevicesInfoCallback, this);
  } else {
    operation = pa_context_get_sink_info_list(
        input_context_, OutputDevicesInfoCallback, this);
  }
  WaitForOperationCompletion(input_mainloop_, operation);

  // Prepend the default device if the list is not empty.
  if (!device_names->empty()) {
    device_names->push_front(
        AudioDeviceName(AudioManager::GetDefaultDeviceName(),
                        AudioManagerBase::kDefaultDeviceId));
  }
}

void AudioManagerPulse::GetAudioInputDeviceNames(
    AudioDeviceNames* device_names) {
  GetAudioDeviceNames(true, device_names);
}

void AudioManagerPulse::GetAudioOutputDeviceNames(
    AudioDeviceNames* device_names) {
  GetAudioDeviceNames(false, device_names);
}

AudioParameters AudioManagerPulse::GetInputStreamParameters(
    const std::string& device_id) {
  int user_buffer_size = GetUserBufferSize();
  int buffer_size = user_buffer_size ?
      user_buffer_size : kDefaultInputBufferSize;

  // TODO(xians): add support for querying native channel layout for pulse.
  return AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
                         CHANNEL_LAYOUT_STEREO, GetNativeSampleRate(), 16,
                         buffer_size);
}

AudioOutputStream* AudioManagerPulse::MakeLinearOutputStream(
    const AudioParameters& params) {
  DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format());
  return MakeOutputStream(params, AudioManagerBase::kDefaultDeviceId);
}

AudioOutputStream* AudioManagerPulse::MakeLowLatencyOutputStream(
    const AudioParameters& params,
    const std::string& device_id) {
  DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
  return MakeOutputStream(
      params,
      device_id.empty() ? AudioManagerBase::kDefaultDeviceId : device_id);
}

AudioInputStream* AudioManagerPulse::MakeLinearInputStream(
    const AudioParameters& params, const std::string& device_id) {
  DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format());
  return MakeInputStream(params, device_id);
}

AudioInputStream* AudioManagerPulse::MakeLowLatencyInputStream(
    const AudioParameters& params, const std::string& device_id) {
  DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
  return MakeInputStream(params, device_id);
}

AudioParameters AudioManagerPulse::GetPreferredOutputStreamParameters(
    const std::string& output_device_id,
    const AudioParameters& input_params) {
  // TODO(tommi): Support |output_device_id|.
  VLOG_IF(0, !output_device_id.empty()) << "Not implemented!";

  ChannelLayout channel_layout = CHANNEL_LAYOUT_STEREO;
  int buffer_size = kMinimumOutputBufferSize;
  int bits_per_sample = 16;
  int sample_rate = GetNativeSampleRate();
  if (input_params.IsValid()) {
    bits_per_sample = input_params.bits_per_sample();
    channel_layout = input_params.channel_layout();
    buffer_size =
        std::min(kMaximumOutputBufferSize,
                 std::max(buffer_size, input_params.frames_per_buffer()));
  }

  int user_buffer_size = GetUserBufferSize();
  if (user_buffer_size)
    buffer_size = user_buffer_size;

  return AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, channel_layout,
                         sample_rate, bits_per_sample, buffer_size);
}

AudioOutputStream* AudioManagerPulse::MakeOutputStream(
    const AudioParameters& params,
    const std::string& device_id) {
  DCHECK(!device_id.empty());
  return new PulseAudioOutputStream(params, device_id, this);
}

AudioInputStream* AudioManagerPulse::MakeInputStream(
    const AudioParameters& params, const std::string& device_id) {
  return new PulseAudioInputStream(this, device_id, params,
                                   input_mainloop_, input_context_);
}

int AudioManagerPulse::GetNativeSampleRate() {
  DCHECK(input_mainloop_);
  DCHECK(input_context_);
  AutoPulseLock auto_lock(input_mainloop_);
  pa_operation* operation = pa_context_get_server_info(
      input_context_, SampleRateInfoCallback, this);
  WaitForOperationCompletion(input_mainloop_, operation);

  return native_input_sample_rate_;
}

bool AudioManagerPulse::Init() {
  DCHECK(!input_mainloop_);

#if defined(DLOPEN_PULSEAUDIO)
  StubPathMap paths;

  // Check if the pulse library is avialbale.
  paths[kModulePulse].push_back(kPulseLib);
  if (!InitializeStubs(paths)) {
    VLOG(1) << "Failed on loading the Pulse library and symbols";
    return false;
  }
#endif  // defined(DLOPEN_PULSEAUDIO)

  // Create a mainloop API and connect to the default server.
  // The mainloop is the internal asynchronous API event loop.
  input_mainloop_ = pa_threaded_mainloop_new();
  if (!input_mainloop_)
    return false;

  // Start the threaded mainloop.
  if (pa_threaded_mainloop_start(input_mainloop_))
    return false;

  // Lock the event loop object, effectively blocking the event loop thread
  // from processing events. This is necessary.
  AutoPulseLock auto_lock(input_mainloop_);

  pa_mainloop_api* pa_mainloop_api =
      pa_threaded_mainloop_get_api(input_mainloop_);
  input_context_ = pa_context_new(pa_mainloop_api, "Chrome input");
  if (!input_context_)
    return false;

  pa_context_set_state_callback(input_context_, &pulse::ContextStateCallback,
                                input_mainloop_);
  if (pa_context_connect(input_context_, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL)) {
    VLOG(1) << "Failed to connect to the context.  Error: "
            << pa_strerror(pa_context_errno(input_context_));
    return false;
  }

  // Wait until |input_context_| is ready.  pa_threaded_mainloop_wait() must be
  // called after pa_context_get_state() in case the context is already ready,
  // otherwise pa_threaded_mainloop_wait() will hang indefinitely.
  while (true) {
    pa_context_state_t context_state = pa_context_get_state(input_context_);
    if (!PA_CONTEXT_IS_GOOD(context_state))
      return false;
    if (context_state == PA_CONTEXT_READY)
      break;
    pa_threaded_mainloop_wait(input_mainloop_);
  }

  return true;
}

void AudioManagerPulse::DestroyPulse() {
  if (!input_mainloop_) {
    DCHECK(!input_context_);
    return;
  }

  {
    AutoPulseLock auto_lock(input_mainloop_);
    if (input_context_) {
      // Clear our state callback.
      pa_context_set_state_callback(input_context_, NULL, NULL);
      pa_context_disconnect(input_context_);
      pa_context_unref(input_context_);
      input_context_ = NULL;
    }
  }

  pa_threaded_mainloop_stop(input_mainloop_);
  pa_threaded_mainloop_free(input_mainloop_);
  input_mainloop_ = NULL;
}

void AudioManagerPulse::InputDevicesInfoCallback(pa_context* context,
                                                 const pa_source_info* info,
                                                 int error, void *user_data) {
  AudioManagerPulse* manager = reinterpret_cast<AudioManagerPulse*>(user_data);

  if (error) {
    // Signal the pulse object that it is done.
    pa_threaded_mainloop_signal(manager->input_mainloop_, 0);
    return;
  }

  // Exclude the output devices.
  if (info->monitor_of_sink == PA_INVALID_INDEX) {
    manager->devices_->push_back(AudioDeviceName(info->description,
                                                 info->name));
  }
}

void AudioManagerPulse::OutputDevicesInfoCallback(pa_context* context,
                                                  const pa_sink_info* info,
                                                  int error, void *user_data) {
  AudioManagerPulse* manager = reinterpret_cast<AudioManagerPulse*>(user_data);

  if (error) {
    // Signal the pulse object that it is done.
    pa_threaded_mainloop_signal(manager->input_mainloop_, 0);
    return;
  }

  manager->devices_->push_back(AudioDeviceName(info->description,
                                               info->name));
}

void AudioManagerPulse::SampleRateInfoCallback(pa_context* context,
                                               const pa_server_info* info,
                                               void* user_data) {
  AudioManagerPulse* manager = reinterpret_cast<AudioManagerPulse*>(user_data);

  manager->native_input_sample_rate_ = info->sample_spec.rate;
  pa_threaded_mainloop_signal(manager->input_mainloop_, 0);
}

}  // namespace media
