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