<commit_before>/*
 *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif

#import <AVFoundation/AVFoundation.h>
#import <Foundation/Foundation.h>

#include "webrtc/modules/audio_device/ios/audio_device_ios.h"
#include "webrtc/modules/utility/interface/helpers_ios.h"

#include "webrtc/base/checks.h"
#include "webrtc/base/logging.h"
#include "webrtc/system_wrappers/interface/trace.h"

namespace webrtc {

#define LOGI() LOG(LS_INFO) << "AudioDeviceIOS::"

using ios::CheckAndLogError;

static void ActivateAudioSession(AVAudioSession* session, bool activate) {
  LOG(LS_INFO) << "ActivateAudioSession(" << activate << ")";
  @autoreleasepool {
    NSError* error = nil;
    BOOL success = NO;
    if (!activate) {
      // Deactivate the audio session.
      success = [session setActive:NO error:&error];
      DCHECK(CheckAndLogError(success, error));
      return;
    }
    // Activate an audio session and set category and mode. Only make changes
    // if needed since setting them to the value they already have will clear
    // transient properties (such as PortOverride) that some other component
    // have set up.
    if (session.category != AVAudioSessionCategoryPlayAndRecord) {
      error = nil;
      success = [session setCategory:AVAudioSessionCategoryPlayAndRecord
                               error:&error];
      DCHECK(CheckAndLogError(success, error));
    }
    if (session.mode != AVAudioSessionModeVoiceChat) {
      error = nil;
      success = [session setMode:AVAudioSessionModeVoiceChat error:&error];
      DCHECK(CheckAndLogError(success, error));
    }
    error = nil;
    success = [session setActive:YES error:&error];
    DCHECK(CheckAndLogError(success, error));
    // Ensure that category and mode are actually activated.
    DCHECK(
        [session.category isEqualToString:AVAudioSessionCategoryPlayAndRecord]);
    DCHECK([session.mode isEqualToString:AVAudioSessionModeVoiceChat]);
  }
}

// Query hardware characteristics, such as input and output latency, input and
// output channel count, hardware sample rate, hardware volume setting, and
// whether audio input is available. To obtain meaningful values for hardware
// characteristics,the audio session must be initialized and active before we
// query the values.
// TODO(henrika): Note that these characteristics can change at runtime. For
// instance, input sample rate may change when a user plugs in a headset.
static void GetHardwareAudioParameters(AudioParameters* playout_parameters,
                                       AudioParameters* record_parameters) {
  LOG(LS_INFO) << "GetHardwareAudioParameters";
  @autoreleasepool {
    // Implicit initialization happens when we obtain a reference to the
    // AVAudioSession object.
    AVAudioSession* session = [AVAudioSession sharedInstance];
    // Always get values when the audio session is active.
    ActivateAudioSession(session, true);
    CHECK(session.isInputAvailable) << "No input path is available!";
    // Get current hardware parameters.
    double sample_rate = (double)session.sampleRate;
    double io_buffer_duration = (double)session.IOBufferDuration;
    int output_channels = (int)session.outputNumberOfChannels;
    int input_channels = (int)session.inputNumberOfChannels;
    int frames_per_buffer =
        static_cast<int>(sample_rate * io_buffer_duration + 0.5);
    // Copy hardware parameters to output parameters.
    playout_parameters->reset(sample_rate, output_channels, frames_per_buffer);
    record_parameters->reset(sample_rate, input_channels, frames_per_buffer);
    // Add logging for debugging purposes.
    LOG(LS_INFO) << " sample rate: " << sample_rate;
    LOG(LS_INFO) << " IO buffer duration: " << io_buffer_duration;
    LOG(LS_INFO) << " frames_per_buffer: " << frames_per_buffer;
    LOG(LS_INFO) << " output channels: " << output_channels;
    LOG(LS_INFO) << " input channels: " << input_channels;
    LOG(LS_INFO) << " output latency: " << (double)session.outputLatency;
    LOG(LS_INFO) << " input latency: " << (double)session.inputLatency;
    // Don't keep the audio session active. Instead, deactivate when needed.
    ActivateAudioSession(session, false);
    // TODO(henrika): to be extra safe, we can do more here. E.g., set
    // preferred values for sample rate, channels etc., re-activate an audio
    // session and verify the actual values again. Then we know for sure that
    // the current values will in fact be correct. Or, we can skip all this
    // and check setting when audio is started. Probably better.
  }
}

#if !defined(NDEBUG)
static void LogDeviceInfo() {
  LOG(LS_INFO) << "LogDeviceInfo";
  @autoreleasepool {
    LOG(LS_INFO) << " system name: " << ios::GetSystemName();
    LOG(LS_INFO) << " system version: " << ios::GetSystemVersion();
    LOG(LS_INFO) << " device type: " << ios::GetDeviceType();
    LOG(LS_INFO) << " device name: " << ios::GetDeviceName();
  }
}
#endif

AudioDeviceIOS::AudioDeviceIOS()
    : audio_device_buffer_(nullptr),
      _critSect(*CriticalSectionWrapper::CreateCriticalSection()),
      _auVoiceProcessing(nullptr),
      _audioInterruptionObserver(nullptr),
      _initialized(false),
      _isShutDown(false),
      _recording(false),
      _playing(false),
      _recIsInitialized(false),
      _playIsInitialized(false),
      _adbSampFreq(0),
      _recordingDelay(0),
      _playoutDelay(0),
      _playoutDelayMeasurementCounter(9999),
      _recordingDelayHWAndOS(0),
      _recordingDelayMeasurementCounter(9999),
      _playoutBufferUsed(0),
      _recordingCurrentSeq(0),
      _recordingBufferTotalSize(0) {
  LOGI() << "ctor" << ios::GetCurrentThreadDescription();
  memset(_playoutBuffer, 0, sizeof(_playoutBuffer));
  memset(_recordingBuffer, 0, sizeof(_recordingBuffer));
  memset(_recordingLength, 0, sizeof(_recordingLength));
  memset(_recordingSeqNumber, 0, sizeof(_recordingSeqNumber));
}

AudioDeviceIOS::~AudioDeviceIOS() {
  LOGI() << "~dtor";
  DCHECK(thread_checker_.CalledOnValidThread());
  Terminate();
  delete &_critSect;
}

void AudioDeviceIOS::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) {
  LOGI() << "AttachAudioBuffer";
  DCHECK(audioBuffer);
  DCHECK(thread_checker_.CalledOnValidThread());
  audio_device_buffer_ = audioBuffer;
}

int32_t AudioDeviceIOS::Init() {
  LOGI() << "Init";
  DCHECK(thread_checker_.CalledOnValidThread());
  if (_initialized) {
    return 0;
  }
#if !defined(NDEBUG)
  LogDeviceInfo();
#endif
  // Query hardware audio parameters and cache the results. These parameters
  // will be used as preferred values later when streaming starts.
  // Note that I override these "optimal" value below since I don't want to
  // modify the existing behavior yet.
  GetHardwareAudioParameters(&playout_parameters_, &record_parameters_);
  // TODO(henrika): these parameters are currently hard coded to match the
  // existing implementation where we always use 16kHz as preferred sample
  // rate and mono only. Goal is to improve this scheme and make it more
  // flexible. In addition, a better native buffer size shall be derived.
  // Using 10ms as default here (only used by unit test so far).
  // We should also implemented observers for notification of any change in
  // these parameters.
  playout_parameters_.reset(16000, 1, 160);
  record_parameters_.reset(16000, 1, 160);

  // AttachAudioBuffer() is called at construction by the main class but check
  // just in case.
  DCHECK(audio_device_buffer_) << "AttachAudioBuffer must be called first";
  // Inform the audio device buffer (ADB) about the new audio format.
  // TODO(henrika): try to improve this section.
  audio_device_buffer_->SetPlayoutSampleRate(playout_parameters_.sample_rate());
  audio_device_buffer_->SetPlayoutChannels(playout_parameters_.channels());
  audio_device_buffer_->SetRecordingSampleRate(
      record_parameters_.sample_rate());
  audio_device_buffer_->SetRecordingChannels(record_parameters_.channels());

  DCHECK(!_captureWorkerThread);
  // Create and start the capture thread.
  // TODO(henrika): do we need this thread?
  _isShutDown = false;
  _captureWorkerThread =
      ThreadWrapper::CreateThread(RunCapture, this, "CaptureWorkerThread");
  if (!_captureWorkerThread->Start()) {
    LOG_F(LS_ERROR) << "Failed to start CaptureWorkerThread!";
    return -1;
  }
  _captureWorkerThread->SetPriority(kRealtimePriority);
  _initialized = true;
  return 0;
}

int32_t AudioDeviceIOS::Terminate() {
  LOGI() << "Terminate";
  DCHECK(thread_checker_.CalledOnValidThread());
  if (!_initialized) {
    return 0;
  }
  // Stop the capture thread.
  if (_captureWorkerThread) {
    if (!_captureWorkerThread->Stop()) {
      LOG_F(LS_ERROR) << "Failed to stop CaptureWorkerThread!";
      return -1;
    }
    _captureWorkerThread.reset();
  }
  ShutdownPlayOrRecord();
  _isShutDown = true;
  _initialized = false;
  return 0;
}

int32_t AudioDeviceIOS::InitPlayout() {
  LOGI() << "InitPlayout";
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(_initialized);
  DCHECK(!_playIsInitialized);
  DCHECK(!_playing);
  if (!_recIsInitialized) {
    if (InitPlayOrRecord() == -1) {
      LOG_F(LS_ERROR) << "InitPlayOrRecord failed!";
      return -1;
    }
  }
  _playIsInitialized = true;
  return 0;
}

int32_t AudioDeviceIOS::InitRecording() {
  LOGI() << "InitRecording";
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(_initialized);
  DCHECK(!_recIsInitialized);
  DCHECK(!_recording);
  if (!_playIsInitialized) {
    if (InitPlayOrRecord() == -1) {
      LOG_F(LS_ERROR) << "InitPlayOrRecord failed!";
      return -1;
    }
  }
  _recIsInitialized = true;
  return 0;
}

int32_t AudioDeviceIOS::StartPlayout() {
  LOGI() << "StartPlayout";
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(_playIsInitialized);
  DCHECK(!_playing);

  CriticalSectionScoped lock(&_critSect);

  memset(_playoutBuffer, 0, sizeof(_playoutBuffer));
  _playoutBufferUsed = 0;
  _playoutDelay = 0;
  // Make sure first call to update delay function will update delay
  _playoutDelayMeasurementCounter = 9999;

  if (!_recording) {
    OSStatus result = AudioOutputUnitStart(_auVoiceProcessing);
    if (result != noErr) {
      LOG_F(LS_ERROR) << "AudioOutputUnitStart failed: " << result;
      return -1;
    }
  }
  _playing = true;
  return 0;
}

int32_t AudioDeviceIOS::StopPlayout() {
  LOGI() << "StopPlayout";
  DCHECK(thread_checker_.CalledOnValidThread());
  if (!_playIsInitialized || !_playing) {
    return 0;
  }

  CriticalSectionScoped lock(&_critSect);

  if (!_recording) {
    // Both playout and recording has stopped, shutdown the device.
    ShutdownPlayOrRecord();
  }
  _playIsInitialized = false;
  _playing = false;
  return 0;
}

int32_t AudioDeviceIOS::StartRecording() {
  LOGI() << "StartRecording";
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(_recIsInitialized);
  DCHECK(!_recording);

  CriticalSectionScoped lock(&_critSect);

  memset(_recordingBuffer, 0, sizeof(_recordingBuffer));
  memset(_recordingLength, 0, sizeof(_recordingLength));
  memset(_recordingSeqNumber, 0, sizeof(_recordingSeqNumber));

  _recordingCurrentSeq = 0;
  _recordingBufferTotalSize = 0;
  _recordingDelay = 0;
  _recordingDelayHWAndOS = 0;
  // Make sure first call to update delay function will update delay
  _recordingDelayMeasurementCounter = 9999;

  if (!_playing) {
    OSStatus result = AudioOutputUnitStart(_auVoiceProcessing);
    if (result != noErr) {
      LOG_F(LS_ERROR) << "AudioOutputUnitStart failed: " << result;
      return -1;
    }
  }
  _recording = true;
  return 0;
}

int32_t AudioDeviceIOS::StopRecording() {
  LOGI() << "StopRecording";
  DCHECK(thread_checker_.CalledOnValidThread());
  if (!_recIsInitialized || !_recording) {
    return 0;
  }

  CriticalSectionScoped lock(&_critSect);

  if (!_playing) {
    // Both playout and recording has stopped, shutdown the device.
    ShutdownPlayOrRecord();
  }
  _recIsInitialized = false;
  _recording = false;
  return 0;
}

// Change the default receiver playout route to speaker.
int32_t AudioDeviceIOS::SetLoudspeakerStatus(bool enable) {
  LOGI() << "SetLoudspeakerStatus(" << enable << ")";

  AVAudioSession* session = [AVAudioSession sharedInstance];
  NSString* category = session.category;
  AVAudioSessionCategoryOptions options = session.categoryOptions;
  // Respect old category options if category is
  // AVAudioSessionCategoryPlayAndRecord. Otherwise reset it since old options
  // might not be valid for this category.
  if ([category isEqualToString:AVAudioSessionCategoryPlayAndRecord]) {
    if (enable) {
      options |= AVAudioSessionCategoryOptionDefaultToSpeaker;
    } else {
      options &= ~AVAudioSessionCategoryOptionDefaultToSpeaker;
    }
  } else {
    options = AVAudioSessionCategoryOptionDefaultToSpeaker;
  }
  NSError* error = nil;
  BOOL success = [session setCategory:AVAudioSessionCategoryPlayAndRecord
                          withOptions:options
                                error:&error];
  ios::CheckAndLogError(success, error);
  return (error == nil) ? 0 : -1;
}

int32_t AudioDeviceIOS::GetLoudspeakerStatus(bool& enabled) const {
  LOGI() << "GetLoudspeakerStatus";
  AVAudioSession* session = [AVAudioSession sharedInstance];
  AVAudioSessionCategoryOptions options = session.categoryOptions;
  enabled = options & AVAudioSessionCategoryOptionDefaultToSpeaker;
  return 0;
}

int32_t AudioDeviceIOS::PlayoutDelay(uint16_t& delayMS) const {
  delayMS = _playoutDelay;
  return 0;
}

int32_t AudioDeviceIOS::RecordingDelay(uint16_t& delayMS) const {
  delayMS = _recordingDelay;
  return 0;
}

int32_t AudioDeviceIOS::PlayoutBuffer(AudioDeviceModule::BufferType& type,
                                      uint16_t& sizeMS) const {
  type = AudioDeviceModule::kAdaptiveBufferSize;
  sizeMS = _playoutDelay;
  return 0;
}

int AudioDeviceIOS::GetPlayoutAudioParameters(AudioParameters* params) const {
  CHECK(playout_parameters_.is_valid());
  DCHECK(thread_checker_.CalledOnValidThread());
  *params = playout_parameters_;
  return 0;
}

int AudioDeviceIOS::GetRecordAudioParameters(AudioParameters* params) const {
  CHECK(record_parameters_.is_valid());
  DCHECK(thread_checker_.CalledOnValidThread());
  *params = record_parameters_;
  return 0;
}

// ============================================================================
//                                 Private Methods
// ============================================================================

int32_t AudioDeviceIOS::InitPlayOrRecord() {
  LOGI() << "AudioDeviceIOS::InitPlayOrRecord";
  DCHECK(!_auVoiceProcessing);

  OSStatus result = -1;

  // Create Voice Processing Audio Unit
  AudioComponentDescription desc;
  AudioComponent comp;

  desc.componentType = kAudioUnitType_Output;
  desc.componentSubType = kAudioUnitSubType_VoiceProcessingIO;
  desc.componentManufacturer = kAudioUnitManufacturer_Apple;
  desc.componentFlags = 0;
  desc.componentFlagsMask = 0;

  comp = AudioComponentFindNext(nullptr, &desc);
  if (nullptr == comp) {
    LOG_F(LS_ERROR) << "Could not find audio component for Audio Unit";
    return -1;
  }

  result = AudioComponentInstanceNew(comp, &_auVoiceProcessing);
  if (0 != result) {
    LOG_F(LS_ERROR) << "Failed to create Audio Unit instance: " << result;
    return -1;
  }

  // TODO(henrika): I think we should set the preferred channel configuration
  // in both directions as well to be safe.

  // Set preferred hardware sample rate to 16 kHz.
  // TODO(henrika): improve this selection of sample rate. Why do we currently
  // use a hard coded value? How can we fail and still continue?
  NSError* error = nil;
  AVAudioSession* session = [AVAudioSession sharedInstance];
  Float64 preferredSampleRate(playout_parameters_.sample_rate());
  [session setPreferredSampleRate:preferredSampleRate error:&error];
  if (error != nil) {
    const char* errorString = [[error localizedDescription] UTF8String];
    LOG_F(LS_ERROR) << "setPreferredSampleRate failed: " << errorString;
  }

  // TODO(henrika): we can reduce latency by setting the IOBufferDuration
  // here. Default size for 16kHz is 0.016 sec or 16 msec on an iPhone 6.

  // Activate the audio session.
  ActivateAudioSession(session, true);

  UInt32 enableIO = 1;
  result = AudioUnitSetProperty(_auVoiceProcessing,
                                kAudioOutputUnitProperty_EnableIO,
                                kAudioUnitScope_Input,
                                1,  // input bus
                                &enableIO, sizeof(enableIO));
  if (0 != result) {
    LOG_F(LS_ERROR) << "Failed to enable IO on input: " << result;
  }

  result = AudioUnitSetProperty(_auVoiceProcessing,
                                kAudioOutputUnitProperty_EnableIO,
                                kAudioUnitScope_Output,
                                0,  // output bus
                                &enableIO, sizeof(enableIO));
  if (0 != result) {
    LOG_F(LS_ERROR) << "Failed to enable IO on output: " << result;
  }

  // Disable AU buffer allocation for the recorder, we allocate our own.
  // TODO(henrika): understand this part better.
  UInt32 flag = 0;
  result = AudioUnitSetProperty(_auVoiceProcessing,
                                kAudioUnitProperty_ShouldAllocateBuffer,
                                kAudioUnitScope_Output, 1, &flag, sizeof(flag));
  if (0 != result) {
    LOG_F(LS_WARNING) << "Failed to disable AU buffer allocation: " << result;
    // Should work anyway
  }

  // Set recording callback.
  AURenderCallbackStruct auCbS;
  memset(&auCbS, 0, sizeof(auCbS));
  auCbS.inputProc = RecordProcess;
  auCbS.inputProcRefCon = this;
  result = AudioUnitSetProperty(
      _auVoiceProcessing, kAudioOutputUnitProperty_SetInputCallback,
      kAudioUnitScope_Global, 1, &auCbS, sizeof(auCbS));
  if (0 != result) {
    LOG_F(LS_ERROR) << "Failed to set AU record callback: " << result;
  }

  // Set playout callback.
  memset(&auCbS, 0, sizeof(auCbS));
  auCbS.inputProc = PlayoutProcess;
  auCbS.inputProcRefCon = this;
  result = AudioUnitSetProperty(
      _auVoiceProcessing, kAudioUnitProperty_SetRenderCallback,
      kAudioUnitScope_Global, 0, &auCbS, sizeof(auCbS));
  if (0 != result) {
    LOG_F(LS_ERROR) << "Failed to set AU output callback: " << result;
  }

  // Get stream format for out/0
  AudioStreamBasicDescription playoutDesc;
  UInt32 size = sizeof(playoutDesc);
  result =
      AudioUnitGetProperty(_auVoiceProcessing, kAudioUnitProperty_StreamFormat,
                           kAudioUnitScope_Output, 0, &playoutDesc, &size);
  if (0 != result) {
    LOG_F(LS_ERROR) << "Failed to get AU output stream format: " << result;
  }

  playoutDesc.mSampleRate = preferredSampleRate;
  LOG(LS_INFO) << "Audio Unit playout opened in sampling rate: "
               << playoutDesc.mSampleRate;

  // Store the sampling frequency to use towards the Audio Device Buffer
  // todo: Add 48 kHz (increase buffer sizes). Other fs?
  // TODO(henrika): Figure out if we really need this complex handling.
  if ((playoutDesc.mSampleRate > 44090.0) &&
      (playoutDesc.mSampleRate < 44110.0)) {
    _adbSampFreq = 44100;
  } else if ((playoutDesc.mSampleRate > 15990.0) &&
             (playoutDesc.mSampleRate < 16010.0)) {
    _adbSampFreq = 16000;
  } else if ((playoutDesc.mSampleRate > 7990.0) &&
             (playoutDesc.mSampleRate < 8010.0)) {
    _adbSampFreq = 8000;
  } else {
    _adbSampFreq = 0;
    FATAL() << "Invalid sample rate";
  }

  // Set the audio device buffer sampling rates (use same for play and record).
  // TODO(henrika): this is not a good place to set these things up.
  DCHECK(audio_device_buffer_);
  DCHECK_EQ(_adbSampFreq, playout_parameters_.sample_rate());
  audio_device_buffer_->SetRecordingSampleRate(_adbSampFreq);
  audio_device_buffer_->SetPlayoutSampleRate(_adbSampFreq);

  // Set stream format for out/0.
  playoutDesc.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger |
                             kLinearPCMFormatFlagIsPacked |
                             kLinearPCMFormatFlagIsNonInterleaved;
  playoutDesc.mBytesPerPacket = 2;
  playoutDesc.mFramesPerPacket = 1;
  playoutDesc.mBytesPerFrame = 2;
  playoutDesc.mChannelsPerFrame = 1;
  playoutDesc.mBitsPerChannel = 16;
  result =
      AudioUnitSetProperty(_auVoiceProcessing, kAudioUnitProperty_StreamFormat,
                           kAudioUnitScope_Input, 0, &playoutDesc, size);
  if (0 != result) {
    LOG_F(LS_ERROR) << "Failed to set AU stream format for out/0";
  }

  // Get stream format for in/1.
  AudioStreamBasicDescription recordingDesc;
  size = sizeof(recordingDesc);
  result =
      AudioUnitGetProperty(_auVoiceProcessing, kAudioUnitProperty_StreamFormat,
                           kAudioUnitScope_Input, 1, &recordingDesc, &size);
  if (0 != result) {
    LOG_F(LS_ERROR) << "Failed to get AU stream format for in/1";
  }

  recordingDesc.mSampleRate = preferredSampleRate;
  LOG(LS_INFO) << "Audio Unit recording opened in sampling rate: "
               << recordingDesc.mSampleRate;

  // Set stream format for out/1 (use same sampling frequency as for in/1).
  recordingDesc.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger |
                               kLinearPCMFormatFlagIsPacked |
                               kLinearPCMFormatFlagIsNonInterleaved;
  recordingDesc.mBytesPerPacket = 2;
  recordingDesc.mFramesPerPacket = 1;
  recordingDesc.mBytesPerFrame = 2;
  recordingDesc.mChannelsPerFrame = 1;
  recordingDesc.mBitsPerChannel = 16;
  result =
      AudioUnitSetProperty(_auVoiceProcessing, kAudioUnitProperty_StreamFormat,
                           kAudioUnitScope_Output, 1, &recordingDesc, size);
  if (0 != result) {
    LOG_F(LS_ERROR) << "Failed to set AU stream format for out/1";
  }

  // Initialize here already to be able to get/set stream properties.
  result = AudioUnitInitialize(_auVoiceProcessing);
  if (0 != result) {
    LOG_F(LS_ERROR) << "AudioUnitInitialize failed: " << result;
  }

  // Get hardware sample rate for logging (see if we get what we asked for).
  // TODO(henrika): what if we don't get what we ask for?
  double sampleRate = session.sampleRate;
  LOG(LS_INFO) << "Current HW sample rate is: " << sampleRate
               << ", ADB sample rate is: " << _adbSampFreq;
  LOG(LS_INFO) << "Current HW IO buffer size is: " <<
      [session IOBufferDuration];

  // Listen to audio interruptions.
  // TODO(henrika): learn this area better.
  NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
  id observer = [center
      addObserverForName:AVAudioSessionInterruptionNotification
                  object:nil
                   queue:[NSOperationQueue mainQueue]
              usingBlock:^(NSNotification* notification) {
                NSNumber* typeNumber =
                    [notification userInfo][AVAudioSessionInterruptionTypeKey];
                AVAudioSessionInterruptionType type =
                    (AVAudioSessionInterruptionType)[typeNumber
                                                         unsignedIntegerValue];
                switch (type) {
                  case AVAudioSessionInterruptionTypeBegan:
                    // At this point our audio session has been deactivated and
                    // the
                    // audio unit render callbacks no longer occur. Nothing to
                    // do.
                    break;
                  case AVAudioSessionInterruptionTypeEnded: {
                    NSError* error = nil;
                    AVAudioSession* session = [AVAudioSession sharedInstance];
                    [session setActive:YES error:&error];
                    if (error != nil) {
                      LOG_F(LS_ERROR) << "Failed to active audio session";
                    }
                    // Post interruption the audio unit render callbacks don't
                    // automatically continue, so we restart the unit manually
                    // here.
                    AudioOutputUnitStop(_auVoiceProcessing);
                    AudioOutputUnitStart(_auVoiceProcessing);
                    break;
                  }
                }
              }];
  // Increment refcount on observer using ARC bridge. Instance variable is a
  // void* instead of an id because header is included in other pure C++
  // files.
  _audioInterruptionObserver = (__bridge_retained void*)observer;

  return 0;
}

int32_t AudioDeviceIOS::ShutdownPlayOrRecord() {
  LOGI() << "ShutdownPlayOrRecord";

  if (_audioInterruptionObserver != nullptr) {
    NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
    // Transfer ownership of observer back to ARC, which will dealloc the
    // observer once it exits this scope.
    id observer = (__bridge_transfer id)_audioInterruptionObserver;
    [center removeObserver:observer];
    _audioInterruptionObserver = nullptr;
  }

  // Close and delete AU.
  OSStatus result = -1;
  if (nullptr != _auVoiceProcessing) {
    result = AudioOutputUnitStop(_auVoiceProcessing);
    if (0 != result) {
      LOG_F(LS_ERROR) << "AudioOutputUnitStop failed: " << result;
    }
    result = AudioComponentInstanceDispose(_auVoiceProcessing);
    if (0 != result) {
      LOG_F(LS_ERROR) << "AudioComponentInstanceDispose failed: " << result;
    }
    _auVoiceProcessing = nullptr;
  }

  // All I/O should be stopped or paused prior to deactivating the audio
  // session, hence we deactivate as last action.
  AVAudioSession* session = [AVAudioSession sharedInstance];
  ActivateAudioSession(session, false);
  return 0;
}

// ============================================================================
//                                  Thread Methods
// ============================================================================

OSStatus AudioDeviceIOS::RecordProcess(
    void* inRefCon,
    AudioUnitRenderActionFlags* ioActionFlags,
    const AudioTimeStamp* inTimeStamp,
    UInt32 inBusNumber,
    UInt32 inNumberFrames,
    AudioBufferList* ioData) {
  AudioDeviceIOS* ptrThis = static_cast<AudioDeviceIOS*>(inRefCon);
  return ptrThis->RecordProcessImpl(ioActionFlags, inTimeStamp, inBusNumber,
                                    inNumberFrames);
}

OSStatus AudioDeviceIOS::RecordProcessImpl(
    AudioUnitRenderActionFlags* ioActionFlags,
    const AudioTimeStamp* inTimeStamp,
    uint32_t inBusNumber,
    uint32_t inNumberFrames) {
  // Setup some basic stuff
  // Use temp buffer not to lock up recording buffer more than necessary
  // todo: Make dataTmp a member variable with static size that holds
  //       max possible frames?
  int16_t* dataTmp = new int16_t[inNumberFrames];
  memset(dataTmp, 0, 2 * inNumberFrames);

  AudioBufferList abList;
  abList.mNumberBuffers = 1;
  abList.mBuffers[0].mData = dataTmp;
  abList.mBuffers[0].mDataByteSize = 2 * inNumberFrames;  // 2 bytes/sample
  abList.mBuffers[0].mNumberChannels = 1;

  // Get data from mic
  OSStatus res = AudioUnitRender(_auVoiceProcessing, ioActionFlags, inTimeStamp,
                                 inBusNumber, inNumberFrames, &abList);
  if (res != 0) {
    // TODO(henrika): improve error handling.
    delete[] dataTmp;
    return 0;
  }

  if (_recording) {
    // Insert all data in temp buffer into recording buffers
    // There is zero or one buffer partially full at any given time,
    // all others are full or empty
    // Full means filled with noSamp10ms samples.

    const unsigned int noSamp10ms = _adbSampFreq / 100;
    unsigned int dataPos = 0;
    uint16_t bufPos = 0;
    int16_t insertPos = -1;
    unsigned int nCopy = 0;  // Number of samples to copy

    while (dataPos < inNumberFrames) {
      // Loop over all recording buffers or
      // until we find the partially full buffer
      // First choice is to insert into partially full buffer,
      // second choice is to insert into empty buffer
      bufPos = 0;
      insertPos = -1;
      nCopy = 0;
      while (bufPos < N_REC_BUFFERS) {
        if ((_recordingLength[bufPos] > 0) &&
            (_recordingLength[bufPos] < noSamp10ms)) {
          // Found the partially full buffer
          insertPos = static_cast<int16_t>(bufPos);
          // Don't need to search more, quit loop
          bufPos = N_REC_BUFFERS;
        } else if ((-1 == insertPos) && (0 == _recordingLength[bufPos])) {
          // Found an empty buffer
          insertPos = static_cast<int16_t>(bufPos);
        }
        ++bufPos;
      }

      // Insert data into buffer
      if (insertPos > -1) {
        // We found a non-full buffer, copy data to it
        unsigned int dataToCopy = inNumberFrames - dataPos;
        unsigned int currentRecLen = _recordingLength[insertPos];
        unsigned int roomInBuffer = noSamp10ms - currentRecLen;
        nCopy = (dataToCopy < roomInBuffer ? dataToCopy : roomInBuffer);

        memcpy(&_recordingBuffer[insertPos][currentRecLen], &dataTmp[dataPos],
               nCopy * sizeof(int16_t));
        if (0 == currentRecLen) {
          _recordingSeqNumber[insertPos] = _recordingCurrentSeq;
          ++_recordingCurrentSeq;
        }
        _recordingBufferTotalSize += nCopy;
        // Has to be done last to avoid interrupt problems between threads.
        _recordingLength[insertPos] += nCopy;
        dataPos += nCopy;
      } else {
        // Didn't find a non-full buffer
        // TODO(henrika): improve error handling
        dataPos = inNumberFrames;  // Don't try to insert more
      }
    }
  }
  delete[] dataTmp;
  return 0;
}

OSStatus AudioDeviceIOS::PlayoutProcess(
    void* inRefCon,
    AudioUnitRenderActionFlags* ioActionFlags,
    const AudioTimeStamp* inTimeStamp,
    UInt32 inBusNumber,
    UInt32 inNumberFrames,
    AudioBufferList* ioData) {
  AudioDeviceIOS* ptrThis = static_cast<AudioDeviceIOS*>(inRefCon);
  return ptrThis->PlayoutProcessImpl(inNumberFrames, ioData);
}

OSStatus AudioDeviceIOS::PlayoutProcessImpl(uint32_t inNumberFrames,
                                            AudioBufferList* ioData) {
  int16_t* data = static_cast<int16_t*>(ioData->mBuffers[0].mData);
  unsigned int dataSizeBytes = ioData->mBuffers[0].mDataByteSize;
  unsigned int dataSize = dataSizeBytes / 2;  // Number of samples
  CHECK_EQ(dataSize, inNumberFrames);
  memset(data, 0, dataSizeBytes);  // Start with empty buffer

  // Get playout data from Audio Device Buffer

  if (_playing) {
    unsigned int noSamp10ms = _adbSampFreq / 100;
    // todo: Member variable and allocate when samp freq is determined
    int16_t* dataTmp = new int16_t[noSamp10ms];
    memset(dataTmp, 0, 2 * noSamp10ms);
    unsigned int dataPos = 0;
    int noSamplesOut = 0;
    unsigned int nCopy = 0;

    // First insert data from playout buffer if any
    if (_playoutBufferUsed > 0) {
      nCopy = (dataSize < _playoutBufferUsed) ? dataSize : _playoutBufferUsed;
      DCHECK_EQ(nCopy, _playoutBufferUsed);
      memcpy(data, _playoutBuffer, 2 * nCopy);
      dataPos = nCopy;
      memset(_playoutBuffer, 0, sizeof(_playoutBuffer));
      _playoutBufferUsed = 0;
    }

    // Now get the rest from Audio Device Buffer.
    while (dataPos < dataSize) {
      // Update playout delay
      UpdatePlayoutDelay();

      // Ask for new PCM data to be played out using the AudioDeviceBuffer
      noSamplesOut = audio_device_buffer_->RequestPlayoutData(noSamp10ms);

      // Get data from Audio Device Buffer
      noSamplesOut = audio_device_buffer_->GetPlayoutData(
          reinterpret_cast<int8_t*>(dataTmp));
      CHECK_EQ(noSamp10ms, (unsigned int)noSamplesOut);

      // Insert as much as fits in data buffer
      nCopy =
          (dataSize - dataPos) > noSamp10ms ? noSamp10ms : (dataSize - dataPos);
      memcpy(&data[dataPos], dataTmp, 2 * nCopy);

      // Save rest in playout buffer if any
      if (nCopy < noSamp10ms) {
        memcpy(_playoutBuffer, &dataTmp[nCopy], 2 * (noSamp10ms - nCopy));
        _playoutBufferUsed = noSamp10ms - nCopy;
      }

      // Update loop/index counter, if we copied less than noSamp10ms
      // samples we shall quit loop anyway
      dataPos += noSamp10ms;
    }
    delete[] dataTmp;
  }
  return 0;
}

// TODO(henrika): can either be removed or simplified.
void AudioDeviceIOS::UpdatePlayoutDelay() {
  ++_playoutDelayMeasurementCounter;

  if (_playoutDelayMeasurementCounter >= 100) {
    // Update HW and OS delay every second, unlikely to change

    // Since this is eventually rounded to integral ms, add 0.5ms
    // here to get round-to-nearest-int behavior instead of
    // truncation.
    double totalDelaySeconds = 0.0005;

    // HW output latency
    AVAudioSession* session = [AVAudioSession sharedInstance];
    double latency = session.outputLatency;
    assert(latency >= 0);
    totalDelaySeconds += latency;

    // HW buffer duration
    double ioBufferDuration = session.IOBufferDuration;
    assert(ioBufferDuration >= 0);
    totalDelaySeconds += ioBufferDuration;

    // AU latency
    Float64 f64(0);
    UInt32 size = sizeof(f64);
    OSStatus result =
        AudioUnitGetProperty(_auVoiceProcessing, kAudioUnitProperty_Latency,
                             kAudioUnitScope_Global, 0, &f64, &size);
    if (0 != result) {
      LOG_F(LS_ERROR) << "AU latency error: " << result;
    }
    assert(f64 >= 0);
    totalDelaySeconds += f64;

    // To ms
    _playoutDelay = static_cast<uint32_t>(totalDelaySeconds / 1000);

    // Reset counter
    _playoutDelayMeasurementCounter = 0;
  }

  // todo: Add playout buffer?
}

void AudioDeviceIOS::UpdateRecordingDelay() {
  ++_recordingDelayMeasurementCounter;

  if (_recordingDelayMeasurementCounter >= 100) {
    // Update HW and OS delay every second, unlikely to change

    // Since this is eventually rounded to integral ms, add 0.5ms
    // here to get round-to-nearest-int behavior instead of
    // truncation.
    double totalDelaySeconds = 0.0005;

    // HW input latency
    AVAudioSession* session = [AVAudioSession sharedInstance];
    double latency = session.inputLatency;
    assert(latency >= 0);
    totalDelaySeconds += latency;

    // HW buffer duration
    double ioBufferDuration = session.IOBufferDuration;
    assert(ioBufferDuration >= 0);
    totalDelaySeconds += ioBufferDuration;

    // AU latency
    Float64 f64(0);
    UInt32 size = sizeof(f64);
    OSStatus result =
        AudioUnitGetProperty(_auVoiceProcessing, kAudioUnitProperty_Latency,
                             kAudioUnitScope_Global, 0, &f64, &size);
    if (0 != result) {
      LOG_F(LS_ERROR) << "AU latency error: " << result;
    }
    assert(f64 >= 0);
    totalDelaySeconds += f64;

    // To ms
    _recordingDelayHWAndOS = static_cast<uint32_t>(totalDelaySeconds / 1000);

    // Reset counter
    _recordingDelayMeasurementCounter = 0;
  }

  _recordingDelay = _recordingDelayHWAndOS;

  // ADB recording buffer size, update every time
  // Don't count the one next 10 ms to be sent, then convert samples => ms
  const uint32_t noSamp10ms = _adbSampFreq / 100;
  if (_recordingBufferTotalSize > noSamp10ms) {
    _recordingDelay +=
        (_recordingBufferTotalSize - noSamp10ms) / (_adbSampFreq / 1000);
  }
}

bool AudioDeviceIOS::RunCapture(void* ptrThis) {
  return static_cast<AudioDeviceIOS*>(ptrThis)->CaptureWorkerThread();
}

bool AudioDeviceIOS::CaptureWorkerThread() {
  if (_recording) {
    int bufPos = 0;
    unsigned int lowestSeq = 0;
    int lowestSeqBufPos = 0;
    bool foundBuf = true;
    const unsigned int noSamp10ms = _adbSampFreq / 100;

    while (foundBuf) {
      // Check if we have any buffer with data to insert
      // into the Audio Device Buffer,
      // and find the one with the lowest seq number
      foundBuf = false;
      for (bufPos = 0; bufPos < N_REC_BUFFERS; ++bufPos) {
        if (noSamp10ms == _recordingLength[bufPos]) {
          if (!foundBuf) {
            lowestSeq = _recordingSeqNumber[bufPos];
            lowestSeqBufPos = bufPos;
            foundBuf = true;
          } else if (_recordingSeqNumber[bufPos] < lowestSeq) {
            lowestSeq = _recordingSeqNumber[bufPos];
            lowestSeqBufPos = bufPos;
          }
        }
      }

      // Insert data into the Audio Device Buffer if found any
      if (foundBuf) {
        // Update recording delay
        UpdateRecordingDelay();

        // Set the recorded buffer
        audio_device_buffer_->SetRecordedBuffer(
            reinterpret_cast<int8_t*>(_recordingBuffer[lowestSeqBufPos]),
            _recordingLength[lowestSeqBufPos]);

        // Don't need to set the current mic level in ADB since we only
        // support digital AGC,
        // and besides we cannot get or set the IOS mic level anyway.

        // Set VQE info, use clockdrift == 0
        audio_device_buffer_->SetVQEData(_playoutDelay, _recordingDelay, 0);

        // Deliver recorded samples at specified sample rate, mic level
        // etc. to the observer using callback
        audio_device_buffer_->DeliverRecordedData();

        // Make buffer available
        _recordingSeqNumber[lowestSeqBufPos] = 0;
        _recordingBufferTotalSize -= _recordingLength[lowestSeqBufPos];
        // Must be done last to avoid interrupt problems between threads
        _recordingLength[lowestSeqBufPos] = 0;
      }
    }
  }

  {
    // Normal case
    // Sleep thread (5ms) to let other threads get to work
    // todo: Is 5 ms optimal? Sleep shorter if inserted into the Audio
    //       Device Buffer?
    timespec t;
    t.tv_sec = 0;
    t.tv_nsec = 5 * 1000 * 1000;
    nanosleep(&t, nullptr);
  }
  return true;
}

}  // namespace webrtc
<commit_msg>Fix a bug in computing audio delay on ios device. Converts seconds to milliseconds by multiplying 1000 instead of dividing 1000.<commit_after>/*
 *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif

#import <AVFoundation/AVFoundation.h>
#import <Foundation/Foundation.h>

#include "webrtc/modules/audio_device/ios/audio_device_ios.h"
#include "webrtc/modules/utility/interface/helpers_ios.h"

#include "webrtc/base/checks.h"
#include "webrtc/base/logging.h"
#include "webrtc/system_wrappers/interface/trace.h"

namespace webrtc {

#define LOGI() LOG(LS_INFO) << "AudioDeviceIOS::"

using ios::CheckAndLogError;

static void ActivateAudioSession(AVAudioSession* session, bool activate) {
  LOG(LS_INFO) << "ActivateAudioSession(" << activate << ")";
  @autoreleasepool {
    NSError* error = nil;
    BOOL success = NO;
    if (!activate) {
      // Deactivate the audio session.
      success = [session setActive:NO error:&error];
      DCHECK(CheckAndLogError(success, error));
      return;
    }
    // Activate an audio session and set category and mode. Only make changes
    // if needed since setting them to the value they already have will clear
    // transient properties (such as PortOverride) that some other component
    // have set up.
    if (session.category != AVAudioSessionCategoryPlayAndRecord) {
      error = nil;
      success = [session setCategory:AVAudioSessionCategoryPlayAndRecord
                               error:&error];
      DCHECK(CheckAndLogError(success, error));
    }
    if (session.mode != AVAudioSessionModeVoiceChat) {
      error = nil;
      success = [session setMode:AVAudioSessionModeVoiceChat error:&error];
      DCHECK(CheckAndLogError(success, error));
    }
    error = nil;
    success = [session setActive:YES error:&error];
    DCHECK(CheckAndLogError(success, error));
    // Ensure that category and mode are actually activated.
    DCHECK(
        [session.category isEqualToString:AVAudioSessionCategoryPlayAndRecord]);
    DCHECK([session.mode isEqualToString:AVAudioSessionModeVoiceChat]);
  }
}

// Query hardware characteristics, such as input and output latency, input and
// output channel count, hardware sample rate, hardware volume setting, and
// whether audio input is available. To obtain meaningful values for hardware
// characteristics,the audio session must be initialized and active before we
// query the values.
// TODO(henrika): Note that these characteristics can change at runtime. For
// instance, input sample rate may change when a user plugs in a headset.
static void GetHardwareAudioParameters(AudioParameters* playout_parameters,
                                       AudioParameters* record_parameters) {
  LOG(LS_INFO) << "GetHardwareAudioParameters";
  @autoreleasepool {
    // Implicit initialization happens when we obtain a reference to the
    // AVAudioSession object.
    AVAudioSession* session = [AVAudioSession sharedInstance];
    // Always get values when the audio session is active.
    ActivateAudioSession(session, true);
    CHECK(session.isInputAvailable) << "No input path is available!";
    // Get current hardware parameters.
    double sample_rate = (double)session.sampleRate;
    double io_buffer_duration = (double)session.IOBufferDuration;
    int output_channels = (int)session.outputNumberOfChannels;
    int input_channels = (int)session.inputNumberOfChannels;
    int frames_per_buffer =
        static_cast<int>(sample_rate * io_buffer_duration + 0.5);
    // Copy hardware parameters to output parameters.
    playout_parameters->reset(sample_rate, output_channels, frames_per_buffer);
    record_parameters->reset(sample_rate, input_channels, frames_per_buffer);
    // Add logging for debugging purposes.
    LOG(LS_INFO) << " sample rate: " << sample_rate;
    LOG(LS_INFO) << " IO buffer duration: " << io_buffer_duration;
    LOG(LS_INFO) << " frames_per_buffer: " << frames_per_buffer;
    LOG(LS_INFO) << " output channels: " << output_channels;
    LOG(LS_INFO) << " input channels: " << input_channels;
    LOG(LS_INFO) << " output latency: " << (double)session.outputLatency;
    LOG(LS_INFO) << " input latency: " << (double)session.inputLatency;
    // Don't keep the audio session active. Instead, deactivate when needed.
    ActivateAudioSession(session, false);
    // TODO(henrika): to be extra safe, we can do more here. E.g., set
    // preferred values for sample rate, channels etc., re-activate an audio
    // session and verify the actual values again. Then we know for sure that
    // the current values will in fact be correct. Or, we can skip all this
    // and check setting when audio is started. Probably better.
  }
}

#if !defined(NDEBUG)
static void LogDeviceInfo() {
  LOG(LS_INFO) << "LogDeviceInfo";
  @autoreleasepool {
    LOG(LS_INFO) << " system name: " << ios::GetSystemName();
    LOG(LS_INFO) << " system version: " << ios::GetSystemVersion();
    LOG(LS_INFO) << " device type: " << ios::GetDeviceType();
    LOG(LS_INFO) << " device name: " << ios::GetDeviceName();
  }
}
#endif

AudioDeviceIOS::AudioDeviceIOS()
    : audio_device_buffer_(nullptr),
      _critSect(*CriticalSectionWrapper::CreateCriticalSection()),
      _auVoiceProcessing(nullptr),
      _audioInterruptionObserver(nullptr),
      _initialized(false),
      _isShutDown(false),
      _recording(false),
      _playing(false),
      _recIsInitialized(false),
      _playIsInitialized(false),
      _adbSampFreq(0),
      _recordingDelay(0),
      _playoutDelay(0),
      _playoutDelayMeasurementCounter(9999),
      _recordingDelayHWAndOS(0),
      _recordingDelayMeasurementCounter(9999),
      _playoutBufferUsed(0),
      _recordingCurrentSeq(0),
      _recordingBufferTotalSize(0) {
  LOGI() << "ctor" << ios::GetCurrentThreadDescription();
  memset(_playoutBuffer, 0, sizeof(_playoutBuffer));
  memset(_recordingBuffer, 0, sizeof(_recordingBuffer));
  memset(_recordingLength, 0, sizeof(_recordingLength));
  memset(_recordingSeqNumber, 0, sizeof(_recordingSeqNumber));
}

AudioDeviceIOS::~AudioDeviceIOS() {
  LOGI() << "~dtor";
  DCHECK(thread_checker_.CalledOnValidThread());
  Terminate();
  delete &_critSect;
}

void AudioDeviceIOS::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) {
  LOGI() << "AttachAudioBuffer";
  DCHECK(audioBuffer);
  DCHECK(thread_checker_.CalledOnValidThread());
  audio_device_buffer_ = audioBuffer;
}

int32_t AudioDeviceIOS::Init() {
  LOGI() << "Init";
  DCHECK(thread_checker_.CalledOnValidThread());
  if (_initialized) {
    return 0;
  }
#if !defined(NDEBUG)
  LogDeviceInfo();
#endif
  // Query hardware audio parameters and cache the results. These parameters
  // will be used as preferred values later when streaming starts.
  // Note that I override these "optimal" value below since I don't want to
  // modify the existing behavior yet.
  GetHardwareAudioParameters(&playout_parameters_, &record_parameters_);
  // TODO(henrika): these parameters are currently hard coded to match the
  // existing implementation where we always use 16kHz as preferred sample
  // rate and mono only. Goal is to improve this scheme and make it more
  // flexible. In addition, a better native buffer size shall be derived.
  // Using 10ms as default here (only used by unit test so far).
  // We should also implemented observers for notification of any change in
  // these parameters.
  playout_parameters_.reset(16000, 1, 160);
  record_parameters_.reset(16000, 1, 160);

  // AttachAudioBuffer() is called at construction by the main class but check
  // just in case.
  DCHECK(audio_device_buffer_) << "AttachAudioBuffer must be called first";
  // Inform the audio device buffer (ADB) about the new audio format.
  // TODO(henrika): try to improve this section.
  audio_device_buffer_->SetPlayoutSampleRate(playout_parameters_.sample_rate());
  audio_device_buffer_->SetPlayoutChannels(playout_parameters_.channels());
  audio_device_buffer_->SetRecordingSampleRate(
      record_parameters_.sample_rate());
  audio_device_buffer_->SetRecordingChannels(record_parameters_.channels());

  DCHECK(!_captureWorkerThread);
  // Create and start the capture thread.
  // TODO(henrika): do we need this thread?
  _isShutDown = false;
  _captureWorkerThread =
      ThreadWrapper::CreateThread(RunCapture, this, "CaptureWorkerThread");
  if (!_captureWorkerThread->Start()) {
    LOG_F(LS_ERROR) << "Failed to start CaptureWorkerThread!";
    return -1;
  }
  _captureWorkerThread->SetPriority(kRealtimePriority);
  _initialized = true;
  return 0;
}

int32_t AudioDeviceIOS::Terminate() {
  LOGI() << "Terminate";
  DCHECK(thread_checker_.CalledOnValidThread());
  if (!_initialized) {
    return 0;
  }
  // Stop the capture thread.
  if (_captureWorkerThread) {
    if (!_captureWorkerThread->Stop()) {
      LOG_F(LS_ERROR) << "Failed to stop CaptureWorkerThread!";
      return -1;
    }
    _captureWorkerThread.reset();
  }
  ShutdownPlayOrRecord();
  _isShutDown = true;
  _initialized = false;
  return 0;
}

int32_t AudioDeviceIOS::InitPlayout() {
  LOGI() << "InitPlayout";
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(_initialized);
  DCHECK(!_playIsInitialized);
  DCHECK(!_playing);
  if (!_recIsInitialized) {
    if (InitPlayOrRecord() == -1) {
      LOG_F(LS_ERROR) << "InitPlayOrRecord failed!";
      return -1;
    }
  }
  _playIsInitialized = true;
  return 0;
}

int32_t AudioDeviceIOS::InitRecording() {
  LOGI() << "InitRecording";
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(_initialized);
  DCHECK(!_recIsInitialized);
  DCHECK(!_recording);
  if (!_playIsInitialized) {
    if (InitPlayOrRecord() == -1) {
      LOG_F(LS_ERROR) << "InitPlayOrRecord failed!";
      return -1;
    }
  }
  _recIsInitialized = true;
  return 0;
}

int32_t AudioDeviceIOS::StartPlayout() {
  LOGI() << "StartPlayout";
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(_playIsInitialized);
  DCHECK(!_playing);

  CriticalSectionScoped lock(&_critSect);

  memset(_playoutBuffer, 0, sizeof(_playoutBuffer));
  _playoutBufferUsed = 0;
  _playoutDelay = 0;
  // Make sure first call to update delay function will update delay
  _playoutDelayMeasurementCounter = 9999;

  if (!_recording) {
    OSStatus result = AudioOutputUnitStart(_auVoiceProcessing);
    if (result != noErr) {
      LOG_F(LS_ERROR) << "AudioOutputUnitStart failed: " << result;
      return -1;
    }
  }
  _playing = true;
  return 0;
}

int32_t AudioDeviceIOS::StopPlayout() {
  LOGI() << "StopPlayout";
  DCHECK(thread_checker_.CalledOnValidThread());
  if (!_playIsInitialized || !_playing) {
    return 0;
  }

  CriticalSectionScoped lock(&_critSect);

  if (!_recording) {
    // Both playout and recording has stopped, shutdown the device.
    ShutdownPlayOrRecord();
  }
  _playIsInitialized = false;
  _playing = false;
  return 0;
}

int32_t AudioDeviceIOS::StartRecording() {
  LOGI() << "StartRecording";
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(_recIsInitialized);
  DCHECK(!_recording);

  CriticalSectionScoped lock(&_critSect);

  memset(_recordingBuffer, 0, sizeof(_recordingBuffer));
  memset(_recordingLength, 0, sizeof(_recordingLength));
  memset(_recordingSeqNumber, 0, sizeof(_recordingSeqNumber));

  _recordingCurrentSeq = 0;
  _recordingBufferTotalSize = 0;
  _recordingDelay = 0;
  _recordingDelayHWAndOS = 0;
  // Make sure first call to update delay function will update delay
  _recordingDelayMeasurementCounter = 9999;

  if (!_playing) {
    OSStatus result = AudioOutputUnitStart(_auVoiceProcessing);
    if (result != noErr) {
      LOG_F(LS_ERROR) << "AudioOutputUnitStart failed: " << result;
      return -1;
    }
  }
  _recording = true;
  return 0;
}

int32_t AudioDeviceIOS::StopRecording() {
  LOGI() << "StopRecording";
  DCHECK(thread_checker_.CalledOnValidThread());
  if (!_recIsInitialized || !_recording) {
    return 0;
  }

  CriticalSectionScoped lock(&_critSect);

  if (!_playing) {
    // Both playout and recording has stopped, shutdown the device.
    ShutdownPlayOrRecord();
  }
  _recIsInitialized = false;
  _recording = false;
  return 0;
}

// Change the default receiver playout route to speaker.
int32_t AudioDeviceIOS::SetLoudspeakerStatus(bool enable) {
  LOGI() << "SetLoudspeakerStatus(" << enable << ")";

  AVAudioSession* session = [AVAudioSession sharedInstance];
  NSString* category = session.category;
  AVAudioSessionCategoryOptions options = session.categoryOptions;
  // Respect old category options if category is
  // AVAudioSessionCategoryPlayAndRecord. Otherwise reset it since old options
  // might not be valid for this category.
  if ([category isEqualToString:AVAudioSessionCategoryPlayAndRecord]) {
    if (enable) {
      options |= AVAudioSessionCategoryOptionDefaultToSpeaker;
    } else {
      options &= ~AVAudioSessionCategoryOptionDefaultToSpeaker;
    }
  } else {
    options = AVAudioSessionCategoryOptionDefaultToSpeaker;
  }
  NSError* error = nil;
  BOOL success = [session setCategory:AVAudioSessionCategoryPlayAndRecord
                          withOptions:options
                                error:&error];
  ios::CheckAndLogError(success, error);
  return (error == nil) ? 0 : -1;
}

int32_t AudioDeviceIOS::GetLoudspeakerStatus(bool& enabled) const {
  LOGI() << "GetLoudspeakerStatus";
  AVAudioSession* session = [AVAudioSession sharedInstance];
  AVAudioSessionCategoryOptions options = session.categoryOptions;
  enabled = options & AVAudioSessionCategoryOptionDefaultToSpeaker;
  return 0;
}

int32_t AudioDeviceIOS::PlayoutDelay(uint16_t& delayMS) const {
  delayMS = _playoutDelay;
  return 0;
}

int32_t AudioDeviceIOS::RecordingDelay(uint16_t& delayMS) const {
  delayMS = _recordingDelay;
  return 0;
}

int32_t AudioDeviceIOS::PlayoutBuffer(AudioDeviceModule::BufferType& type,
                                      uint16_t& sizeMS) const {
  type = AudioDeviceModule::kAdaptiveBufferSize;
  sizeMS = _playoutDelay;
  return 0;
}

int AudioDeviceIOS::GetPlayoutAudioParameters(AudioParameters* params) const {
  CHECK(playout_parameters_.is_valid());
  DCHECK(thread_checker_.CalledOnValidThread());
  *params = playout_parameters_;
  return 0;
}

int AudioDeviceIOS::GetRecordAudioParameters(AudioParameters* params) const {
  CHECK(record_parameters_.is_valid());
  DCHECK(thread_checker_.CalledOnValidThread());
  *params = record_parameters_;
  return 0;
}

// ============================================================================
//                                 Private Methods
// ============================================================================

int32_t AudioDeviceIOS::InitPlayOrRecord() {
  LOGI() << "AudioDeviceIOS::InitPlayOrRecord";
  DCHECK(!_auVoiceProcessing);

  OSStatus result = -1;

  // Create Voice Processing Audio Unit
  AudioComponentDescription desc;
  AudioComponent comp;

  desc.componentType = kAudioUnitType_Output;
  desc.componentSubType = kAudioUnitSubType_VoiceProcessingIO;
  desc.componentManufacturer = kAudioUnitManufacturer_Apple;
  desc.componentFlags = 0;
  desc.componentFlagsMask = 0;

  comp = AudioComponentFindNext(nullptr, &desc);
  if (nullptr == comp) {
    LOG_F(LS_ERROR) << "Could not find audio component for Audio Unit";
    return -1;
  }

  result = AudioComponentInstanceNew(comp, &_auVoiceProcessing);
  if (0 != result) {
    LOG_F(LS_ERROR) << "Failed to create Audio Unit instance: " << result;
    return -1;
  }

  // TODO(henrika): I think we should set the preferred channel configuration
  // in both directions as well to be safe.

  // Set preferred hardware sample rate to 16 kHz.
  // TODO(henrika): improve this selection of sample rate. Why do we currently
  // use a hard coded value? How can we fail and still continue?
  NSError* error = nil;
  AVAudioSession* session = [AVAudioSession sharedInstance];
  Float64 preferredSampleRate(playout_parameters_.sample_rate());
  [session setPreferredSampleRate:preferredSampleRate error:&error];
  if (error != nil) {
    const char* errorString = [[error localizedDescription] UTF8String];
    LOG_F(LS_ERROR) << "setPreferredSampleRate failed: " << errorString;
  }

  // TODO(henrika): we can reduce latency by setting the IOBufferDuration
  // here. Default size for 16kHz is 0.016 sec or 16 msec on an iPhone 6.

  // Activate the audio session.
  ActivateAudioSession(session, true);

  UInt32 enableIO = 1;
  result = AudioUnitSetProperty(_auVoiceProcessing,
                                kAudioOutputUnitProperty_EnableIO,
                                kAudioUnitScope_Input,
                                1,  // input bus
                                &enableIO, sizeof(enableIO));
  if (0 != result) {
    LOG_F(LS_ERROR) << "Failed to enable IO on input: " << result;
  }

  result = AudioUnitSetProperty(_auVoiceProcessing,
                                kAudioOutputUnitProperty_EnableIO,
                                kAudioUnitScope_Output,
                                0,  // output bus
                                &enableIO, sizeof(enableIO));
  if (0 != result) {
    LOG_F(LS_ERROR) << "Failed to enable IO on output: " << result;
  }

  // Disable AU buffer allocation for the recorder, we allocate our own.
  // TODO(henrika): understand this part better.
  UInt32 flag = 0;
  result = AudioUnitSetProperty(_auVoiceProcessing,
                                kAudioUnitProperty_ShouldAllocateBuffer,
                                kAudioUnitScope_Output, 1, &flag, sizeof(flag));
  if (0 != result) {
    LOG_F(LS_WARNING) << "Failed to disable AU buffer allocation: " << result;
    // Should work anyway
  }

  // Set recording callback.
  AURenderCallbackStruct auCbS;
  memset(&auCbS, 0, sizeof(auCbS));
  auCbS.inputProc = RecordProcess;
  auCbS.inputProcRefCon = this;
  result = AudioUnitSetProperty(
      _auVoiceProcessing, kAudioOutputUnitProperty_SetInputCallback,
      kAudioUnitScope_Global, 1, &auCbS, sizeof(auCbS));
  if (0 != result) {
    LOG_F(LS_ERROR) << "Failed to set AU record callback: " << result;
  }

  // Set playout callback.
  memset(&auCbS, 0, sizeof(auCbS));
  auCbS.inputProc = PlayoutProcess;
  auCbS.inputProcRefCon = this;
  result = AudioUnitSetProperty(
      _auVoiceProcessing, kAudioUnitProperty_SetRenderCallback,
      kAudioUnitScope_Global, 0, &auCbS, sizeof(auCbS));
  if (0 != result) {
    LOG_F(LS_ERROR) << "Failed to set AU output callback: " << result;
  }

  // Get stream format for out/0
  AudioStreamBasicDescription playoutDesc;
  UInt32 size = sizeof(playoutDesc);
  result =
      AudioUnitGetProperty(_auVoiceProcessing, kAudioUnitProperty_StreamFormat,
                           kAudioUnitScope_Output, 0, &playoutDesc, &size);
  if (0 != result) {
    LOG_F(LS_ERROR) << "Failed to get AU output stream format: " << result;
  }

  playoutDesc.mSampleRate = preferredSampleRate;
  LOG(LS_INFO) << "Audio Unit playout opened in sampling rate: "
               << playoutDesc.mSampleRate;

  // Store the sampling frequency to use towards the Audio Device Buffer
  // todo: Add 48 kHz (increase buffer sizes). Other fs?
  // TODO(henrika): Figure out if we really need this complex handling.
  if ((playoutDesc.mSampleRate > 44090.0) &&
      (playoutDesc.mSampleRate < 44110.0)) {
    _adbSampFreq = 44100;
  } else if ((playoutDesc.mSampleRate > 15990.0) &&
             (playoutDesc.mSampleRate < 16010.0)) {
    _adbSampFreq = 16000;
  } else if ((playoutDesc.mSampleRate > 7990.0) &&
             (playoutDesc.mSampleRate < 8010.0)) {
    _adbSampFreq = 8000;
  } else {
    _adbSampFreq = 0;
    FATAL() << "Invalid sample rate";
  }

  // Set the audio device buffer sampling rates (use same for play and record).
  // TODO(henrika): this is not a good place to set these things up.
  DCHECK(audio_device_buffer_);
  DCHECK_EQ(_adbSampFreq, playout_parameters_.sample_rate());
  audio_device_buffer_->SetRecordingSampleRate(_adbSampFreq);
  audio_device_buffer_->SetPlayoutSampleRate(_adbSampFreq);

  // Set stream format for out/0.
  playoutDesc.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger |
                             kLinearPCMFormatFlagIsPacked |
                             kLinearPCMFormatFlagIsNonInterleaved;
  playoutDesc.mBytesPerPacket = 2;
  playoutDesc.mFramesPerPacket = 1;
  playoutDesc.mBytesPerFrame = 2;
  playoutDesc.mChannelsPerFrame = 1;
  playoutDesc.mBitsPerChannel = 16;
  result =
      AudioUnitSetProperty(_auVoiceProcessing, kAudioUnitProperty_StreamFormat,
                           kAudioUnitScope_Input, 0, &playoutDesc, size);
  if (0 != result) {
    LOG_F(LS_ERROR) << "Failed to set AU stream format for out/0";
  }

  // Get stream format for in/1.
  AudioStreamBasicDescription recordingDesc;
  size = sizeof(recordingDesc);
  result =
      AudioUnitGetProperty(_auVoiceProcessing, kAudioUnitProperty_StreamFormat,
                           kAudioUnitScope_Input, 1, &recordingDesc, &size);
  if (0 != result) {
    LOG_F(LS_ERROR) << "Failed to get AU stream format for in/1";
  }

  recordingDesc.mSampleRate = preferredSampleRate;
  LOG(LS_INFO) << "Audio Unit recording opened in sampling rate: "
               << recordingDesc.mSampleRate;

  // Set stream format for out/1 (use same sampling frequency as for in/1).
  recordingDesc.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger |
                               kLinearPCMFormatFlagIsPacked |
                               kLinearPCMFormatFlagIsNonInterleaved;
  recordingDesc.mBytesPerPacket = 2;
  recordingDesc.mFramesPerPacket = 1;
  recordingDesc.mBytesPerFrame = 2;
  recordingDesc.mChannelsPerFrame = 1;
  recordingDesc.mBitsPerChannel = 16;
  result =
      AudioUnitSetProperty(_auVoiceProcessing, kAudioUnitProperty_StreamFormat,
                           kAudioUnitScope_Output, 1, &recordingDesc, size);
  if (0 != result) {
    LOG_F(LS_ERROR) << "Failed to set AU stream format for out/1";
  }

  // Initialize here already to be able to get/set stream properties.
  result = AudioUnitInitialize(_auVoiceProcessing);
  if (0 != result) {
    LOG_F(LS_ERROR) << "AudioUnitInitialize failed: " << result;
  }

  // Get hardware sample rate for logging (see if we get what we asked for).
  // TODO(henrika): what if we don't get what we ask for?
  double sampleRate = session.sampleRate;
  LOG(LS_INFO) << "Current HW sample rate is: " << sampleRate
               << ", ADB sample rate is: " << _adbSampFreq;
  LOG(LS_INFO) << "Current HW IO buffer size is: " <<
      [session IOBufferDuration];

  // Listen to audio interruptions.
  // TODO(henrika): learn this area better.
  NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
  id observer = [center
      addObserverForName:AVAudioSessionInterruptionNotification
                  object:nil
                   queue:[NSOperationQueue mainQueue]
              usingBlock:^(NSNotification* notification) {
                NSNumber* typeNumber =
                    [notification userInfo][AVAudioSessionInterruptionTypeKey];
                AVAudioSessionInterruptionType type =
                    (AVAudioSessionInterruptionType)[typeNumber
                                                         unsignedIntegerValue];
                switch (type) {
                  case AVAudioSessionInterruptionTypeBegan:
                    // At this point our audio session has been deactivated and
                    // the
                    // audio unit render callbacks no longer occur. Nothing to
                    // do.
                    break;
                  case AVAudioSessionInterruptionTypeEnded: {
                    NSError* error = nil;
                    AVAudioSession* session = [AVAudioSession sharedInstance];
                    [session setActive:YES error:&error];
                    if (error != nil) {
                      LOG_F(LS_ERROR) << "Failed to active audio session";
                    }
                    // Post interruption the audio unit render callbacks don't
                    // automatically continue, so we restart the unit manually
                    // here.
                    AudioOutputUnitStop(_auVoiceProcessing);
                    AudioOutputUnitStart(_auVoiceProcessing);
                    break;
                  }
                }
              }];
  // Increment refcount on observer using ARC bridge. Instance variable is a
  // void* instead of an id because header is included in other pure C++
  // files.
  _audioInterruptionObserver = (__bridge_retained void*)observer;

  return 0;
}

int32_t AudioDeviceIOS::ShutdownPlayOrRecord() {
  LOGI() << "ShutdownPlayOrRecord";

  if (_audioInterruptionObserver != nullptr) {
    NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
    // Transfer ownership of observer back to ARC, which will dealloc the
    // observer once it exits this scope.
    id observer = (__bridge_transfer id)_audioInterruptionObserver;
    [center removeObserver:observer];
    _audioInterruptionObserver = nullptr;
  }

  // Close and delete AU.
  OSStatus result = -1;
  if (nullptr != _auVoiceProcessing) {
    result = AudioOutputUnitStop(_auVoiceProcessing);
    if (0 != result) {
      LOG_F(LS_ERROR) << "AudioOutputUnitStop failed: " << result;
    }
    result = AudioComponentInstanceDispose(_auVoiceProcessing);
    if (0 != result) {
      LOG_F(LS_ERROR) << "AudioComponentInstanceDispose failed: " << result;
    }
    _auVoiceProcessing = nullptr;
  }

  // All I/O should be stopped or paused prior to deactivating the audio
  // session, hence we deactivate as last action.
  AVAudioSession* session = [AVAudioSession sharedInstance];
  ActivateAudioSession(session, false);
  return 0;
}

// ============================================================================
//                                  Thread Methods
// ============================================================================

OSStatus AudioDeviceIOS::RecordProcess(
    void* inRefCon,
    AudioUnitRenderActionFlags* ioActionFlags,
    const AudioTimeStamp* inTimeStamp,
    UInt32 inBusNumber,
    UInt32 inNumberFrames,
    AudioBufferList* ioData) {
  AudioDeviceIOS* ptrThis = static_cast<AudioDeviceIOS*>(inRefCon);
  return ptrThis->RecordProcessImpl(ioActionFlags, inTimeStamp, inBusNumber,
                                    inNumberFrames);
}

OSStatus AudioDeviceIOS::RecordProcessImpl(
    AudioUnitRenderActionFlags* ioActionFlags,
    const AudioTimeStamp* inTimeStamp,
    uint32_t inBusNumber,
    uint32_t inNumberFrames) {
  // Setup some basic stuff
  // Use temp buffer not to lock up recording buffer more than necessary
  // todo: Make dataTmp a member variable with static size that holds
  //       max possible frames?
  int16_t* dataTmp = new int16_t[inNumberFrames];
  memset(dataTmp, 0, 2 * inNumberFrames);

  AudioBufferList abList;
  abList.mNumberBuffers = 1;
  abList.mBuffers[0].mData = dataTmp;
  abList.mBuffers[0].mDataByteSize = 2 * inNumberFrames;  // 2 bytes/sample
  abList.mBuffers[0].mNumberChannels = 1;

  // Get data from mic
  OSStatus res = AudioUnitRender(_auVoiceProcessing, ioActionFlags, inTimeStamp,
                                 inBusNumber, inNumberFrames, &abList);
  if (res != 0) {
    // TODO(henrika): improve error handling.
    delete[] dataTmp;
    return 0;
  }

  if (_recording) {
    // Insert all data in temp buffer into recording buffers
    // There is zero or one buffer partially full at any given time,
    // all others are full or empty
    // Full means filled with noSamp10ms samples.

    const unsigned int noSamp10ms = _adbSampFreq / 100;
    unsigned int dataPos = 0;
    uint16_t bufPos = 0;
    int16_t insertPos = -1;
    unsigned int nCopy = 0;  // Number of samples to copy

    while (dataPos < inNumberFrames) {
      // Loop over all recording buffers or
      // until we find the partially full buffer
      // First choice is to insert into partially full buffer,
      // second choice is to insert into empty buffer
      bufPos = 0;
      insertPos = -1;
      nCopy = 0;
      while (bufPos < N_REC_BUFFERS) {
        if ((_recordingLength[bufPos] > 0) &&
            (_recordingLength[bufPos] < noSamp10ms)) {
          // Found the partially full buffer
          insertPos = static_cast<int16_t>(bufPos);
          // Don't need to search more, quit loop
          bufPos = N_REC_BUFFERS;
        } else if ((-1 == insertPos) && (0 == _recordingLength[bufPos])) {
          // Found an empty buffer
          insertPos = static_cast<int16_t>(bufPos);
        }
        ++bufPos;
      }

      // Insert data into buffer
      if (insertPos > -1) {
        // We found a non-full buffer, copy data to it
        unsigned int dataToCopy = inNumberFrames - dataPos;
        unsigned int currentRecLen = _recordingLength[insertPos];
        unsigned int roomInBuffer = noSamp10ms - currentRecLen;
        nCopy = (dataToCopy < roomInBuffer ? dataToCopy : roomInBuffer);

        memcpy(&_recordingBuffer[insertPos][currentRecLen], &dataTmp[dataPos],
               nCopy * sizeof(int16_t));
        if (0 == currentRecLen) {
          _recordingSeqNumber[insertPos] = _recordingCurrentSeq;
          ++_recordingCurrentSeq;
        }
        _recordingBufferTotalSize += nCopy;
        // Has to be done last to avoid interrupt problems between threads.
        _recordingLength[insertPos] += nCopy;
        dataPos += nCopy;
      } else {
        // Didn't find a non-full buffer
        // TODO(henrika): improve error handling
        dataPos = inNumberFrames;  // Don't try to insert more
      }
    }
  }
  delete[] dataTmp;
  return 0;
}

OSStatus AudioDeviceIOS::PlayoutProcess(
    void* inRefCon,
    AudioUnitRenderActionFlags* ioActionFlags,
    const AudioTimeStamp* inTimeStamp,
    UInt32 inBusNumber,
    UInt32 inNumberFrames,
    AudioBufferList* ioData) {
  AudioDeviceIOS* ptrThis = static_cast<AudioDeviceIOS*>(inRefCon);
  return ptrThis->PlayoutProcessImpl(inNumberFrames, ioData);
}

OSStatus AudioDeviceIOS::PlayoutProcessImpl(uint32_t inNumberFrames,
                                            AudioBufferList* ioData) {
  int16_t* data = static_cast<int16_t*>(ioData->mBuffers[0].mData);
  unsigned int dataSizeBytes = ioData->mBuffers[0].mDataByteSize;
  unsigned int dataSize = dataSizeBytes / 2;  // Number of samples
  CHECK_EQ(dataSize, inNumberFrames);
  memset(data, 0, dataSizeBytes);  // Start with empty buffer

  // Get playout data from Audio Device Buffer

  if (_playing) {
    unsigned int noSamp10ms = _adbSampFreq / 100;
    // todo: Member variable and allocate when samp freq is determined
    int16_t* dataTmp = new int16_t[noSamp10ms];
    memset(dataTmp, 0, 2 * noSamp10ms);
    unsigned int dataPos = 0;
    int noSamplesOut = 0;
    unsigned int nCopy = 0;

    // First insert data from playout buffer if any
    if (_playoutBufferUsed > 0) {
      nCopy = (dataSize < _playoutBufferUsed) ? dataSize : _playoutBufferUsed;
      DCHECK_EQ(nCopy, _playoutBufferUsed);
      memcpy(data, _playoutBuffer, 2 * nCopy);
      dataPos = nCopy;
      memset(_playoutBuffer, 0, sizeof(_playoutBuffer));
      _playoutBufferUsed = 0;
    }

    // Now get the rest from Audio Device Buffer.
    while (dataPos < dataSize) {
      // Update playout delay
      UpdatePlayoutDelay();

      // Ask for new PCM data to be played out using the AudioDeviceBuffer
      noSamplesOut = audio_device_buffer_->RequestPlayoutData(noSamp10ms);

      // Get data from Audio Device Buffer
      noSamplesOut = audio_device_buffer_->GetPlayoutData(
          reinterpret_cast<int8_t*>(dataTmp));
      CHECK_EQ(noSamp10ms, (unsigned int)noSamplesOut);

      // Insert as much as fits in data buffer
      nCopy =
          (dataSize - dataPos) > noSamp10ms ? noSamp10ms : (dataSize - dataPos);
      memcpy(&data[dataPos], dataTmp, 2 * nCopy);

      // Save rest in playout buffer if any
      if (nCopy < noSamp10ms) {
        memcpy(_playoutBuffer, &dataTmp[nCopy], 2 * (noSamp10ms - nCopy));
        _playoutBufferUsed = noSamp10ms - nCopy;
      }

      // Update loop/index counter, if we copied less than noSamp10ms
      // samples we shall quit loop anyway
      dataPos += noSamp10ms;
    }
    delete[] dataTmp;
  }
  return 0;
}

// TODO(henrika): can either be removed or simplified.
void AudioDeviceIOS::UpdatePlayoutDelay() {
  ++_playoutDelayMeasurementCounter;

  if (_playoutDelayMeasurementCounter >= 100) {
    // Update HW and OS delay every second, unlikely to change

    // Since this is eventually rounded to integral ms, add 0.5ms
    // here to get round-to-nearest-int behavior instead of
    // truncation.
    double totalDelaySeconds = 0.0005;

    // HW output latency
    AVAudioSession* session = [AVAudioSession sharedInstance];
    double latency = session.outputLatency;
    assert(latency >= 0);
    totalDelaySeconds += latency;

    // HW buffer duration
    double ioBufferDuration = session.IOBufferDuration;
    assert(ioBufferDuration >= 0);
    totalDelaySeconds += ioBufferDuration;

    // AU latency
    Float64 f64(0);
    UInt32 size = sizeof(f64);
    OSStatus result =
        AudioUnitGetProperty(_auVoiceProcessing, kAudioUnitProperty_Latency,
                             kAudioUnitScope_Global, 0, &f64, &size);
    if (0 != result) {
      LOG_F(LS_ERROR) << "AU latency error: " << result;
    }
    assert(f64 >= 0);
    totalDelaySeconds += f64;

    // To ms
    _playoutDelay = static_cast<uint32_t>(totalDelaySeconds * 1000);

    // Reset counter
    _playoutDelayMeasurementCounter = 0;
  }

  // todo: Add playout buffer?
}

void AudioDeviceIOS::UpdateRecordingDelay() {
  ++_recordingDelayMeasurementCounter;

  if (_recordingDelayMeasurementCounter >= 100) {
    // Update HW and OS delay every second, unlikely to change

    // Since this is eventually rounded to integral ms, add 0.5ms
    // here to get round-to-nearest-int behavior instead of
    // truncation.
    double totalDelaySeconds = 0.0005;

    // HW input latency
    AVAudioSession* session = [AVAudioSession sharedInstance];
    double latency = session.inputLatency;
    assert(latency >= 0);
    totalDelaySeconds += latency;

    // HW buffer duration
    double ioBufferDuration = session.IOBufferDuration;
    assert(ioBufferDuration >= 0);
    totalDelaySeconds += ioBufferDuration;

    // AU latency
    Float64 f64(0);
    UInt32 size = sizeof(f64);
    OSStatus result =
        AudioUnitGetProperty(_auVoiceProcessing, kAudioUnitProperty_Latency,
                             kAudioUnitScope_Global, 0, &f64, &size);
    if (0 != result) {
      LOG_F(LS_ERROR) << "AU latency error: " << result;
    }
    assert(f64 >= 0);
    totalDelaySeconds += f64;

    // To ms
    _recordingDelayHWAndOS = static_cast<uint32_t>(totalDelaySeconds / 1000);

    // Reset counter
    _recordingDelayMeasurementCounter = 0;
  }

  _recordingDelay = _recordingDelayHWAndOS;

  // ADB recording buffer size, update every time
  // Don't count the one next 10 ms to be sent, then convert samples => ms
  const uint32_t noSamp10ms = _adbSampFreq / 100;
  if (_recordingBufferTotalSize > noSamp10ms) {
    _recordingDelay +=
        (_recordingBufferTotalSize - noSamp10ms) / (_adbSampFreq / 1000);
  }
}

bool AudioDeviceIOS::RunCapture(void* ptrThis) {
  return static_cast<AudioDeviceIOS*>(ptrThis)->CaptureWorkerThread();
}

bool AudioDeviceIOS::CaptureWorkerThread() {
  if (_recording) {
    int bufPos = 0;
    unsigned int lowestSeq = 0;
    int lowestSeqBufPos = 0;
    bool foundBuf = true;
    const unsigned int noSamp10ms = _adbSampFreq / 100;

    while (foundBuf) {
      // Check if we have any buffer with data to insert
      // into the Audio Device Buffer,
      // and find the one with the lowest seq number
      foundBuf = false;
      for (bufPos = 0; bufPos < N_REC_BUFFERS; ++bufPos) {
        if (noSamp10ms == _recordingLength[bufPos]) {
          if (!foundBuf) {
            lowestSeq = _recordingSeqNumber[bufPos];
            lowestSeqBufPos = bufPos;
            foundBuf = true;
          } else if (_recordingSeqNumber[bufPos] < lowestSeq) {
            lowestSeq = _recordingSeqNumber[bufPos];
            lowestSeqBufPos = bufPos;
          }
        }
      }

      // Insert data into the Audio Device Buffer if found any
      if (foundBuf) {
        // Update recording delay
        UpdateRecordingDelay();

        // Set the recorded buffer
        audio_device_buffer_->SetRecordedBuffer(
            reinterpret_cast<int8_t*>(_recordingBuffer[lowestSeqBufPos]),
            _recordingLength[lowestSeqBufPos]);

        // Don't need to set the current mic level in ADB since we only
        // support digital AGC,
        // and besides we cannot get or set the IOS mic level anyway.

        // Set VQE info, use clockdrift == 0
        audio_device_buffer_->SetVQEData(_playoutDelay, _recordingDelay, 0);

        // Deliver recorded samples at specified sample rate, mic level
        // etc. to the observer using callback
        audio_device_buffer_->DeliverRecordedData();

        // Make buffer available
        _recordingSeqNumber[lowestSeqBufPos] = 0;
        _recordingBufferTotalSize -= _recordingLength[lowestSeqBufPos];
        // Must be done last to avoid interrupt problems between threads
        _recordingLength[lowestSeqBufPos] = 0;
      }
    }
  }

  {
    // Normal case
    // Sleep thread (5ms) to let other threads get to work
    // todo: Is 5 ms optimal? Sleep shorter if inserted into the Audio
    //       Device Buffer?
    timespec t;
    t.tv_sec = 0;
    t.tv_nsec = 5 * 1000 * 1000;
    nanosleep(&t, nullptr);
  }
  return true;
}

}  // namespace webrtc

{'fraction_non_alphanumeric': 0.1017749616277697, 'fraction_numerical': 0.012908811838325002, 'mean_word_length': 3.165919772652749, 'pattern_counts': {'":': 0, '<': 445, '<?xml version=': 0, '>': 105, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 24, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5991315', 'n_tokens_mistral': 23147, 'n_tokens_neox': 22059, 'n_words': 5962}