// 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/audio/mac/audio_low_latency_output_mac.h"

#include <CoreServices/CoreServices.h>

#include "base/basictypes.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "base/mac/mac_logging.h"
#include "media/audio/audio_util.h"
#include "media/audio/mac/audio_manager_mac.h"
#include "media/base/media_switches.h"

namespace media {

static std::ostream& operator<<(std::ostream& os,
                                const AudioStreamBasicDescription& format) {
  os << "sample rate       : " << format.mSampleRate << std::endl
     << "format ID         : " << format.mFormatID << std::endl
     << "format flags      : " << format.mFormatFlags << std::endl
     << "bytes per packet  : " << format.mBytesPerPacket << std::endl
     << "frames per packet : " << format.mFramesPerPacket << std::endl
     << "bytes per frame   : " << format.mBytesPerFrame << std::endl
     << "channels per frame: " << format.mChannelsPerFrame << std::endl
     << "bits per channel  : " << format.mBitsPerChannel;
  return os;
}

static AudioObjectPropertyAddress kDefaultOutputDeviceAddress = {
  kAudioHardwarePropertyDefaultOutputDevice,
  kAudioObjectPropertyScopeGlobal,
  kAudioObjectPropertyElementMaster
};

// Overview of operation:
// 1) An object of AUAudioOutputStream is created by the AudioManager
// factory: audio_man->MakeAudioStream().
// 2) Next some thread will call Open(), at that point the underlying
// default output Audio Unit is created and configured.
// 3) Then some thread will call Start(source).
// Then the Audio Unit is started which creates its own thread which
// periodically will call the source for more data as buffers are being
// consumed.
// 4) At some point some thread will call Stop(), which we handle by directly
// stopping the default output Audio Unit.
// 6) The same thread that called stop will call Close() where we cleanup
// and notify the audio manager, which likely will destroy this object.

AUAudioOutputStream::AUAudioOutputStream(
    AudioManagerMac* manager, const AudioParameters& params)
    : manager_(manager),
      source_(NULL),
      output_unit_(0),
      output_device_id_(kAudioObjectUnknown),
      volume_(1),
      hardware_latency_frames_(0),
      stopped_(false),
      audio_bus_(AudioBus::Create(params)) {
  // We must have a manager.
  DCHECK(manager_);

  // A frame is one sample across all channels. In interleaved audio the per
  // frame fields identify the set of n |channels|. In uncompressed audio, a
  // packet is always one frame.
  format_.mSampleRate = params.sample_rate();
  format_.mFormatID = kAudioFormatLinearPCM;
  format_.mFormatFlags = kLinearPCMFormatFlagIsPacked |
                         kLinearPCMFormatFlagIsSignedInteger;
  format_.mBitsPerChannel = params.bits_per_sample();
  format_.mChannelsPerFrame = params.channels();
  format_.mFramesPerPacket = 1;
  format_.mBytesPerPacket = (format_.mBitsPerChannel * params.channels()) / 8;
  format_.mBytesPerFrame = format_.mBytesPerPacket;
  format_.mReserved = 0;

  DVLOG(1) << "Desired ouput format: " << format_;

  // Calculate the number of sample frames per callback.
  number_of_frames_ = params.GetBytesPerBuffer() / format_.mBytesPerPacket;
  DVLOG(1) << "Number of frames per callback: " << number_of_frames_;
  const AudioParameters parameters =
      manager_->GetDefaultOutputStreamParameters();
  CHECK_EQ(number_of_frames_,
           static_cast<size_t>(parameters.frames_per_buffer()));
}

AUAudioOutputStream::~AUAudioOutputStream() {
}

bool AUAudioOutputStream::Open() {
  // Obtain the current input device selected by the user.
  UInt32 size = sizeof(output_device_id_);
  OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
                                               &kDefaultOutputDeviceAddress,
                                               0,
                                               0,
                                               &size,
                                               &output_device_id_);
  if (result != noErr || output_device_id_ == kAudioObjectUnknown) {
    OSSTATUS_DLOG(WARNING, result)
        << "Could not get default audio output device.";
    return false;
  }

  // Open and initialize the DefaultOutputUnit.
  AudioComponent comp;
  AudioComponentDescription desc;

  desc.componentType = kAudioUnitType_Output;
  desc.componentSubType = kAudioUnitSubType_DefaultOutput;
  desc.componentManufacturer = kAudioUnitManufacturer_Apple;
  desc.componentFlags = 0;
  desc.componentFlagsMask = 0;
  comp = AudioComponentFindNext(0, &desc);
  if (!comp)
    return false;

  result = AudioComponentInstanceNew(comp, &output_unit_);
  if (result != noErr) {
    OSSTATUS_DLOG(WARNING, result) << "AudioComponentInstanceNew() failed.";
    return false;
  }

  result = AudioUnitInitialize(output_unit_);
  if (result != noErr) {
    OSSTATUS_DLOG(WARNING, result) << "AudioUnitInitialize() failed.";
    return false;
  }

