// 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_synchronized_mac.h"

#include <CoreServices/CoreServices.h>
#include <algorithm>

#include "base/basictypes.h"
#include "base/debug/trace_event.h"
#include "base/logging.h"
#include "base/mac/mac_logging.h"
#include "media/audio/mac/audio_manager_mac.h"
#include "media/base/channel_mixer.h"

namespace media {

static const int kHardwareBufferSize = 128;
static const int kFifoSize = 16384;

// TODO(crogers): handle the non-stereo case.
static const int kChannels = 2;

// This value was determined empirically for minimum latency while still
// guarding against FIFO under-runs.
static const int kBaseTargetFifoFrames = 256 + 64;

// If the input and output sample-rate don't match, then we need to maintain
// an additional safety margin due to the callback timing jitter and the
// varispeed buffering.  This value was empirically tuned.
static const int kAdditionalTargetFifoFrames = 128;

static void ZeroBufferList(AudioBufferList* buffer_list) {
  for (size_t i = 0; i < buffer_list->mNumberBuffers; ++i)
    memset(buffer_list->mBuffers[i].mData,
           0,
           buffer_list->mBuffers[i].mDataByteSize);
}

static void WrapBufferList(AudioBufferList* buffer_list,
                           AudioBus* bus,
                           int frames) {
  DCHECK(buffer_list);
  DCHECK(bus);
  int channels = bus->channels();
  int buffer_list_channels = buffer_list->mNumberBuffers;

  // Copy pointers from AudioBufferList.
  int source_idx = 0;
  for (int i = 0; i < channels; ++i) {
    bus->SetChannelData(
        i, static_cast<float*>(buffer_list->mBuffers[source_idx].mData));

    // It's ok to pass in a |buffer_list| with fewer channels, in which
    // case we just duplicate the last channel.
    if (source_idx < buffer_list_channels - 1)
      ++source_idx;
  }

  // Finally set the actual length.
  bus->set_frames(frames);
}

AudioSynchronizedStream::AudioSynchronizedStream(
    AudioManagerMac* manager,
    const AudioParameters& params,
    AudioDeviceID input_id,
    AudioDeviceID output_id)
    : manager_(manager),
      params_(params),
      input_sample_rate_(0),
      output_sample_rate_(0),
      input_id_(input_id),
      output_id_(output_id),
      input_buffer_list_(NULL),
      fifo_(kChannels, kFifoSize),
      target_fifo_frames_(kBaseTargetFifoFrames),
      average_delta_(0.0),
      fifo_rate_compensation_(1.0),
      input_unit_(0),
      varispeed_unit_(0),
      output_unit_(0),
      first_input_time_(-1),
      is_running_(false),
      hardware_buffer_size_(kHardwareBufferSize),
      channels_(kChannels) {
  VLOG(1) << "AudioSynchronizedStream::AudioSynchronizedStream()";
}

AudioSynchronizedStream::~AudioSynchronizedStream() {
  DCHECK(!input_unit_);
  DCHECK(!output_unit_);
  DCHECK(!varispeed_unit_);
}

bool AudioSynchronizedStream::Open() {
  if (params_.channels() != kChannels) {
    LOG(ERROR) << "Only stereo output is currently supported.";
    return false;
  }

  // Create the input, output, and varispeed AudioUnits.
  OSStatus result = CreateAudioUnits();
  if (result != noErr) {
    LOG(ERROR) << "Cannot create AudioUnits.";
    return false;
  }

  result = SetupInput(input_id_);
  if (result != noErr) {
    LOG(ERROR) << "Error configuring input AudioUnit.";
    return false;
  }

  result = SetupOutput(output_id_);
  if (result != noErr) {
    LOG(ERROR) << "Error configuring output AudioUnit.";
    return false;
  }

  result = SetupCallbacks();
  if (result != noErr) {
    LOG(ERROR) << "Error setting up callbacks on AudioUnits.";
    return false;
  }

  result = SetupStreamFormats();
  if (result != noErr) {
    LOG(ERROR) << "Error configuring stream formats on AudioUnits.";
    return false;
  }

  AllocateInputData();

  // Final initialization of the AudioUnits.
  result = AudioUnitInitialize(input_unit_);
  if (result != noErr) {
    LOG(ERROR) << "Error initializing input AudioUnit.";
    return false;
  }

  result = AudioUnitInitialize(output_unit_);
  if (result != noErr) {
    LOG(ERROR) << "Error initializing output AudioUnit.";
    return false;
  }

  result = AudioUnitInitialize(varispeed_unit_);
  if (result != noErr) {
    LOG(ERROR) << "Error initializing varispeed AudioUnit.";
    return false;
  }

  if (input_sample_rate_ != output_sample_rate_) {
    // Add extra safety margin.
    target_fifo_frames_ += kAdditionalTargetFifoFrames;
  }

  // Buffer initial silence corresponding to target I/O buffering.
  fifo_.Clear();
  scoped_ptr<AudioBus> silence =
      AudioBus::Create(channels_, target_fifo_frames_);
  silence->Zero();
  fifo_.Push(silence.get());

  return true;
}

void AudioSynchronizedStream::Close() {
  DCHECK(!is_running_);

  if (input_buffer_list_) {
    free(input_buffer_list_);
    input_buffer_list_ = 0;
    input_bus_.reset(NULL);
    wrapper_bus_.reset(NULL);
  }

  if (input_unit_) {
    AudioUnitUninitialize(input_unit_);
    CloseComponent(input_unit_);
  }

  if (output_unit_) {
    AudioUnitUninitialize(output_unit_);
    CloseComponent(output_unit_);
  }

  if (varispeed_unit_) {
    AudioUnitUninitialize(varispeed_unit_);
    CloseComponent(varispeed_unit_);
  }

  input_unit_ = NULL;
  output_unit_ = NULL;
  varispeed_unit_ = NULL;

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

void AudioSynchronizedStream::Start(AudioSourceCallback* callback) {
  DCHECK(callback);
  DCHECK(input_unit_);
  DCHECK(output_unit_);
  DCHECK(varispeed_unit_);

  if (is_running_ || !input_unit_ || !output_unit_ || !varispeed_unit_)
    return;

  source_ = callback;

  // Reset state variables each time we Start().
  fifo_rate_compensation_ = 1.0;
  average_delta_ = 0.0;

  OSStatus result = noErr;

  if (!is_running_) {
    first_input_time_ = -1;

    result = AudioOutputUnitStart(input_unit_);
    OSSTATUS_DCHECK(result == noErr, result);

    if (result == noErr) {
      result = AudioOutputUnitStart(output_unit_);
      OSSTATUS_DCHECK(result == noErr, result);
    }
  }

  is_running_ = true;
}

void AudioSynchronizedStream::Stop() {
  OSStatus result = noErr;
  if (is_running_) {
    result = AudioOutputUnitStop(input_unit_);
    OSSTATUS_DCHECK(result == noErr, result);

    if (result == noErr) {
      result = AudioOutputUnitStop(output_unit_);
      OSSTATUS_DCHECK(result == noErr, result);
    }
  }

  if (result == noErr)
    is_running_ = false;
}

bool AudioSynchronizedStream::IsRunning() {
  return is_running_;
}

// TODO(crogers): implement - or remove from AudioOutputStream.
void AudioSynchronizedStream::SetVolume(double volume) {}
void AudioSynchronizedStream::GetVolume(double* volume) {}

OSStatus AudioSynchronizedStream::SetOutputDeviceAsCurrent(
    AudioDeviceID output_id) {
  OSStatus result = noErr;

  // Get the default output device if device is unknown.
  if (output_id == kAudioDeviceUnknown) {
    AudioObjectPropertyAddress pa;
    pa.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
    pa.mScope = kAudioObjectPropertyScopeGlobal;
    pa.mElement = kAudioObjectPropertyElementMaster;
    UInt32 size = sizeof(output_id);

    result = AudioObjectGetPropertyData(
        kAudioObjectSystemObject,
        &pa,
        0,
        0,
        &size,
        &output_id);

    OSSTATUS_DCHECK(result == noErr, result);
    if (result != noErr)
      return result;
  }

  // Set the render frame size.
  UInt32 frame_size = hardware_buffer_size_;
  AudioObjectPropertyAddress pa;
  pa.mSelector = kAudioDevicePropertyBufferFrameSize;
  pa.mScope = kAudioDevicePropertyScopeInput;
  pa.mElement = kAudioObjectPropertyElementMaster;
  result = AudioObjectSetPropertyData(
      output_id,
      &pa,
      0,
      0,
      sizeof(frame_size),
      &frame_size);

  OSSTATUS_DCHECK(result == noErr, result);
  if (result != noErr)
    return result;

  output_info_.Initialize(output_id, false);

  // Set the Current Device to the Default Output Unit.
  result = AudioUnitSetProperty(
      output_unit_,
      kAudioOutputUnitProperty_CurrentDevice,
      kAudioUnitScope_Global,
      0,
      &output_info_.id_,
      sizeof(output_info_.id_));

  OSSTATUS_DCHECK(result == noErr, result);
  return result;
}

OSStatus AudioSynchronizedStream::SetInputDeviceAsCurrent(
    AudioDeviceID input_id) {
  OSStatus result = noErr;

  // Get the default input device if device is unknown.
  if (input_id == kAudioDeviceUnknown) {
    AudioObjectPropertyAddress pa;
    pa.mSelector = kAudioHardwarePropertyDefaultInputDevice;
    pa.mScope = kAudioObjectPropertyScopeGlobal;
    pa.mElement = kAudioObjectPropertyElementMaster;
    UInt32 size = sizeof(input_id);

    result = AudioObjectGetPropertyData(
        kAudioObjectSystemObject,
        &pa,
        0,
        0,
        &size,
        &input_id);

    OSSTATUS_DCHECK(result == noErr, result);
    if (result != noErr)
      return result;
  }

  // Set the render frame size.
  UInt32 frame_size = hardware_buffer_size_;
  AudioObjectPropertyAddress pa;
  pa.mSelector = kAudioDevicePropertyBufferFrameSize;
  pa.mScope = kAudioDevicePropertyScopeInput;
  pa.mElement = kAudioObjectPropertyElementMaster;
  result = AudioObjectSetPropertyData(
      input_id,
      &pa,
      0,
      0,
      sizeof(frame_size),
      &frame_size);

  OSSTATUS_DCHECK(result == noErr, result);
  if (result != noErr)
    return result;

  input_info_.Initialize(input_id, true);

  // Set the Current Device to the AUHAL.
  // This should be done only after I/O has been enabled on the AUHAL.
  result = AudioUnitSetProperty(
      input_unit_,
      kAudioOutputUnitProperty_CurrentDevice,
      kAudioUnitScope_Global,
      0,
      &input_info_.id_,
      sizeof(input_info_.id_));

  OSSTATUS_DCHECK(result == noErr, result);
  return result;
}

OSStatus AudioSynchronizedStream::CreateAudioUnits() {
  // Q: Why do we need a varispeed unit?
  // A: If the input device and the output device are running at
  // different sample rates and/or on different clocks, we will need
  // to compensate to avoid a pitch change and
  // to avoid buffer under and over runs.
  ComponentDescription varispeed_desc;
  varispeed_desc.componentType = kAudioUnitType_FormatConverter;
  varispeed_desc.componentSubType = kAudioUnitSubType_Varispeed;
  varispeed_desc.componentManufacturer = kAudioUnitManufacturer_Apple;
  varispeed_desc.componentFlags = 0;
  varispeed_desc.componentFlagsMask = 0;

  Component varispeed_comp = FindNextComponent(NULL, &varispeed_desc);
  if (varispeed_comp == NULL)
    return -1;

  OSStatus result = OpenAComponent(varispeed_comp, &varispeed_unit_);
  OSSTATUS_DCHECK(result == noErr, result);
  if (result != noErr)
    return result;

  // Open input AudioUnit.
  ComponentDescription input_desc;
  input_desc.componentType = kAudioUnitType_Output;
  input_desc.componentSubType = kAudioUnitSubType_HALOutput;
  input_desc.componentManufacturer = kAudioUnitManufacturer_Apple;
  input_desc.componentFlags = 0;
  input_desc.componentFlagsMask = 0;

  Component input_comp = FindNextComponent(NULL, &input_desc);
  if (input_comp == NULL)
    return -1;

  result = OpenAComponent(input_comp, &input_unit_);
  OSSTATUS_DCHECK(result == noErr, result);
  if (result != noErr)
    return result;

  // Open output AudioUnit.
  ComponentDescription output_desc;
  output_desc.componentType = kAudioUnitType_Output;
  output_desc.componentSubType = kAudioUnitSubType_DefaultOutput;
  output_desc.componentManufacturer = kAudioUnitManufacturer_Apple;
  output_desc.componentFlags = 0;
  output_desc.componentFlagsMask = 0;

  Component output_comp = FindNextComponent(NULL, &output_desc);
  if (output_comp == NULL)
    return -1;

  result = OpenAComponent(output_comp, &output_unit_);
  OSSTATUS_DCHECK(result == noErr, result);
  if (result != noErr)
    return result;

  return noErr;
}

OSStatus AudioSynchronizedStream::SetupInput(AudioDeviceID input_id) {
  // The AUHAL used for input needs to be initialized
  // before anything is done to it.
  OSStatus result = AudioUnitInitialize(input_unit_);
  OSSTATUS_DCHECK(result == noErr, result);
  if (result != noErr)
    return result;

  // We must enable the Audio Unit (AUHAL) for input and disable output
  // BEFORE setting the AUHAL's current device.
  result = EnableIO();
  OSSTATUS_DCHECK(result == noErr, result);
  if (result != noErr)
    return result;

  result = SetInputDeviceAsCurrent(input_id);
  OSSTATUS_DCHECK(result == noErr, result);

  return result;
}

OSStatus AudioSynchronizedStream::EnableIO() {
  // Enable input on the AUHAL.
  UInt32 enable_io = 1;
  OSStatus result = AudioUnitSetProperty(
      input_unit_,
      kAudioOutputUnitProperty_EnableIO,
      kAudioUnitScope_Input,
      1,  // input element
      &enable_io,
      sizeof(enable_io));

  OSSTATUS_DCHECK(result == noErr, result);
  if (result != noErr)
    return result;

  // Disable Output on the AUHAL.
  enable_io = 0;
  result = AudioUnitSetProperty(
      input_unit_,
      kAudioOutputUnitProperty_EnableIO,
      kAudioUnitScope_Output,
      0,  // output element
      &enable_io,
      sizeof(enable_io));

  OSSTATUS_DCHECK(result == noErr, result);
  return result;
}

OSStatus AudioSynchronizedStream::SetupOutput(AudioDeviceID output_id) {
  OSStatus result = noErr;

  result = SetOutputDeviceAsCurrent(output_id);
  OSSTATUS_DCHECK(result == noErr, result);
  if (result != noErr)
    return result;

  // Tell the output unit not to reset timestamps.
  // Otherwise sample rate changes will cause sync loss.
  UInt32 start_at_zero = 0;
  result = AudioUnitSetProperty(
      output_unit_,
      kAudioOutputUnitProperty_StartTimestampsAtZero,
      kAudioUnitScope_Global,
      0,
      &start_at_zero,
      sizeof(start_at_zero));

  OSSTATUS_DCHECK(result == noErr, result);

  return result;
}

OSStatus AudioSynchronizedStream::SetupCallbacks() {
  // Set the input callback.
  AURenderCallbackStruct callback;
  callback.inputProc = InputProc;
  callback.inputProcRefCon = this;
  OSStatus result = AudioUnitSetProperty(
      input_unit_,
      kAudioOutputUnitProperty_SetInputCallback,
      kAudioUnitScope_Global,
      0,
      &callback,
      sizeof(callback));

  OSSTATUS_DCHECK(result == noErr, result);
  if (result != noErr)
    return result;

  // Set the output callback.
  callback.inputProc = OutputProc;
  callback.inputProcRefCon = this;
  result = AudioUnitSetProperty(
      output_unit_,
      kAudioUnitProperty_SetRenderCallback,
      kAudioUnitScope_Input,
      0,
      &callback,
      sizeof(callback));

  OSSTATUS_DCHECK(result == noErr, result);
  if (result != noErr)
    return result;

  // Set the varispeed callback.
  callback.inputProc = VarispeedProc;
  callback.inputProcRefCon = this;
  result = AudioUnitSetProperty(
      varispeed_unit_,
      kAudioUnitProperty_SetRenderCallback,
      kAudioUnitScope_Input,
      0,
      &callback,
      sizeof(callback));

  OSSTATUS_DCHECK(result == noErr, result);

  return result;
}

OSStatus AudioSynchronizedStream::SetupStreamFormats() {
  AudioStreamBasicDescription asbd, asbd_dev1_in, asbd_dev2_out;

  // Get the Stream Format (Output client side).
  UInt32 property_size = sizeof(asbd_dev1_in);
  OSStatus result = AudioUnitGetProperty(
      input_unit_,
      kAudioUnitProperty_StreamFormat,
      kAudioUnitScope_Input,
      1,
      &asbd_dev1_in,
      &property_size);

  OSSTATUS_DCHECK(result == noErr, result);
  if (result != noErr)
    return result;

  // Get the Stream Format (client side).
  property_size = sizeof(asbd);
  result = AudioUnitGetProperty(
      input_unit_,
      kAudioUnitProperty_StreamFormat,
      kAudioUnitScope_Output,
      1,
      &asbd,
      &property_size);

  OSSTATUS_DCHECK(result == noErr, result);
  if (result != noErr)
    return result;

  // Get the Stream Format (Output client side).
  property_size = sizeof(asbd_dev2_out);
  result = AudioUnitGetProperty(
      output_unit_,
      kAudioUnitProperty_StreamFormat,
      kAudioUnitScope_Output,
      0,
      &asbd_dev2_out,
      &property_size);

  OSSTATUS_DCHECK(result == noErr, result);
  if (result != noErr)
    return result;

  // Set the format of all the AUs to the input/output devices channel count.
  // For a simple case, you want to set this to
  // the lower of count of the channels in the input device vs output device.
  asbd.mChannelsPerFrame = std::min(asbd_dev1_in.mChannelsPerFrame,
                                    asbd_dev2_out.mChannelsPerFrame);

  // We must get the sample rate of the input device and set it to the
  // stream format of AUHAL.
  Float64 rate = 0;
  property_size = sizeof(rate);

  AudioObjectPropertyAddress pa;
  pa.mSelector = kAudioDevicePropertyNominalSampleRate;
  pa.mScope = kAudioObjectPropertyScopeWildcard;
  pa.mElement = kAudioObjectPropertyElementMaster;
  result = AudioObjectGetPropertyData(
      input_info_.id_,
      &pa,
      0,
      0,
      &property_size,
      &rate);

  OSSTATUS_DCHECK(result == noErr, result);
  if (result != noErr)
    return result;

  input_sample_rate_ = rate;

  asbd.mSampleRate = rate;
  property_size = sizeof(asbd);

  // Set the new formats to the AUs...
  result = AudioUnitSetProperty(
      input_unit_,
      kAudioUnitProperty_StreamFormat,
      kAudioUnitScope_Output,
      1,
      &asbd,
      property_size);

  OSSTATUS_DCHECK(result == noErr, result);
  if (result != noErr)
    return result;

  result = AudioUnitSetProperty(
      varispeed_unit_,
      kAudioUnitProperty_StreamFormat,
      kAudioUnitScope_Input,
      0,
      &asbd,
      property_size);

  OSSTATUS_DCHECK(result == noErr, result);
  if (result != noErr)
    return result;

  // Set the correct sample rate for the output device,
  // but keep the channel count the same.
  property_size = sizeof(rate);

  pa.mSelector = kAudioDevicePropertyNominalSampleRate;
  pa.mScope = kAudioObjectPropertyScopeWildcard;
  pa.mElement = kAudioObjectPropertyElementMaster;
  result = AudioObjectGetPropertyData(
      output_info_.id_,
      &pa,
      0,
      0,
      &property_size,
      &rate);

  OSSTATUS_DCHECK(result == noErr, result);
  if (result != noErr)
    return result;

  output_sample_rate_ = rate;

  // The requested sample-rate must match the hardware sample-rate.
  if (output_sample_rate_ != params_.sample_rate()) {
    LOG(ERROR) << "Requested sample-rate: " << params_.sample_rate()
        <<  " must match the hardware sample-rate: " << output_sample_rate_;
    return kAudioDeviceUnsupportedFormatError;
  }

  asbd.mSampleRate = rate;
  property_size = sizeof(asbd);

  // Set the new audio stream formats for the rest of the AUs...
  result = AudioUnitSetProperty(
      varispeed_unit_,
      kAudioUnitProperty_StreamFormat,
      kAudioUnitScope_Output,
      0,
      &asbd,
      property_size);

  OSSTATUS_DCHECK(result == noErr, result);
  if (result != noErr)
    return result;

  result = AudioUnitSetProperty(
      output_unit_,
      kAudioUnitProperty_StreamFormat,
      kAudioUnitScope_Input,
      0,
      &asbd,
      property_size);

  OSSTATUS_DCHECK(result == noErr, result);
  return result;
}

void AudioSynchronizedStream::AllocateInputData() {
  // Get the native number of input channels that the hardware supports.
  int hardware_channels = 0;
  bool got_hardware_channels = AudioManagerMac::GetDeviceChannels(
      input_id_, kAudioDevicePropertyScopeInput, &hardware_channels);
  if (!got_hardware_channels || hardware_channels > 2) {
    // Only mono and stereo are supported on the input side. When it fails to
    // get the native channel number or the native channel number is bigger
    // than 2, we open the device in stereo mode.
    hardware_channels = 2;
  }

  // Allocate storage for the AudioBufferList used for the
  // input data from the input AudioUnit.
  // We allocate enough space for with one AudioBuffer per channel.
  size_t malloc_size = offsetof(AudioBufferList, mBuffers[0]) +
      (sizeof(AudioBuffer) * hardware_channels);

  input_buffer_list_ = static_cast<AudioBufferList*>(malloc(malloc_size));
  input_buffer_list_->mNumberBuffers = hardware_channels;

  input_bus_ = AudioBus::Create(hardware_channels, hardware_buffer_size_);
  wrapper_bus_ = AudioBus::CreateWrapper(channels_);
  if (hardware_channels != params_.input_channels()) {
    ChannelLayout hardware_channel_layout =
        GuessChannelLayout(hardware_channels);
    ChannelLayout requested_channel_layout =
        GuessChannelLayout(params_.input_channels());
    channel_mixer_.reset(new ChannelMixer(hardware_channel_layout,
                                          requested_channel_layout));
    mixer_bus_ = AudioBus::Create(params_.input_channels(),
                                  hardware_buffer_size_);
  }

  // Allocate buffers for AudioBufferList.
  UInt32 buffer_size_bytes = input_bus_->frames() * sizeof(Float32);
  for (size_t i = 0; i < input_buffer_list_->mNumberBuffers; ++i) {
    input_buffer_list_->mBuffers[i].mNumberChannels = 1;
    input_buffer_list_->mBuffers[i].mDataByteSize = buffer_size_bytes;
    input_buffer_list_->mBuffers[i].mData = input_bus_->channel(i);
  }
}

OSStatus AudioSynchronizedStream::HandleInputCallback(
    AudioUnitRenderActionFlags* io_action_flags,
    const AudioTimeStamp* time_stamp,
    UInt32 bus_number,
    UInt32 number_of_frames,
    AudioBufferList* io_data) {
  TRACE_EVENT0("audio", "AudioSynchronizedStream::HandleInputCallback");

  if (first_input_time_ < 0.0)
    first_input_time_ = time_stamp->mSampleTime;

  // Get the new audio input data.
  OSStatus result = AudioUnitRender(
      input_unit_,
      io_action_flags,
      time_stamp,
      bus_number,
      number_of_frames,
      input_buffer_list_);

  // TODO(xians): Add back the DCHECK after synchronize IO supports all
  // combination of input and output params. See http://issue/246521.
  if (result != noErr)
    return result;

  // Buffer input into FIFO.
  int available_frames = fifo_.max_frames() - fifo_.frames();
  if (input_bus_->frames() <= available_frames) {
    if (channel_mixer_) {
      channel_mixer_->Transform(input_bus_.get(), mixer_bus_.get());
      fifo_.Push(mixer_bus_.get());
    } else {
      fifo_.Push(input_bus_.get());
    }
  }

  return result;
}

OSStatus AudioSynchronizedStream::HandleVarispeedCallback(
    AudioUnitRenderActionFlags* io_action_flags,
    const AudioTimeStamp* time_stamp,
    UInt32 bus_number,
    UInt32 number_of_frames,
    AudioBufferList* io_data) {
  // Create a wrapper bus on the AudioBufferList.
  WrapBufferList(io_data, wrapper_bus_.get(), number_of_frames);

  if (fifo_.frames() < static_cast<int>(number_of_frames)) {
    // We don't DCHECK here, since this is a possible run-time condition
    // if the machine is bogged down.
    wrapper_bus_->Zero();
    return noErr;
  }

  // Read from the FIFO to feed the varispeed.
  fifo_.Consume(wrapper_bus_.get(), 0, number_of_frames);

  return noErr;
}

OSStatus AudioSynchronizedStream::HandleOutputCallback(
    AudioUnitRenderActionFlags* io_action_flags,
    const AudioTimeStamp* time_stamp,
    UInt32 bus_number,
    UInt32 number_of_frames,
    AudioBufferList* io_data) {
  // Input callback hasn't run yet or we've suddenly changed sample-rates
  // -> silence.
  if (first_input_time_ < 0.0 ||
      static_cast<int>(number_of_frames) != params_.frames_per_buffer()) {
    ZeroBufferList(io_data);
    return noErr;
  }

  // Use the varispeed playback rate to offset small discrepancies
  // in hardware clocks, and also any differences in sample-rate
  // between input and output devices.

  // Calculate a varispeed rate scalar factor to compensate for drift between
  // input and output.  We use the actual number of frames still in the FIFO
  // compared with the ideal value of |target_fifo_frames_|.
  int delta = fifo_.frames() - target_fifo_frames_;

  // Average |delta| because it can jitter back/forth quite frequently
  // by +/- the hardware buffer-size *if* the input and output callbacks are
  // happening at almost exactly the same time.  Also, if the input and output
  // sample-rates are different then |delta| will jitter quite a bit due to
  // the rate conversion happening in the varispeed, plus the jittering of
  // the callbacks.  The average value is what's important here.
  average_delta_ += (delta - average_delta_) * 0.1;

  // Compute a rate compensation which always attracts us back to the
  // |target_fifo_frames_| over a period of kCorrectionTimeSeconds.
  const double kCorrectionTimeSeconds = 0.1;
  double correction_time_frames = kCorrectionTimeSeconds * output_sample_rate_;
  fifo_rate_compensation_ =
      (correction_time_frames + average_delta_) / correction_time_frames;

  // Adjust for FIFO drift.
  OSStatus result = AudioUnitSetParameter(
      varispeed_unit_,
      kVarispeedParam_PlaybackRate,
      kAudioUnitScope_Global,
      0,
      fifo_rate_compensation_,
      0);

  OSSTATUS_DCHECK(result == noErr, result);
  if (result != noErr)
    return result;

  // Render to the output using the varispeed.
  result = AudioUnitRender(
      varispeed_unit_,
      io_action_flags,
      time_stamp,
      0,
      number_of_frames,
      io_data);

  OSSTATUS_DCHECK(result == noErr, result);
  if (result != noErr)
    return result;

  // Create a wrapper bus on the AudioBufferList.
  WrapBufferList(io_data, wrapper_bus_.get(), number_of_frames);

  // Process in-place!
  source_->OnMoreIOData(wrapper_bus_.get(),
                        wrapper_bus_.get(),
                        AudioBuffersState(0, 0));

  return noErr;
}

OSStatus AudioSynchronizedStream::InputProc(
    void* user_data,
    AudioUnitRenderActionFlags* io_action_flags,
    const AudioTimeStamp* time_stamp,
    UInt32 bus_number,
    UInt32 number_of_frames,
    AudioBufferList* io_data) {
  AudioSynchronizedStream* stream =
      static_cast<AudioSynchronizedStream*>(user_data);
  DCHECK(stream);

  return stream->HandleInputCallback(
      io_action_flags,
      time_stamp,
      bus_number,
      number_of_frames,
      io_data);
}

OSStatus AudioSynchronizedStream::VarispeedProc(
    void* user_data,
    AudioUnitRenderActionFlags* io_action_flags,
    const AudioTimeStamp* time_stamp,
    UInt32 bus_number,
    UInt32 number_of_frames,
    AudioBufferList* io_data) {
  AudioSynchronizedStream* stream =
      static_cast<AudioSynchronizedStream*>(user_data);
  DCHECK(stream);

  return stream->HandleVarispeedCallback(
      io_action_flags,
      time_stamp,
      bus_number,
      number_of_frames,
      io_data);
}

OSStatus AudioSynchronizedStream::OutputProc(
    void* user_data,
    AudioUnitRenderActionFlags* io_action_flags,
    const AudioTimeStamp* time_stamp,
    UInt32 bus_number,
    UInt32 number_of_frames,
    AudioBufferList* io_data) {
  AudioSynchronizedStream* stream =
      static_cast<AudioSynchronizedStream*>(user_data);
  DCHECK(stream);

  return stream->HandleOutputCallback(
      io_action_flags,
      time_stamp,
      bus_number,
      number_of_frames,
      io_data);
}

void AudioSynchronizedStream::AudioDeviceInfo::Initialize(
    AudioDeviceID id, bool is_input) {
  id_ = id;
  is_input_ = is_input;
  if (id_ == kAudioDeviceUnknown)
    return;

  UInt32 property_size = sizeof(buffer_size_frames_);

  AudioObjectPropertyAddress pa;
  pa.mSelector = kAudioDevicePropertyBufferFrameSize;
  pa.mScope = kAudioObjectPropertyScopeWildcard;
  pa.mElement = kAudioObjectPropertyElementMaster;
  OSStatus result = AudioObjectGetPropertyData(
      id_,
      &pa,
      0,
      0,
      &property_size,
      &buffer_size_frames_);

  OSSTATUS_DCHECK(result == noErr, result);
}

}  // namespace media