// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Implementation of AudioInputStream for Mac OS X using the special AUHAL
// input Audio Unit present in OS 10.4 and later.
// The AUHAL input Audio Unit is for low-latency audio I/O.
//
// Overview of operation:
//
// - An object of AUAudioInputStream is created by the AudioManager
//   factory: audio_man->MakeAudioInputStream().
// - Next some thread will call Open(), at that point the underlying
//   AUHAL output Audio Unit is created and configured.
// - Then some thread will call Start(sink).
//   Then the Audio Unit is started which creates its own thread which
//   periodically will provide the sink with more data as buffers are being
//   produced/recorded.
// - At some point some thread will call Stop(), which we handle by directly
//   stopping the AUHAL output Audio Unit.
// - The same thread that called stop will call Close() where we cleanup
//   and notify the audio manager, which likely will destroy this object.
//
// Implementation notes:
//
// - It is recommended to first acquire the native sample rate of the default
//   input device and then use the same rate when creating this object.
// - Calling Close() also leads to self destruction.
// - The latency consists of two parts:
//   1) Hardware latency, which includes Audio Unit latency, audio device
//      latency;
//   2) The delay between the actual recording instant and the time when the
//      data packet is provided as a callback.
//
#ifndef MEDIA_AUDIO_APPLE_AUDIO_LOW_LATENCY_INPUT_H_
#define MEDIA_AUDIO_APPLE_AUDIO_LOW_LATENCY_INPUT_H_

#include <AudioUnit/AudioUnit.h>

#include <atomic>
#include <memory>
#include <vector>

#include "base/cancelable_callback.h"
#include "base/memory/raw_ptr.h"
#include "base/synchronization/lock.h"
#include "base/threading/thread_checker.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "media/audio/agc_audio_stream.h"
#include "media/audio/apple/glitch_helper.h"
#include "media/audio/audio_io.h"
#include "media/base/amplitude_peak_detector.h"
#include "media/base/audio_block_fifo.h"
#include "media/base/audio_glitch_info.h"
#include "media/base/audio_parameters.h"
#include "media/base/sample_format.h"

#if BUILDFLAG(IS_MAC)
#include "media/audio/mac/audio_manager_mac.h"
#else
#include "media/audio/ios/audio_manager_ios.h"
#endif