  hardware_latency_frames_ = GetHardwareLatency();

  return Configure();
}

bool AUAudioOutputStream::Configure() {
  // Set the render callback.
  AURenderCallbackStruct input;
  input.inputProc = InputProc;
  input.inputProcRefCon = this;
  OSStatus result = AudioUnitSetProperty(
      output_unit_,
      kAudioUnitProperty_SetRenderCallback,
      kAudioUnitScope_Global,
      0,
      &input,
      sizeof(input));
  if (result != noErr) {
    OSSTATUS_DLOG(WARNING, result)
      << "AudioUnitSetProperty(kAudioUnitProperty_SetRenderCallback) failed.";
    return false;
  }

  // Set the stream format.
  result = AudioUnitSetProperty(
      output_unit_,
      kAudioUnitProperty_StreamFormat,
      kAudioUnitScope_Input,
      0,
      &format_,
      sizeof(format_));
  if (result != noErr) {
    OSSTATUS_DLOG(WARNING, result)
        << "AudioUnitSetProperty(kAudioUnitProperty_StreamFormat) failed.";
    return false;
  }

  // Set the buffer frame size.
  // WARNING: Setting this value changes the frame size for all audio units in
  // the current process.  It's imperative that the input and output frame sizes
  // be the same as the frames_per_buffer() returned by
  // GetDefaultOutputStreamParameters.
  // See http://crbug.com/154352 for details.
  UInt32 buffer_size = number_of_frames_;
  result = AudioUnitSetProperty(
      output_unit_,
      kAudioDevicePropertyBufferFrameSize,
      kAudioUnitScope_Output,
      0,
      &buffer_size,
      sizeof(buffer_size));
  if (result != noErr) {
    OSSTATUS_DLOG(WARNING, result)
        << "AudioUnitSetProperty(kAudioDevicePropertyBufferFrameSize) failed.";
    return false;
  }

  return true;
}

void AUAudioOutputStream::Close() {
  if (output_unit_)
    AudioComponentInstanceDispose(output_unit_);

  // Inform the audio manager that we have been closed. This can cause our
  // destruction.
  manager_->ReleaseOutputStream(this);
}

void AUAudioOutputStream::Start(AudioSourceCallback* callback) {
  DCHECK(callback);
  if (!output_unit_) {
    DLOG(ERROR) << "Open() has not been called successfully";
    return;
  }

  stopped_ = false;
  {
    base::AutoLock auto_lock(source_lock_);
    source_ = callback;
  }

  AudioOutputUnitStart(output_unit_);
}

void AUAudioOutputStream::Stop() {
  if (stopped_)
    return;

  AudioOutputUnitStop(output_unit_);

  base::AutoLock auto_lock(source_lock_);
  source_ = NULL;
  stopped_ = true;
}

void AUAudioOutputStream::SetVolume(double volume) {
  if (!output_unit_)
    return;
  volume_ = static_cast<float>(volume);

  // TODO(crogers): set volume property
}

void AUAudioOutputStream::GetVolume(double* volume) {
  if (!output_unit_)
    return;
  *volume = volume_;
}

// Pulls on our provider to get rendered audio stream.
// Note to future hackers of this function: Do not add locks here because this
// is running on a real-time thread (for low-latency).
OSStatus AUAudioOutputStream::Render(UInt32 number_of_frames,
                                     AudioBufferList* io_data,
                                     const AudioTimeStamp* output_time_stamp) {
  // Update the playout latency.
  double playout_latency_frames = GetPlayoutLatency(output_time_stamp);

  AudioBuffer& buffer = io_data->mBuffers[0];
  uint8* audio_data = reinterpret_cast<uint8*>(buffer.mData);
  uint32 hardware_pending_bytes = static_cast<uint32>
      ((playout_latency_frames + 0.5) * format_.mBytesPerFrame);

  // Unfortunately AUAudioInputStream and AUAudioOutputStream share the frame
  // size set by kAudioDevicePropertyBufferFrameSize above on a per process
  // basis.  What this means is that the |number_of_frames| value may be larger
  // or smaller than the value set during Configure().  In this case either
  // audio input or audio output will be broken, so just output silence.
  // TODO(crogers): Figure out what can trigger a change in |number_of_frames|.
  // See http://crbug.com/154352 for details.
  if (number_of_frames != static_cast<UInt32>(audio_bus_->frames())) {
    memset(audio_data, 0, number_of_frames * format_.mBytesPerFrame);
    return noErr;
  }

  int frames_filled = 0;
  {
    // Render() shouldn't be called except between AudioOutputUnitStart() and
    // AudioOutputUnitStop() calls, but crash reports have shown otherwise:
    // http://crbug.com/178765.  We use |source_lock_| to prevent races and
    // crashes in Render() when |source_| is cleared.
    base::AutoLock auto_lock(source_lock_);
    if (!source_) {
      memset(audio_data, 0, number_of_frames * format_.mBytesPerFrame);
      return noErr;
    }

    frames_filled = source_->OnMoreData(
        audio_bus_.get(), AudioBuffersState(0, hardware_pending_bytes));
  }

  // Note: If this ever changes to output raw float the data must be clipped and
  // sanitized since it may come from an untrusted source such as NaCl.
  audio_bus_->ToInterleaved(
      frames_filled, format_.mBitsPerChannel / 8, audio_data);
  uint32 filled = frames_filled * format_.mBytesPerFrame;

  // Perform in-place, software-volume adjustments.
  media::AdjustVolume(audio_data,
                      filled,
                      audio_bus_->channels(),
                      format_.mBitsPerChannel / 8,
                      volume_);

  return noErr;
}

