// 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.

#ifndef MEDIA_AUDIO_MAC_AUDIO_SYNCHRONIZED_MAC_H_
#define MEDIA_AUDIO_MAC_AUDIO_SYNCHRONIZED_MAC_H_

#include <AudioToolbox/AudioToolbox.h>
#include <AudioUnit/AudioUnit.h>
#include <CoreAudio/CoreAudio.h>

#include "base/compiler_specific.h"
#include "base/synchronization/lock.h"
#include "media/audio/audio_io.h"
#include "media/audio/audio_parameters.h"
#include "media/base/audio_bus.h"
#include "media/base/audio_fifo.h"

namespace media {

class AudioManagerMac;
class ChannelMixer;

// AudioSynchronizedStream allows arbitrary combinations of input and output
// devices running off different clocks and using different drivers, with
// potentially differing sample-rates.  It implements AudioOutputStream
// and shuttles its synchronized I/O data using AudioSourceCallback.
//
// It is required to first acquire the native sample rate of the selected
// output device and then use the same rate when creating this object.
//
// ............................................................................
// Theory of Operation:
//                                       .
//      INPUT THREAD                     .             OUTPUT THREAD
//   +-----------------+     +------+    .
//   | Input AudioUnit | --> |      |    .
//   +-----------------+     |      |    .
//                           | FIFO |    .
//                           |      |        +-----------+
//                           |      | -----> | Varispeed |
//                           |      |        +-----------+
//                           +------+    .         |
//                                       .         |              +-----------+
//                                       .    OnMoreIOData() -->  | Output AU |
//                                       .                        +-----------+
//
// The input AudioUnit's InputProc is called on one thread which feeds the
// FIFO.  The output AudioUnit's OutputProc is called on a second thread
// which pulls on the varispeed to get the current input data.  The varispeed
// handles mismatches between input and output sample-rate and also clock drift
// between the input and output drivers.  The varispeed consumes its data from
// the FIFO and adjusts its rate dynamically according to the amount
// of data buffered in the FIFO.  If the FIFO starts getting too much data
// buffered then the varispeed will speed up slightly to compensate
// and similarly if the FIFO doesn't have enough data buffered then the
// varispeed will slow down slightly.
//
// Finally, once the input data is available then OnMoreIOData() is called
// which is given this input, and renders the output which is finally sent
// to the Output AudioUnit.
class AudioSynchronizedStream : public AudioOutputStream {
 public:
  // The ctor takes all the usual parameters, plus |manager| which is the
  // the audio manager who is creating this object.
  AudioSynchronizedStream(AudioManagerMac* manager,
                          const AudioParameters& params,
                          AudioDeviceID input_id,
                          AudioDeviceID output_id);

  virtual ~AudioSynchronizedStream();

  // Implementation of AudioOutputStream.
  virtual bool Open() OVERRIDE;
  virtual void Close() OVERRIDE;
  virtual void Start(AudioSourceCallback* callback) OVERRIDE;
  virtual void Stop() OVERRIDE;

  virtual void SetVolume(double volume) OVERRIDE;
  virtual void GetVolume(double* volume) OVERRIDE;

  OSStatus SetInputDeviceAsCurrent(AudioDeviceID input_id);
  OSStatus SetOutputDeviceAsCurrent(AudioDeviceID output_id);
  AudioDeviceID GetInputDeviceID() { return input_info_.id_;  }
  AudioDeviceID GetOutputDeviceID() { return output_info_.id_; }

  bool IsRunning();

 private:
  // Initialization.
  OSStatus CreateAudioUnits();
  OSStatus SetupInput(AudioDeviceID input_id);
  OSStatus EnableIO();
  OSStatus SetupOutput(AudioDeviceID output_id);
  OSStatus SetupCallbacks();
  OSStatus SetupStreamFormats();
  void AllocateInputData();