namespace media {
class AudioManagerApple;
class AUCallbackProxy;

class MEDIA_EXPORT AUAudioInputStream
    : public AgcAudioStream<AudioInputStream> {
 public:
  using Error = AudioInputStream::AudioInputCallback::Error;

  // The ctor takes all the usual parameters, plus |manager| which is the
  // the audio manager who is creating this object.
  AUAudioInputStream(AudioManagerApple* manager,
                     const AudioParameters& input_params,
                     AudioDeviceID audio_device_id,
                     const AudioManager::LogCallback& log_callback);

  AUAudioInputStream(const AUAudioInputStream&) = delete;
  AUAudioInputStream& operator=(const AUAudioInputStream&) = delete;

  // The dtor is typically called by the AudioManager only and it is usually
  // triggered by calling AudioInputStream::Close().
  ~AUAudioInputStream() override;

  // Implementation of AudioInputStream.
  AudioInputStream::OpenOutcome Open() override;
  void Start(AudioInputCallback* callback) override;
  void Stop() override;
  void Close() override;
  double GetMaxVolume() override;
  void SetVolume(double volume) override;
  double GetVolume() override;
  bool IsMuted() override;
  void SetOutputDeviceForAec(const std::string& output_device_id) override;

  // Returns true if the audio unit is active/running.
  // The result is based on the kAudioOutputUnitProperty_IsRunning property
  // which exists for output units.
  bool IsRunning();

  AudioDeviceID device_id() const { return input_device_id_; }
  size_t requested_buffer_size() const {
    return input_params_.frames_per_buffer();
  }
  AudioUnit audio_unit() const { return audio_unit_; }

  static bool IsEchoCancellationSupported(AudioDeviceID audio_device_id,
                                          const AudioParameters& params);

  // Fan out the data from the first half of audio_buffer into interleaved
  // stereo across the whole of audio_buffer. Public for testing only.
  static void UpmixMonoToStereoInPlace(AudioBuffer* audio_buffer,
                                       int bytes_per_sample);

  // Called by `data_callback_proxy_` on the real-time priority I/O thread from
  // the audio unit.
  OSStatus OnDataIsAvailable(AudioUnitRenderActionFlags* flags,
                             const AudioTimeStamp* time_stamp,
                             UInt32 bus_number,
                             UInt32 number_of_frames);

 private:
  bool OpenAUHAL();
  bool OpenVoiceProcessingAU();
  void SetSystemAGC(bool enable);

  // Callback functions called on a real-time priority I/O thread from the audio
  // unit. These methods are called when recorded audio is available.
  static OSStatus DataIsAvailable(void* context,
                                  AudioUnitRenderActionFlags* flags,
                                  const AudioTimeStamp* time_stamp,
                                  UInt32 bus_number,
                                  UInt32 number_of_frames,
                                  AudioBufferList* io_data);

  // Pushes recorded data to consumer of the input audio stream.
  OSStatus Provide(UInt32 number_of_frames,
                   AudioBufferList* io_data,
                   const AudioTimeStamp* time_stamp);

  // Attempts to set the audio format to Float32. If rejected by the OS, falls
  // back to SignedInt16. Returns the OSStatus of the AudioUnitSetProperty
  // attempt.
  OSStatus ConfigureFormat();

  // Attempts to set the audio format to Float32 for VoiceProcessing streams. If
  // rejected by the OS, fall back to SignedInt16. Returns the OSStatus of the
  // AudioUnitSetProperty attempt.
  OSStatus ConfigureFormatForVoiceProcessing();

  // Returns a copy of `source_format` configured for 16-bit signed integer
  // (S16) samples. Used as a fallback when F32 is not supported.
  AudioStreamBasicDescription GetFallbackFormat(
      const AudioStreamBasicDescription& source_format);

  // Sets the stream format property on the Audio Unit.
  OSStatus SetInputStreamFormat(const AudioStreamBasicDescription& format,
                                SampleFormat uma_format);

  // Gets the current capture time.
  base::TimeTicks GetCaptureTime(const AudioTimeStamp* input_time_stamp);

  // Issues the OnError() callback to the |sink_|.
  void HandleError(OSStatus err,
                   const char* message,
                   const base::Location& location = FROM_HERE);
  void HandleErrorAndNotify_Locked(Error error_code,
                                   OSStatus err,
                                   const char* message,
                                   const base::Location& location = FROM_HERE)
      EXCLUSIVE_LOCKS_REQUIRED(lock_);

  // Helper methods to set and get atomic |input_callback_is_active_|.
  void SetInputCallbackIsActive(bool active);
  bool GetInputCallbackIsActive();

  // Checks if a stream was started successfully and the audio unit also starts
  // to call InputProc() as it should. This method is called once when a timer
  // expires some time after calling Start().
  void CheckInputStartupSuccess();

  // Uninitializes the audio unit if needed.
  void CloseAudioUnit();

  // Reinitializes the AudioUnit to use a new output device.
  void ReinitializeVoiceProcessingAudioUnit();

  // Adds extra UMA stats when it has been detected that startup failed.
  void AddHistogramsForFailedStartup();

  // Called from the dtor and when the stream is reset.
  void ReportAndResetStats();

  // Logs a message both to the log callback and to the console.
  void SendLog(const std::string& message, OSStatus result = noErr);

  // Verifies that Open(), Start(), Stop() and Close() are all called on the
  // creating thread which is the main browser thread (CrBrowserMain) on Mac.
  THREAD_CHECKER(thread_checker_);

  // Our creator, the audio manager needs to be notified when we close.
  const raw_ptr<AudioManagerApple> manager_;

  // The audio parameters requested when creating the stream.
  const AudioParameters input_params_;

  // Stores the number of frames that we actually get callbacks for.
  // This may be different from what we ask for, so we use this for stats in
  // order to understand how often this happens and what are the typical values.
  size_t number_of_frames_provided_ GUARDED_BY(lock_) = 0;

  // Pointer to the object that will receive the recorded audio samples.
  raw_ptr<AudioInputCallback> sink_ GUARDED_BY(lock_) = nullptr;

  // Structure that holds the desired output format of the stream.
  // Note that, this format can differ from the device(=input) format.
  AudioStreamBasicDescription format_;

  // The special Audio Unit called AUHAL, which allows us to pass audio data
  // directly from a microphone, through the HAL, and to our application.
  // The AUHAL also enables selection of non default devices.
  AudioUnit audio_unit_{0};

  // The UID refers to the current input audio device.
  const AudioDeviceID input_device_id_;

  // Provides a mechanism for encapsulating one or more buffers of audio data.
  AudioBufferList audio_buffer_list_;

  // SampleFormat chosen for audio input. Could be downgraded to S16 if F32 is
  // not supported.
  SampleFormat sample_format_ = kSampleFormatF32;

  // Temporary storage for recorded data. The InputProc() renders into this
  // array as soon as a frame of the desired buffer size has been recorded.
  std::unique_ptr<uint8_t[]> audio_data_buffer_;

  // Fixed capture hardware latency.
  base::TimeDelta hardware_latency_;

  // FIFO used to accumulates recorded data.
  media::AudioBlockFifo fifo_ GUARDED_BY(lock_);

  // Used to defer Start() to workaround http://crbug.com/160920.
  base::CancelableOnceClosure deferred_start_cb_;

  // Contains time of last successful call to AudioUnitRender().
  // Initialized first time in Start() and then updated for each valid
  // audio buffer. Used to detect long error sequences and to take actions
  // if length of error sequence is above a certain limit.
  base::TimeTicks last_success_time_;

  // Flags to indicate if we have gotten an input callback.
  // |got_input_callback_| is only accessed on the OS audio thread in
  // OnDataIsAvailable() and is set to true when the first callback comes. It
  // acts as a gate to only set |input_callback_is_active_| atomically once.
  // |got_input_callback_| is reset to false in Stop() on the main thread. This
  // is safe since after stopping the audio unit there is no current callback
  // ongoing and no further callbacks coming.
  bool got_input_callback_ = false;
  std::atomic<bool> input_callback_is_active_ = false;

  // Timer which triggers CheckInputStartupSuccess() to verify that input
  // callbacks have started as intended after a successful call to Start().
  // This timer lives on the main browser thread.
  std::unique_ptr<base::OneShotTimer> input_callback_timer_;

  // Set to true when we've successfully called SuppressNoiseReduction to
  // disable ambient noise reduction.
  bool noise_reduction_suppressed_ = false;

  // Controls whether or not we use the kAudioUnitSubType_VoiceProcessingIO
  // voice processing component that provides echo cancellation, ducking
  // and gain control on Sierra and later.
  bool use_voice_processing_ = false;

  // The of the output device to cancel echo from.
  AudioDeviceID output_device_id_for_aec_ = kAudioObjectUnknown;

  // Used to detect and report glitches.
  GlitchHelper glitch_helper_ GUARDED_BY(lock_);

  AmplitudePeakDetector peak_detector_;

  // Callback to send statistics info.
  AudioManager::LogCallback log_callback_;

  // Guards members accessed on the helper / audio thread.
  base::Lock lock_;

  // Set to true if stopping the AudioUnit fails. Used to leak
  // `data_callback_proxy_`.
  bool stop_failed_ = false;

  // Proxy to intercept callbacks and allow safe leak on teardown failure.
  std::unique_ptr<AUCallbackProxy> data_callback_proxy_;
};

}  // namespace media

#endif  // MEDIA_AUDIO_APPLE_AUDIO_LOW_LATENCY_INPUT_H_