// DefaultOutputUnit callback
OSStatus AUAudioOutputStream::InputProc(void* user_data,
                                        AudioUnitRenderActionFlags*,
                                        const AudioTimeStamp* output_time_stamp,
                                        UInt32,
                                        UInt32 number_of_frames,
                                        AudioBufferList* io_data) {
  AUAudioOutputStream* audio_output =
      static_cast<AUAudioOutputStream*>(user_data);
  if (!audio_output)
    return -1;

  return audio_output->Render(number_of_frames, io_data, output_time_stamp);
}

int AUAudioOutputStream::HardwareSampleRate() {
  // Determine the default output device's sample-rate.
  AudioDeviceID device_id = kAudioObjectUnknown;
  UInt32 info_size = sizeof(device_id);
  OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
                                               &kDefaultOutputDeviceAddress,
                                               0,
                                               0,
                                               &info_size,
                                               &device_id);
  if (result != noErr || device_id == kAudioObjectUnknown) {
    OSSTATUS_DLOG(WARNING, result)
        << "Could not get default audio output device.";
    return 0;
  }

  Float64 nominal_sample_rate;
  info_size = sizeof(nominal_sample_rate);

  AudioObjectPropertyAddress nominal_sample_rate_address = {
      kAudioDevicePropertyNominalSampleRate,
      kAudioObjectPropertyScopeGlobal,
      kAudioObjectPropertyElementMaster
  };
  result = AudioObjectGetPropertyData(device_id,
                                      &nominal_sample_rate_address,
                                      0,
                                      0,
                                      &info_size,
                                      &nominal_sample_rate);
  if (result != noErr) {
    OSSTATUS_DLOG(WARNING, result)
        << "Could not get default sample rate for device: " << device_id;
    return 0;
  }

  return static_cast<int>(nominal_sample_rate);
}

double AUAudioOutputStream::GetHardwareLatency() {
  if (!output_unit_ || output_device_id_ == kAudioObjectUnknown) {
    DLOG(WARNING) << "Audio unit object is NULL or device ID is unknown";
    return 0.0;
  }

  // Get audio unit latency.
  Float64 audio_unit_latency_sec = 0.0;
  UInt32 size = sizeof(audio_unit_latency_sec);
  OSStatus result = AudioUnitGetProperty(output_unit_,
                                         kAudioUnitProperty_Latency,
                                         kAudioUnitScope_Global,
                                         0,
                                         &audio_unit_latency_sec,
                                         &size);
  if (result != noErr) {
    OSSTATUS_DLOG(WARNING, result) << "Could not get audio unit latency";
    return 0.0;
  }

  // Get output audio device latency.
  AudioObjectPropertyAddress property_address = {
    kAudioDevicePropertyLatency,
    kAudioDevicePropertyScopeOutput,
    kAudioObjectPropertyElementMaster
  };
  UInt32 device_latency_frames = 0;
  size = sizeof(device_latency_frames);
  result = AudioObjectGetPropertyData(output_device_id_,
                                      &property_address,
                                      0,
                                      NULL,
                                      &size,
                                      &device_latency_frames);
  if (result != noErr) {
    OSSTATUS_DLOG(WARNING, result) << "Could not get audio unit latency";
    return 0.0;
  }

  return static_cast<double>((audio_unit_latency_sec *
      format_.mSampleRate) + device_latency_frames);
}

double AUAudioOutputStream::GetPlayoutLatency(
    const AudioTimeStamp* output_time_stamp) {
  // Ensure mHostTime is valid.
  if ((output_time_stamp->mFlags & kAudioTimeStampHostTimeValid) == 0)
    return 0;

  // Get the delay between the moment getting the callback and the scheduled
  // time stamp that tells when the data is going to be played out.
  UInt64 output_time_ns = AudioConvertHostTimeToNanos(
      output_time_stamp->mHostTime);
  UInt64 now_ns = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());

  // Prevent overflow leading to huge delay information; occurs regularly on
  // the bots, probably less so in the wild.
  if (now_ns > output_time_ns)
    return 0;

  double delay_frames = static_cast<double>
      (1e-9 * (output_time_ns - now_ns) * format_.mSampleRate);

  return (delay_frames + hardware_latency_frames_);
}

}  // namespace media