  // Handlers for the AudioUnit callbacks.
  OSStatus HandleInputCallback(AudioUnitRenderActionFlags* io_action_flags,
                               const AudioTimeStamp* time_stamp,
                               UInt32 bus_number,
                               UInt32 number_of_frames,
                               AudioBufferList* io_data);

  OSStatus HandleVarispeedCallback(AudioUnitRenderActionFlags* io_action_flags,
                                   const AudioTimeStamp* time_stamp,
                                   UInt32 bus_number,
                                   UInt32 number_of_frames,
                                   AudioBufferList* io_data);

  OSStatus HandleOutputCallback(AudioUnitRenderActionFlags* io_action_flags,
                                const AudioTimeStamp* time_stamp,
                                UInt32 bus_number,
                                UInt32 number_of_frames,
                                AudioBufferList* io_data);

  // AudioUnit callbacks.
  static OSStatus InputProc(void* user_data,
                            AudioUnitRenderActionFlags* io_action_flags,
                            const AudioTimeStamp* time_stamp,
                            UInt32 bus_number,
                            UInt32 number_of_frames,
                            AudioBufferList* io_data);

  static OSStatus VarispeedProc(void* user_data,
                                AudioUnitRenderActionFlags* io_action_flags,
                                const AudioTimeStamp* time_stamp,
                                UInt32 bus_number,
                                UInt32 number_of_frames,
                                AudioBufferList* io_data);

  static OSStatus OutputProc(void* user_data,
                             AudioUnitRenderActionFlags* io_action_flags,
                             const AudioTimeStamp* time_stamp,
                             UInt32 bus_number,
                             UInt32 number_of_frames,
                             AudioBufferList*  io_data);

  // Our creator.
  AudioManagerMac* manager_;

  // Client parameters.
  AudioParameters params_;

  double input_sample_rate_;
  double output_sample_rate_;

  // Pointer to the object that will provide the audio samples.
  AudioSourceCallback* source_;

  // Values used in Open().
  AudioDeviceID input_id_;
  AudioDeviceID output_id_;

  // The input AudioUnit renders its data here.
  AudioBufferList* input_buffer_list_;

  // Holds the actual data for |input_buffer_list_|.
  scoped_ptr<AudioBus> input_bus_;

  // Used to overlay AudioBufferLists.
  scoped_ptr<AudioBus> wrapper_bus_;

  class AudioDeviceInfo {
   public:
    AudioDeviceInfo()
        : id_(kAudioDeviceUnknown),
          is_input_(false),
          buffer_size_frames_(0) {}
    void Initialize(AudioDeviceID inID, bool isInput);
    bool IsInitialized() const { return id_ != kAudioDeviceUnknown; }

    AudioDeviceID id_;
    bool is_input_;
    UInt32 buffer_size_frames_;
  };

  AudioDeviceInfo input_info_;
  AudioDeviceInfo output_info_;

  // Used for input to output buffering.
  AudioFifo fifo_;

  // The optimal number of frames we'd like to keep in the FIFO at all times.
  int target_fifo_frames_;

  // A running average of the measured delta between actual number of frames
  // in the FIFO versus |target_fifo_frames_|.
  double average_delta_;

  // A varispeed rate scalar which is calculated based on FIFO drift.
  double fifo_rate_compensation_;

  // AudioUnits.
  AudioUnit input_unit_;
  AudioUnit varispeed_unit_;
  AudioUnit output_unit_;

  double first_input_time_;

  bool is_running_;
  int hardware_buffer_size_;
  int channels_;

  // Channel mixer used to transform mono to stereo data. It is only created
  // if the input_hardware_channels is mono.
  scoped_ptr<ChannelMixer> channel_mixer_;
  scoped_ptr<AudioBus> mixer_bus_;

  DISALLOW_COPY_AND_ASSIGN(AudioSynchronizedStream);
};

}  // namespace media

#endif  // MEDIA_AUDIO_MAC_AUDIO_SYNCHRONIZED_MAC_H_