Chromium Code Reviews| Index: webrtc/modules/audio_device/ios/audio_device_ios.mm |
| diff --git a/webrtc/modules/audio_device/ios/audio_device_ios.mm b/webrtc/modules/audio_device/ios/audio_device_ios.mm |
| index 47503a96d70546ef07b822627f6ee69548bdc379..ecb56946f9e2d41151ea06993cda8498ca956f51 100644 |
| --- a/webrtc/modules/audio_device/ios/audio_device_ios.mm |
| +++ b/webrtc/modules/audio_device/ios/audio_device_ios.mm |
| @@ -8,1905 +8,1029 @@ |
| * 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 { |
| -AudioDeviceIOS::AudioDeviceIOS(const int32_t id) |
| - : |
| - _ptrAudioBuffer(NULL), |
| - _critSect(*CriticalSectionWrapper::CreateCriticalSection()), |
| - _id(id), |
| - _auVoiceProcessing(NULL), |
| - _audioInterruptionObserver(NULL), |
| - _initialized(false), |
| - _isShutDown(false), |
| - _recording(false), |
| - _playing(false), |
| - _recIsInitialized(false), |
| - _playIsInitialized(false), |
| - _recordingDeviceIsSpecified(false), |
| - _playoutDeviceIsSpecified(false), |
| - _micIsInitialized(false), |
| - _speakerIsInitialized(false), |
| - _AGC(false), |
| - _adbSampFreq(0), |
| - _recordingDelay(0), |
| - _playoutDelay(0), |
| - _playoutDelayMeasurementCounter(9999), |
| - _recordingDelayHWAndOS(0), |
| - _recordingDelayMeasurementCounter(9999), |
| - _playWarning(0), |
| - _playError(0), |
| - _recWarning(0), |
| - _recError(0), |
| - _playoutBufferUsed(0), |
| - _recordingCurrentSeq(0), |
| - _recordingBufferTotalSize(0) { |
| - WEBRTC_TRACE(kTraceMemory, kTraceAudioDevice, id, |
| - "%s created", __FUNCTION__); |
| - |
| - memset(_playoutBuffer, 0, sizeof(_playoutBuffer)); |
| - memset(_recordingBuffer, 0, sizeof(_recordingBuffer)); |
| - memset(_recordingLength, 0, sizeof(_recordingLength)); |
| - memset(_recordingSeqNumber, 0, sizeof(_recordingSeqNumber)); |
| -} |
| -AudioDeviceIOS::~AudioDeviceIOS() { |
| - WEBRTC_TRACE(kTraceMemory, kTraceAudioDevice, _id, |
| - "%s destroyed", __FUNCTION__); |
| +#define LOGI() LOG(LS_INFO) << "AudioDeviceIOS::" |
| - Terminate(); |
| +using ios::CheckAndLogError; |
| - delete &_critSect; |
| +static void ActivateAudioSession(AVAudioSession* session, bool activate) { |
| + LOG(LS_INFO) << "ActivateAudioSession(" << activate << ")"; |
| + @autoreleasepool { |
| + NSError* errorObject = nil; |
|
tkchin_webrtc
2015/07/08 19:41:14
nit: usually just
NSError* error = nil;
henrika_webrtc
2015/07/09 12:58:01
Done.
|
| + BOOL success = NO; |
| + if (!activate) { |
| + // Deactivate the audio session. |
| + success = [session setActive:NO error:&errorObject]; |
| + DCHECK(CheckAndLogError(success, errorObject)); |
| + 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) { |
| + success = [session setCategory:AVAudioSessionCategoryPlayAndRecord |
| + error:&errorObject]; |
|
tkchin_webrtc
2015/07/08 19:41:14
nit: I think it is good practice to explicitly set
henrika_webrtc
2015/07/09 12:58:02
In fact, I tried CheckAndLogError(BOOL success, NS
|
| + DCHECK(CheckAndLogError(success, errorObject)); |
| + } |
| + if (session.mode != AVAudioSessionModeVoiceChat) { |
| + success = |
| + [session setMode:AVAudioSessionModeVoiceChat error:&errorObject]; |
| + DCHECK(CheckAndLogError(success, errorObject)); |
| + } |
| + success = [session setActive:YES error:&errorObject]; |
| + DCHECK(CheckAndLogError(success, errorObject)); |
| + // 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. |
| + } |
| +} |
| + |
| +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)); |
| } |
| - |
| -// ============================================================================ |
| -// API |
| -// ============================================================================ |
| - |
| -void AudioDeviceIOS::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "%s", __FUNCTION__); |
| - |
| - CriticalSectionScoped lock(&_critSect); |
| - |
| - _ptrAudioBuffer = audioBuffer; |
| - |
| - // inform the AudioBuffer about default settings for this implementation |
| - _ptrAudioBuffer->SetRecordingSampleRate(ENGINE_REC_BUF_SIZE_IN_SAMPLES); |
| - _ptrAudioBuffer->SetPlayoutSampleRate(ENGINE_PLAY_BUF_SIZE_IN_SAMPLES); |
| - _ptrAudioBuffer->SetRecordingChannels(N_REC_CHANNELS); |
| - _ptrAudioBuffer->SetPlayoutChannels(N_PLAY_CHANNELS); |
| +AudioDeviceIOS::~AudioDeviceIOS() { |
| + LOGI() << "~dtor"; |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + Terminate(); |
| + delete &_critSect; |
| } |
| -int32_t AudioDeviceIOS::ActiveAudioLayer( |
| - AudioDeviceModule::AudioLayer& audioLayer) const { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "%s", __FUNCTION__); |
| - audioLayer = AudioDeviceModule::kPlatformDefaultAudio; |
| - return 0; |
| +void AudioDeviceIOS::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) { |
| + LOGI() << "AttachAudioBuffer"; |
| + DCHECK(audioBuffer); |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + audio_device_buffer_ = audioBuffer; |
| } |
| int32_t AudioDeviceIOS::Init() { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "%s", __FUNCTION__); |
| - |
| - CriticalSectionScoped lock(&_critSect); |
| - |
| - if (_initialized) { |
| - return 0; |
| - } |
| - |
| - _isShutDown = false; |
| - |
| - // Create and start capture thread |
| - if (!_captureWorkerThread) { |
| - _captureWorkerThread = ThreadWrapper::CreateThread( |
| - RunCapture, this, "CaptureWorkerThread"); |
| - bool res = _captureWorkerThread->Start(); |
| - WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, |
| - _id, "CaptureWorkerThread started (res=%d)", res); |
| - _captureWorkerThread->SetPriority(kRealtimePriority); |
| - } else { |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, |
| - _id, "Thread already created"); |
| - } |
| - _playWarning = 0; |
| - _playError = 0; |
| - _recWarning = 0; |
| - _recError = 0; |
| - |
| - _initialized = true; |
| - |
| + LOGI() << "Init"; |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + if (_initialized) { |
| return 0; |
| + } |
| + // 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() { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "%s", __FUNCTION__); |
| - |
| - if (!_initialized) { |
| - return 0; |
| - } |
| - |
| - |
| - // Stop capture thread |
| - if (_captureWorkerThread) { |
| - WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, |
| - _id, "Stopping CaptureWorkerThread"); |
| - bool res = _captureWorkerThread->Stop(); |
| - WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, |
| - _id, "CaptureWorkerThread stopped (res=%d)", res); |
| - _captureWorkerThread.reset(); |
| - } |
| - |
| - // Shut down Audio Unit |
| - ShutdownPlayOrRecord(); |
| - |
| - _isShutDown = true; |
| - _initialized = false; |
| - _speakerIsInitialized = false; |
| - _micIsInitialized = false; |
| - _playoutDeviceIsSpecified = false; |
| - _recordingDeviceIsSpecified = false; |
| + LOGI() << "Terminate"; |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + if (!_initialized) { |
| return 0; |
| -} |
| - |
| -bool AudioDeviceIOS::Initialized() const { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "%s", __FUNCTION__); |
| - return (_initialized); |
| -} |
| - |
| -int32_t AudioDeviceIOS::InitSpeaker() { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "%s", __FUNCTION__); |
| - |
| - CriticalSectionScoped lock(&_critSect); |
| - |
| - if (!_initialized) { |
| - WEBRTC_TRACE(kTraceError, kTraceAudioDevice, |
| - _id, " Not initialized"); |
| - return -1; |
| - } |
| - |
| - if (_playing) { |
| - WEBRTC_TRACE(kTraceError, kTraceAudioDevice, |
| - _id, " Cannot init speaker when playing"); |
| - return -1; |
| - } |
| - |
| - if (!_playoutDeviceIsSpecified) { |
| - WEBRTC_TRACE(kTraceError, kTraceAudioDevice, |
| - _id, " Playout device is not specified"); |
| - return -1; |
| + } |
| + // Stop the capture thread. |
| + if (_captureWorkerThread) { |
| + if (!_captureWorkerThread->Stop()) { |
| + LOG_F(LS_ERROR) << "Failed to stop CaptureWorkerThread!"; |
| + return -1; |
| } |
| - |
| - // Do nothing |
| - _speakerIsInitialized = true; |
| - |
| - return 0; |
| + _captureWorkerThread.reset(); |
| + } |
| + ShutdownPlayOrRecord(); |
| + _isShutDown = true; |
| + _initialized = false; |
| + return 0; |
| } |
| -int32_t AudioDeviceIOS::InitMicrophone() { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "%s", __FUNCTION__); |
| - |
| - CriticalSectionScoped lock(&_critSect); |
| - |
| - if (!_initialized) { |
| - WEBRTC_TRACE(kTraceError, kTraceAudioDevice, |
| - _id, " Not initialized"); |
| - return -1; |
| - } |
| - |
| - if (_recording) { |
| - WEBRTC_TRACE(kTraceError, kTraceAudioDevice, |
| - _id, " Cannot init mic when recording"); |
| - return -1; |
| - } |
| - |
| - if (!_recordingDeviceIsSpecified) { |
| - WEBRTC_TRACE(kTraceError, kTraceAudioDevice, |
| - _id, " Recording device is not specified"); |
| - return -1; |
| +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; |
| } |
| - |
| - // Do nothing |
| - |
| - _micIsInitialized = true; |
| - |
| - return 0; |
| -} |
| - |
| -bool AudioDeviceIOS::SpeakerIsInitialized() const { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "%s", __FUNCTION__); |
| - return _speakerIsInitialized; |
| -} |
| - |
| -bool AudioDeviceIOS::MicrophoneIsInitialized() const { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "%s", __FUNCTION__); |
| - return _micIsInitialized; |
| -} |
| - |
| -int32_t AudioDeviceIOS::SpeakerVolumeIsAvailable(bool& available) { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "%s", __FUNCTION__); |
| - |
| - available = false; // Speaker volume not supported on iOS |
| - |
| - return 0; |
| -} |
| - |
| -int32_t AudioDeviceIOS::SetSpeakerVolume(uint32_t volume) { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "AudioDeviceIOS::SetSpeakerVolume(volume=%u)", volume); |
| - |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " API call not supported on this platform"); |
| - return -1; |
| -} |
| - |
| -int32_t AudioDeviceIOS::SpeakerVolume(uint32_t& volume) const { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "%s", __FUNCTION__); |
| - |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " API call not supported on this platform"); |
| - return -1; |
| -} |
| - |
| -int32_t |
| - AudioDeviceIOS::SetWaveOutVolume(uint16_t volumeLeft, |
| - uint16_t volumeRight) { |
| - WEBRTC_TRACE( |
| - kTraceModuleCall, |
| - kTraceAudioDevice, |
| - _id, |
| - "AudioDeviceIOS::SetWaveOutVolume(volumeLeft=%u, volumeRight=%u)", |
| - volumeLeft, volumeRight); |
| - |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " API call not supported on this platform"); |
| - |
| - return -1; |
| -} |
| - |
| -int32_t |
| -AudioDeviceIOS::WaveOutVolume(uint16_t& /*volumeLeft*/, |
| - uint16_t& /*volumeRight*/) const { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "%s", __FUNCTION__); |
| - |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " API call not supported on this platform"); |
| - return -1; |
| -} |
| - |
| -int32_t |
| - AudioDeviceIOS::MaxSpeakerVolume(uint32_t& maxVolume) const { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "%s", __FUNCTION__); |
| - |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " API call not supported on this platform"); |
| - return -1; |
| -} |
| - |
| -int32_t AudioDeviceIOS::MinSpeakerVolume( |
| - uint32_t& minVolume) const { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "%s", __FUNCTION__); |
| - |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " API call not supported on this platform"); |
| - return -1; |
| -} |
| - |
| -int32_t |
| - AudioDeviceIOS::SpeakerVolumeStepSize(uint16_t& stepSize) const { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "%s", __FUNCTION__); |
| - |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " API call not supported on this platform"); |
| - return -1; |
| -} |
| - |
| -int32_t AudioDeviceIOS::SpeakerMuteIsAvailable(bool& available) { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "%s", __FUNCTION__); |
| - |
| - available = false; // Speaker mute not supported on iOS |
| - |
| - return 0; |
| -} |
| - |
| -int32_t AudioDeviceIOS::SetSpeakerMute(bool enable) { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "%s", __FUNCTION__); |
| - |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " API call not supported on this platform"); |
| - return -1; |
| -} |
| - |
| -int32_t AudioDeviceIOS::SpeakerMute(bool& enabled) const { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "%s", __FUNCTION__); |
| - |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " API call not supported on this platform"); |
| - return -1; |
| -} |
| - |
| -int32_t AudioDeviceIOS::MicrophoneMuteIsAvailable(bool& available) { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "%s", __FUNCTION__); |
| - |
| - available = false; // Mic mute not supported on iOS |
| - |
| - return 0; |
| -} |
| - |
| -int32_t AudioDeviceIOS::SetMicrophoneMute(bool enable) { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "%s", __FUNCTION__); |
| - |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " API call not supported on this platform"); |
| - return -1; |
| -} |
| - |
| -int32_t AudioDeviceIOS::MicrophoneMute(bool& enabled) const { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "%s", __FUNCTION__); |
| - |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " API call not supported on this platform"); |
| - return -1; |
| + } |
| + _playIsInitialized = true; |
| + return 0; |
| } |
| -int32_t AudioDeviceIOS::MicrophoneBoostIsAvailable(bool& available) { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "%s", __FUNCTION__); |
| - |
| - available = false; // Mic boost not supported on iOS |
| - |
| - return 0; |
| -} |
| - |
| -int32_t AudioDeviceIOS::SetMicrophoneBoost(bool enable) { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "AudioDeviceIOS::SetMicrophoneBoost(enable=%u)", enable); |
| - |
| - if (!_micIsInitialized) { |
| - WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| - " Microphone not initialized"); |
| - return -1; |
| - } |
| - |
| - if (enable) { |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " SetMicrophoneBoost cannot be enabled on this platform"); |
| - return -1; |
| +int32_t AudioDeviceIOS::InitRecording() { |
| + LOGI() << "InitPlayout"; |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + DCHECK(_initialized); |
| + DCHECK(!_recIsInitialized); |
| + DCHECK(!_recording); |
| + if (!_playIsInitialized) { |
| + if (InitPlayOrRecord() == -1) { |
| + LOG_F(LS_ERROR) << "InitPlayOrRecord failed!"; |
| + return -1; |
| } |
| - |
| - return 0; |
| + } |
| + _recIsInitialized = true; |
| + return 0; |
| } |
| -int32_t AudioDeviceIOS::MicrophoneBoost(bool& enabled) const { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "%s", __FUNCTION__); |
| - if (!_micIsInitialized) { |
| - WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| - " Microphone not initialized"); |
| - return -1; |
| +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; |
| } |
| - |
| - enabled = false; |
| - |
| - return 0; |
| + } |
| + _playing = true; |
| + return 0; |
| } |
| -int32_t AudioDeviceIOS::StereoRecordingIsAvailable(bool& available) { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "%s", __FUNCTION__); |
| +int32_t AudioDeviceIOS::StopPlayout() { |
| + LOGI() << "StopPlayout"; |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + if (!_playIsInitialized || !_playing) { |
| + return 0; |
| + } |
| - available = false; // Stereo recording not supported on iOS |
| + CriticalSectionScoped lock(&_critSect); |
| - return 0; |
| + if (!_recording) { |
| + // Both playout and recording has stopped, shutdown the device. |
| + ShutdownPlayOrRecord(); |
| + } |
| + _playIsInitialized = false; |
| + _playing = false; |
| + return 0; |
| } |
| -int32_t AudioDeviceIOS::SetStereoRecording(bool enable) { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "AudioDeviceIOS::SetStereoRecording(enable=%u)", enable); |
| - |
| - if (enable) { |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " Stereo recording is not supported on this platform"); |
| - return -1; |
| +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; |
| } |
| - return 0; |
| + } |
| + _recording = true; |
| + return 0; |
| } |
| -int32_t AudioDeviceIOS::StereoRecording(bool& enabled) const { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "%s", __FUNCTION__); |
| - |
| - enabled = false; |
| +int32_t AudioDeviceIOS::StopRecording() { |
| + LOGI() << "StopRecording"; |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + if (!_recIsInitialized || !_recording) { |
| return 0; |
| -} |
| + } |
| -int32_t AudioDeviceIOS::StereoPlayoutIsAvailable(bool& available) { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "%s", __FUNCTION__); |
| + CriticalSectionScoped lock(&_critSect); |
| - available = false; // Stereo playout not supported on iOS |
| - |
| - return 0; |
| + if (!_playing) { |
| + // Both playout and recording has stopped, shutdown the device. |
| + ShutdownPlayOrRecord(); |
| + } |
| + _recIsInitialized = false; |
| + _recording = false; |
| + return 0; |
| } |
| -int32_t AudioDeviceIOS::SetStereoPlayout(bool enable) { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "AudioDeviceIOS::SetStereoPlayout(enable=%u)", enable); |
| - |
| +// 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) { |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " Stereo playout is not supported on this platform"); |
| - return -1; |
| + options |= AVAudioSessionCategoryOptionDefaultToSpeaker; |
| + } else { |
| + options &= ~AVAudioSessionCategoryOptionDefaultToSpeaker; |
| } |
| - return 0; |
| + } 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::StereoPlayout(bool& enabled) const { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "%s", __FUNCTION__); |
| - |
| - enabled = false; |
| - return 0; |
| +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::SetAGC(bool enable) { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "AudioDeviceIOS::SetAGC(enable=%d)", enable); |
| - |
| - _AGC = enable; |
| - |
| - return 0; |
| +int32_t AudioDeviceIOS::PlayoutDelay(uint16_t& delayMS) const { |
| + delayMS = _playoutDelay; |
| + return 0; |
| } |
| -bool AudioDeviceIOS::AGC() const { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "%s", __FUNCTION__); |
| - |
| - return _AGC; |
| +int32_t AudioDeviceIOS::RecordingDelay(uint16_t& delayMS) const { |
| + delayMS = _recordingDelay; |
| + return 0; |
| } |
| -int32_t AudioDeviceIOS::MicrophoneVolumeIsAvailable(bool& available) { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "%s", __FUNCTION__); |
| - |
| - available = false; // Mic volume not supported on IOS |
| - |
| - return 0; |
| +int32_t AudioDeviceIOS::PlayoutBuffer(AudioDeviceModule::BufferType& type, |
| + uint16_t& sizeMS) const { |
| + type = AudioDeviceModule::kAdaptiveBufferSize; |
| + sizeMS = _playoutDelay; |
| + return 0; |
| } |
| -int32_t AudioDeviceIOS::SetMicrophoneVolume(uint32_t volume) { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "AudioDeviceIOS::SetMicrophoneVolume(volume=%u)", volume); |
| +int AudioDeviceIOS::GetPlayoutAudioParameters(AudioParameters* params) const { |
| + CHECK(playout_parameters_.is_valid()); |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + *params = playout_parameters_; |
| + return 0; |
| +} |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " API call not supported on this platform"); |
| - return -1; |
| +int AudioDeviceIOS::GetRecordAudioParameters(AudioParameters* params) const { |
| + CHECK(record_parameters_.is_valid()); |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + *params = record_parameters_; |
| + return 0; |
| } |
| -int32_t |
| - AudioDeviceIOS::MicrophoneVolume(uint32_t& volume) const { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "%s", __FUNCTION__); |
| +// ============================================================================ |
| +// Private Methods |
| +// ============================================================================ |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " API call not supported on this platform"); |
| - return -1; |
| -} |
| +int32_t AudioDeviceIOS::InitPlayOrRecord() { |
| + LOGI() << "AudioDeviceIOS::InitPlayOrRecord"; |
| + DCHECK(!_auVoiceProcessing); |
| -int32_t |
| - AudioDeviceIOS::MaxMicrophoneVolume(uint32_t& maxVolume) const { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "%s", __FUNCTION__); |
| + OSStatus result = -1; |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " API call not supported on this platform"); |
| - return -1; |
| -} |
| + // Create Voice Processing Audio Unit |
| + AudioComponentDescription desc; |
| + AudioComponent comp; |
| -int32_t |
| - AudioDeviceIOS::MinMicrophoneVolume(uint32_t& minVolume) const { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "%s", __FUNCTION__); |
| + desc.componentType = kAudioUnitType_Output; |
| + desc.componentSubType = kAudioUnitSubType_VoiceProcessingIO; |
| + desc.componentManufacturer = kAudioUnitManufacturer_Apple; |
| + desc.componentFlags = 0; |
| + desc.componentFlagsMask = 0; |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " API call not supported on this platform"); |
| + comp = AudioComponentFindNext(nullptr, &desc); |
| + if (nullptr == comp) { |
| + LOG_F(LS_ERROR) << "Could not find audio component for Audio Unit"; |
| return -1; |
| -} |
| - |
| -int32_t |
| - AudioDeviceIOS::MicrophoneVolumeStepSize( |
| - uint16_t& stepSize) const { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "%s", __FUNCTION__); |
| + } |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " API call not supported on this platform"); |
| + 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; |
| -int16_t AudioDeviceIOS::PlayoutDevices() { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "%s", __FUNCTION__); |
| + // Deactivate the audio session. |
| + ActivateAudioSession(session, false); |
| - return (int16_t)1; |
| + return 0; |
| } |
| -int32_t AudioDeviceIOS::SetPlayoutDevice(uint16_t index) { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "AudioDeviceIOS::SetPlayoutDevice(index=%u)", index); |
| +int32_t AudioDeviceIOS::ShutdownPlayOrRecord() { |
| + LOGI() << "ShutdownPlayOrRecord"; |
| - if (_playIsInitialized) { |
| - WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| - " Playout already initialized"); |
| - return -1; |
| + 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; |
| } |
| - |
| - if (index !=0) { |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " SetPlayoutDevice invalid index"); |
| - return -1; |
| + result = AudioComponentInstanceDispose(_auVoiceProcessing); |
| + if (0 != result) { |
| + LOG_F(LS_ERROR) << "AudioComponentInstanceDispose failed: " << result; |
| } |
| - _playoutDeviceIsSpecified = true; |
| + _auVoiceProcessing = nullptr; |
| + } |
| - return 0; |
| -} |
| - |
| -int32_t |
| - AudioDeviceIOS::SetPlayoutDevice(AudioDeviceModule::WindowsDeviceType) { |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - "WindowsDeviceType not supported"); |
| - return -1; |
| + return 0; |
| } |
| -int32_t |
| - AudioDeviceIOS::PlayoutDeviceName(uint16_t index, |
| - char name[kAdmMaxDeviceNameSize], |
| - char guid[kAdmMaxGuidSize]) { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "AudioDeviceIOS::PlayoutDeviceName(index=%u)", index); |
| - |
| - if (index != 0) { |
| - return -1; |
| - } |
| - // return empty strings |
| - memset(name, 0, kAdmMaxDeviceNameSize); |
| - if (guid != NULL) { |
| - memset(guid, 0, kAdmMaxGuidSize); |
| - } |
| +// ============================================================================ |
| +// 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; |
| -} |
| - |
| -int32_t |
| - AudioDeviceIOS::RecordingDeviceName(uint16_t index, |
| - char name[kAdmMaxDeviceNameSize], |
| - char guid[kAdmMaxGuidSize]) { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "AudioDeviceIOS::RecordingDeviceName(index=%u)", index); |
| + } |
| + |
| + 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; |
| + } |
| - if (index != 0) { |
| - return -1; |
| - } |
| - // return empty strings |
| - memset(name, 0, kAdmMaxDeviceNameSize); |
| - if (guid != NULL) { |
| - memset(guid, 0, kAdmMaxGuidSize); |
| + // 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; |
| + } |
| - return 0; |
| + // Update loop/index counter, if we copied less than noSamp10ms |
| + // samples we shall quit loop anyway |
| + dataPos += noSamp10ms; |
| + } |
| + delete[] dataTmp; |
| + } |
| + return 0; |
| } |
| -int16_t AudioDeviceIOS::RecordingDevices() { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); |
| +// TODO(henrika): can either be removed or simplified. |
| +void AudioDeviceIOS::UpdatePlayoutDelay() { |
| + ++_playoutDelayMeasurementCounter; |
| - return (int16_t)1; |
| -} |
| + if (_playoutDelayMeasurementCounter >= 100) { |
| + // Update HW and OS delay every second, unlikely to change |
| -int32_t AudioDeviceIOS::SetRecordingDevice(uint16_t index) { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "AudioDeviceIOS::SetRecordingDevice(index=%u)", index); |
| + // 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; |
| - if (_recIsInitialized) { |
| - WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| - " Recording already initialized"); |
| - return -1; |
| + // 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; |
| - if (index !=0) { |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " SetRecordingDevice invalid index"); |
| - return -1; |
| - } |
| + // To ms |
| + _playoutDelay = static_cast<uint32_t>(totalDelaySeconds / 1000); |
| - _recordingDeviceIsSpecified = true; |
| + // Reset counter |
| + _playoutDelayMeasurementCounter = 0; |
| + } |
| - return 0; |
| + // todo: Add playout buffer? |
| } |
| -int32_t |
| - AudioDeviceIOS::SetRecordingDevice( |
| - AudioDeviceModule::WindowsDeviceType) { |
| - WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| - "WindowsDeviceType not supported"); |
| - return -1; |
| -} |
| +void AudioDeviceIOS::UpdateRecordingDelay() { |
| + ++_recordingDelayMeasurementCounter; |
| -// ---------------------------------------------------------------------------- |
| -// SetLoudspeakerStatus |
| -// |
| -// Change the default receiver playout route to speaker. |
| -// |
| -// ---------------------------------------------------------------------------- |
| + if (_recordingDelayMeasurementCounter >= 100) { |
| + // Update HW and OS delay every second, unlikely to change |
| -int32_t AudioDeviceIOS::SetLoudspeakerStatus(bool enable) { |
| - WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, |
| - "AudioDeviceIOS::SetLoudspeakerStatus(enable=%d)", enable); |
| + // 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]; |
| - 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; |
| - [session setCategory:AVAudioSessionCategoryPlayAndRecord |
| - withOptions:options |
| - error:&error]; |
| - if (error != nil) { |
| - WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| - "Error changing default output route "); |
| - return -1; |
| + 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; |
| - return 0; |
| -} |
| + // To ms |
| + _recordingDelayHWAndOS = static_cast<uint32_t>(totalDelaySeconds / 1000); |
| -int32_t AudioDeviceIOS::GetLoudspeakerStatus(bool &enabled) const { |
| - WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, |
| - "AudioDeviceIOS::SetLoudspeakerStatus(enabled=?)"); |
| + // Reset counter |
| + _recordingDelayMeasurementCounter = 0; |
| + } |
| - AVAudioSession* session = [AVAudioSession sharedInstance]; |
| - AVAudioSessionCategoryOptions options = session.categoryOptions; |
| - enabled = options & AVAudioSessionCategoryOptionDefaultToSpeaker; |
| + _recordingDelay = _recordingDelayHWAndOS; |
| - return 0; |
| + // 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); |
| + } |
| } |
| -int32_t AudioDeviceIOS::PlayoutIsAvailable(bool& available) { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); |
| - |
| - available = false; |
| - |
| - // Try to initialize the playout side |
| - int32_t res = InitPlayout(); |
| - |
| - // Cancel effect of initialization |
| - StopPlayout(); |
| - |
| - if (res != -1) { |
| - available = true; |
| - } |
| - |
| - return 0; |
| +bool AudioDeviceIOS::RunCapture(void* ptrThis) { |
| + return static_cast<AudioDeviceIOS*>(ptrThis)->CaptureWorkerThread(); |
| } |
| -int32_t AudioDeviceIOS::RecordingIsAvailable(bool& available) { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); |
| - |
| - available = false; |
| - |
| - // Try to initialize the recording side |
| - int32_t res = InitRecording(); |
| +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; |
| + } |
| + } |
| + } |
| - // Cancel effect of initialization |
| - StopRecording(); |
| + // Insert data into the Audio Device Buffer if found any |
| + if (foundBuf) { |
| + // Update recording delay |
| + UpdateRecordingDelay(); |
| - if (res != -1) { |
| - available = true; |
| - } |
| + // Set the recorded buffer |
| + audio_device_buffer_->SetRecordedBuffer( |
| + reinterpret_cast<int8_t*>(_recordingBuffer[lowestSeqBufPos]), |
| + _recordingLength[lowestSeqBufPos]); |
| - return 0; |
| -} |
| + // 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. |
| -int32_t AudioDeviceIOS::InitPlayout() { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); |
| + // Set VQE info, use clockdrift == 0 |
| + audio_device_buffer_->SetVQEData(_playoutDelay, _recordingDelay, 0); |
| - CriticalSectionScoped lock(&_critSect); |
| + // Deliver recorded samples at specified sample rate, mic level |
| + // etc. to the observer using callback |
| + audio_device_buffer_->DeliverRecordedData(); |
| - if (!_initialized) { |
| - WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, " Not initialized"); |
| - return -1; |
| - } |
| - |
| - if (_playing) { |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " Playout already started"); |
| - return -1; |
| - } |
| - |
| - if (_playIsInitialized) { |
| - WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, |
| - " Playout already initialized"); |
| - return 0; |
| - } |
| - |
| - if (!_playoutDeviceIsSpecified) { |
| - WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| - " Playout device is not specified"); |
| - return -1; |
| - } |
| - |
| - // Initialize the speaker |
| - if (InitSpeaker() == -1) { |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " InitSpeaker() failed"); |
| - } |
| - |
| - _playIsInitialized = true; |
| - |
| - if (!_recIsInitialized) { |
| - // Audio init |
| - if (InitPlayOrRecord() == -1) { |
| - // todo: Handle error |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " InitPlayOrRecord() failed"); |
| - } |
| - } else { |
| - WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, |
| - " Recording already initialized - InitPlayOrRecord() not called"); |
| - } |
| - |
| - return 0; |
| -} |
| - |
| -bool AudioDeviceIOS::PlayoutIsInitialized() const { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); |
| - return (_playIsInitialized); |
| -} |
| - |
| -int32_t AudioDeviceIOS::InitRecording() { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); |
| - |
| - CriticalSectionScoped lock(&_critSect); |
| - |
| - if (!_initialized) { |
| - WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| - " Not initialized"); |
| - return -1; |
| - } |
| - |
| - if (_recording) { |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " Recording already started"); |
| - return -1; |
| - } |
| - |
| - if (_recIsInitialized) { |
| - WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, |
| - " Recording already initialized"); |
| - return 0; |
| - } |
| - |
| - if (!_recordingDeviceIsSpecified) { |
| - WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| - " Recording device is not specified"); |
| - return -1; |
| - } |
| - |
| - // Initialize the microphone |
| - if (InitMicrophone() == -1) { |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " InitMicrophone() failed"); |
| - } |
| - |
| - _recIsInitialized = true; |
| - |
| - if (!_playIsInitialized) { |
| - // Audio init |
| - if (InitPlayOrRecord() == -1) { |
| - // todo: Handle error |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " InitPlayOrRecord() failed"); |
| - } |
| - } else { |
| - WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, |
| - " Playout already initialized - InitPlayOrRecord() " \ |
| - "not called"); |
| - } |
| - |
| - return 0; |
| -} |
| - |
| -bool AudioDeviceIOS::RecordingIsInitialized() const { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); |
| - return (_recIsInitialized); |
| -} |
| - |
| -int32_t AudioDeviceIOS::StartRecording() { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); |
| - |
| - CriticalSectionScoped lock(&_critSect); |
| - |
| - if (!_recIsInitialized) { |
| - WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| - " Recording not initialized"); |
| - return -1; |
| - } |
| - |
| - if (_recording) { |
| - WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, |
| - " Recording already started"); |
| - return 0; |
| - } |
| - |
| - // Reset recording buffer |
| - 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; |
| - _recWarning = 0; |
| - _recError = 0; |
| - |
| - if (!_playing) { |
| - // Start Audio Unit |
| - WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, |
| - " Starting Audio Unit"); |
| - OSStatus result = AudioOutputUnitStart(_auVoiceProcessing); |
| - if (0 != result) { |
| - WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice, _id, |
| - " Error starting Audio Unit (result=%d)", result); |
| - return -1; |
| - } |
| - } |
| - |
| - _recording = true; |
| - |
| - return 0; |
| -} |
| - |
| -int32_t AudioDeviceIOS::StopRecording() { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); |
| - |
| - CriticalSectionScoped lock(&_critSect); |
| - |
| - if (!_recIsInitialized) { |
| - WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, |
| - " Recording is not initialized"); |
| - return 0; |
| - } |
| - |
| - _recording = false; |
| - |
| - if (!_playing) { |
| - // Both playout and recording has stopped, shutdown the device |
| - ShutdownPlayOrRecord(); |
| - } |
| - |
| - _recIsInitialized = false; |
| - _micIsInitialized = false; |
| - |
| - return 0; |
| -} |
| - |
| -bool AudioDeviceIOS::Recording() const { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); |
| - return (_recording); |
| -} |
| - |
| -int32_t AudioDeviceIOS::StartPlayout() { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); |
| - |
| - // This lock is (among other things) needed to avoid concurrency issues |
| - // with capture thread |
| - // shutting down Audio Unit |
| - CriticalSectionScoped lock(&_critSect); |
| - |
| - if (!_playIsInitialized) { |
| - WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| - " Playout not initialized"); |
| - return -1; |
| - } |
| - |
| - if (_playing) { |
| - WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, |
| - " Playing already started"); |
| - return 0; |
| - } |
| - |
| - // Reset playout buffer |
| - memset(_playoutBuffer, 0, sizeof(_playoutBuffer)); |
| - _playoutBufferUsed = 0; |
| - _playoutDelay = 0; |
| - // Make sure first call to update delay function will update delay |
| - _playoutDelayMeasurementCounter = 9999; |
| - _playWarning = 0; |
| - _playError = 0; |
| - |
| - if (!_recording) { |
| - // Start Audio Unit |
| - WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, |
| - " Starting Audio Unit"); |
| - OSStatus result = AudioOutputUnitStart(_auVoiceProcessing); |
| - if (0 != result) { |
| - WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice, _id, |
| - " Error starting Audio Unit (result=%d)", result); |
| - return -1; |
| - } |
| - } |
| - |
| - _playing = true; |
| - |
| - return 0; |
| -} |
| - |
| -int32_t AudioDeviceIOS::StopPlayout() { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); |
| - |
| - CriticalSectionScoped lock(&_critSect); |
| - |
| - if (!_playIsInitialized) { |
| - WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, |
| - " Playout is not initialized"); |
| - return 0; |
| - } |
| - |
| - _playing = false; |
| - |
| - if (!_recording) { |
| - // Both playout and recording has stopped, signal shutdown the device |
| - ShutdownPlayOrRecord(); |
| - } |
| - |
| - _playIsInitialized = false; |
| - _speakerIsInitialized = false; |
| - |
| - return 0; |
| -} |
| - |
| -bool AudioDeviceIOS::Playing() const { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "%s", __FUNCTION__); |
| - return (_playing); |
| -} |
| - |
| -// ---------------------------------------------------------------------------- |
| -// ResetAudioDevice |
| -// |
| -// Disable playout and recording, signal to capture thread to shutdown, |
| -// and set enable states after shutdown to same as current. |
| -// In capture thread audio device will be shutdown, then started again. |
| -// ---------------------------------------------------------------------------- |
| -int32_t AudioDeviceIOS::ResetAudioDevice() { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); |
| - |
| - CriticalSectionScoped lock(&_critSect); |
| - |
| - if (!_playIsInitialized && !_recIsInitialized) { |
| - WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, |
| - " Playout or recording not initialized, doing nothing"); |
| - return 0; // Nothing to reset |
| - } |
| - |
| - // Store the states we have before stopping to restart below |
| - bool initPlay = _playIsInitialized; |
| - bool play = _playing; |
| - bool initRec = _recIsInitialized; |
| - bool rec = _recording; |
| - |
| - int res(0); |
| - |
| - // Stop playout and recording |
| - WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, |
| - " Stopping playout and recording"); |
| - res += StopPlayout(); |
| - res += StopRecording(); |
| - |
| - // Restart |
| - WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, |
| - " Restarting playout and recording (%d, %d, %d, %d)", |
| - initPlay, play, initRec, rec); |
| - if (initPlay) res += InitPlayout(); |
| - if (initRec) res += InitRecording(); |
| - if (play) res += StartPlayout(); |
| - if (rec) res += StartRecording(); |
| - |
| - if (0 != res) { |
| - // Logging is done in init/start/stop calls above |
| - return -1; |
| - } |
| - |
| - 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::SetPlayoutBuffer(const AudioDeviceModule::BufferType type, |
| - uint16_t sizeMS) { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| - "AudioDeviceIOS::SetPlayoutBuffer(type=%u, sizeMS=%u)", |
| - type, sizeMS); |
| - |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " API call not supported on this platform"); |
| - return -1; |
| -} |
| - |
| -int32_t |
| - AudioDeviceIOS::PlayoutBuffer(AudioDeviceModule::BufferType& type, |
| - uint16_t& sizeMS) const { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); |
| - |
| - type = AudioDeviceModule::kAdaptiveBufferSize; |
| - |
| - sizeMS = _playoutDelay; |
| - |
| - return 0; |
| -} |
| - |
| -int32_t AudioDeviceIOS::CPULoad(uint16_t& /*load*/) const { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); |
| - |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " API call not supported on this platform"); |
| - return -1; |
| -} |
| - |
| -bool AudioDeviceIOS::PlayoutWarning() const { |
| - return (_playWarning > 0); |
| -} |
| - |
| -bool AudioDeviceIOS::PlayoutError() const { |
| - return (_playError > 0); |
| -} |
| - |
| -bool AudioDeviceIOS::RecordingWarning() const { |
| - return (_recWarning > 0); |
| -} |
| - |
| -bool AudioDeviceIOS::RecordingError() const { |
| - return (_recError > 0); |
| -} |
| - |
| -void AudioDeviceIOS::ClearPlayoutWarning() { |
| - _playWarning = 0; |
| -} |
| - |
| -void AudioDeviceIOS::ClearPlayoutError() { |
| - _playError = 0; |
| -} |
| - |
| -void AudioDeviceIOS::ClearRecordingWarning() { |
| - _recWarning = 0; |
| -} |
| - |
| -void AudioDeviceIOS::ClearRecordingError() { |
| - _recError = 0; |
| -} |
| - |
| -// ============================================================================ |
| -// Private Methods |
| -// ============================================================================ |
| - |
| -int32_t AudioDeviceIOS::InitPlayOrRecord() { |
| - WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); |
| - |
| - OSStatus result = -1; |
| - |
| - // Check if already initialized |
| - if (NULL != _auVoiceProcessing) { |
| - // We already have initialized before and created any of the audio unit, |
| - // check that all exist |
| - WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, |
| - " Already initialized"); |
| - // todo: Call AudioUnitReset() here and empty all buffers? |
| - return 0; |
| - } |
| - |
| - // 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(NULL, &desc); |
| - if (NULL == comp) { |
| - WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| - " Could not find audio component for Audio Unit"); |
| - return -1; |
| - } |
| - |
| - result = AudioComponentInstanceNew(comp, &_auVoiceProcessing); |
| - if (0 != result) { |
| - WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| - " Could not create Audio Unit instance (result=%d)", |
| - result); |
| - return -1; |
| - } |
| - |
| - // Set preferred hardware sample rate to 16 kHz |
| - NSError* error = nil; |
| - AVAudioSession* session = [AVAudioSession sharedInstance]; |
| - Float64 preferredSampleRate(16000.0); |
| - [session setPreferredSampleRate:preferredSampleRate |
| - error:&error]; |
| - if (error != nil) { |
| - const char* errorString = [[error localizedDescription] UTF8String]; |
| - WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, |
| - "Could not set preferred sample rate: %s", errorString); |
| - } |
| - error = nil; |
| - // Make the setMode:error: and setCategory:error: calls only if necessary. |
| - // Non-obviously, setting them to the value they already have will clear |
| - // transient properties (such as PortOverride) that some other component may |
| - // have set up. |
| - if (session.mode != AVAudioSessionModeVoiceChat) { |
| - [session setMode:AVAudioSessionModeVoiceChat error:&error]; |
| - if (error != nil) { |
| - const char* errorString = [[error localizedDescription] UTF8String]; |
| - WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, |
| - "Could not set mode: %s", errorString); |
| + // Make buffer available |
| + _recordingSeqNumber[lowestSeqBufPos] = 0; |
| + _recordingBufferTotalSize -= _recordingLength[lowestSeqBufPos]; |
| + // Must be done last to avoid interrupt problems between threads |
| + _recordingLength[lowestSeqBufPos] = 0; |
| } |
| } |
| - error = nil; |
| - if (session.category != AVAudioSessionCategoryPlayAndRecord) { |
| - [session setCategory:AVAudioSessionCategoryPlayAndRecord error:&error]; |
| - if (error != nil) { |
| - const char* errorString = [[error localizedDescription] UTF8String]; |
| - WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, |
| - "Could not set category: %s", errorString); |
| - } |
| - } |
| - |
| - ////////////////////// |
| - // Setup Voice Processing Audio Unit |
| - |
| - // Note: For Signal Processing AU element 0 is output bus, element 1 is |
| - // input bus for global scope element is irrelevant (always use |
| - // element 0) |
| - |
| - // Enable IO on both elements |
| - |
| - // todo: Below we just log and continue upon error. We might want |
| - // to close AU and return error for some cases. |
| - // todo: Log info about setup. |
| - |
| - UInt32 enableIO = 1; |
| - result = AudioUnitSetProperty(_auVoiceProcessing, |
| - kAudioOutputUnitProperty_EnableIO, |
| - kAudioUnitScope_Input, |
| - 1, // input bus |
| - &enableIO, |
| - sizeof(enableIO)); |
| - if (0 != result) { |
| - WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| - " Could not enable IO on input (result=%d)", result); |
| - } |
| - |
| - result = AudioUnitSetProperty(_auVoiceProcessing, |
| - kAudioOutputUnitProperty_EnableIO, |
| - kAudioUnitScope_Output, |
| - 0, // output bus |
| - &enableIO, |
| - sizeof(enableIO)); |
| - if (0 != result) { |
| - WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| - " Could not enable IO on output (result=%d)", result); |
| - } |
| - |
| - // Disable AU buffer allocation for the recorder, we allocate our own |
| - UInt32 flag = 0; |
| - result = AudioUnitSetProperty( |
| - _auVoiceProcessing, kAudioUnitProperty_ShouldAllocateBuffer, |
| - kAudioUnitScope_Output, 1, &flag, sizeof(flag)); |
| - if (0 != result) { |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " Could not disable AU buffer allocation (result=%d)", |
| - 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) { |
| - WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| - " Could not set record callback for Audio Unit (result=%d)", |
| - 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) { |
| - WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| - " Could not set play callback for Audio Unit (result=%d)", |
| - 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) { |
| - WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| - " Could not get stream format Audio Unit out/0 (result=%d)", |
| - result); |
| - } |
| - WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, |
| - " Audio Unit playout opened in sampling rate %f", |
| - playoutDesc.mSampleRate); |
| - |
| - playoutDesc.mSampleRate = preferredSampleRate; |
| - |
| - // Store the sampling frequency to use towards the Audio Device Buffer |
| - // todo: Add 48 kHz (increase buffer sizes). Other fs? |
| - 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; |
| - WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| - " Audio Unit out/0 opened in unknown sampling rate (%f)", |
| - playoutDesc.mSampleRate); |
| - // todo: We should bail out here. |
| - } |
| - |
| - // Set the audio device buffer sampling rate, |
| - // we assume we get the same for play and record |
| - if (_ptrAudioBuffer->SetRecordingSampleRate(_adbSampFreq) < 0) { |
| - WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| - " Could not set audio device buffer recording sampling rate (%d)", |
| - _adbSampFreq); |
| - } |
| - |
| - if (_ptrAudioBuffer->SetPlayoutSampleRate(_adbSampFreq) < 0) { |
| - WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| - " Could not set audio device buffer playout sampling rate (%d)", |
| - _adbSampFreq); |
| - } |
| - |
| - // Set stream format for in/0 (use same sampling frequency as 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) { |
| - WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| - " Could not set stream format Audio Unit in/0 (result=%d)", |
| - result); |
| - } |
| - |
| - // Get stream format for in/1 |
| - AudioStreamBasicDescription recordingDesc; |
| - size = sizeof(recordingDesc); |
| - result = AudioUnitGetProperty(_auVoiceProcessing, |
| - kAudioUnitProperty_StreamFormat, |
| - kAudioUnitScope_Input, 1, &recordingDesc, |
| - &size); |
| - if (0 != result) { |
| - WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| - " Could not get stream format Audio Unit in/1 (result=%d)", |
| - result); |
| - } |
| - WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, |
| - " Audio Unit recording opened in sampling rate %f", |
| - recordingDesc.mSampleRate); |
| - |
| - recordingDesc.mSampleRate = preferredSampleRate; |
| - |
| - // 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) { |
| - WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| - " Could not set stream format Audio Unit out/1 (result=%d)", |
| - result); |
| - } |
| - |
| - // Initialize here already to be able to get/set stream properties. |
| - result = AudioUnitInitialize(_auVoiceProcessing); |
| - if (0 != result) { |
| - WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| - " Could not init Audio Unit (result=%d)", result); |
| - } |
| - |
| - // Get hardware sample rate for logging (see if we get what we asked for) |
| - double sampleRate = session.sampleRate; |
| - WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, |
| - " Current HW sample rate is %f, ADB sample rate is %d", |
| - sampleRate, _adbSampFreq); |
| - |
| - // Listen to audio interruptions. |
| - 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) { |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - "Error activating 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; |
| - |
| - // Activate audio session. |
| - error = nil; |
| - [session setActive:YES |
| - error:&error]; |
| - if (error != nil) { |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - "Error activating audio session"); |
| - } |
| - |
| - return 0; |
| -} |
| - |
| -int32_t AudioDeviceIOS::ShutdownPlayOrRecord() { |
| - WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "%s", __FUNCTION__); |
| - |
| - if (_audioInterruptionObserver != NULL) { |
| - 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 = NULL; |
| - } |
| - |
| - // Close and delete AU |
| - OSStatus result = -1; |
| - if (NULL != _auVoiceProcessing) { |
| - result = AudioOutputUnitStop(_auVoiceProcessing); |
| - if (0 != result) { |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " Error stopping Audio Unit (result=%d)", result); |
| - } |
| - result = AudioComponentInstanceDispose(_auVoiceProcessing); |
| - if (0 != result) { |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " Error disposing Audio Unit (result=%d)", result); |
| - } |
| - _auVoiceProcessing = NULL; |
| - } |
| - |
| - 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) { |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " Error getting rec data, error = %d", res); |
| - |
| - if (_recWarning > 0) { |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " Pending rec warning exists"); |
| - } |
| - _recWarning = 1; |
| - |
| - 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 |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " Could not insert into recording buffer"); |
| - if (_recWarning > 0) { |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " Pending rec warning exists"); |
| - } |
| - _recWarning = 1; |
| - 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) { |
| - // Setup some basic stuff |
| -// assert(sizeof(short) == 2); // Assumption for implementation |
| - |
| - 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 |
| - if (dataSize != inNumberFrames) { // Should always be the same |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - "dataSize (%u) != inNumberFrames (%u)", |
| - dataSize, (unsigned int)inNumberFrames); |
| - if (_playWarning > 0) { |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " Pending play warning exists"); |
| - } |
| - _playWarning = 1; |
| - } |
| - 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; |
| - if (nCopy != _playoutBufferUsed) { |
| - // todo: If dataSize < _playoutBufferUsed |
| - // (should normally never be) |
| - // we must move the remaining data |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - "nCopy (%u) != _playoutBufferUsed (%u)", |
| - nCopy, _playoutBufferUsed); |
| - if (_playWarning > 0) { |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " Pending play warning exists"); |
| - } |
| - _playWarning = 1; |
| - } |
| - 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 = _ptrAudioBuffer->RequestPlayoutData(noSamp10ms); |
| - |
| - // Get data from Audio Device Buffer |
| - noSamplesOut = |
| - _ptrAudioBuffer->GetPlayoutData( |
| - reinterpret_cast<int8_t*>(dataTmp)); |
| - // Cast OK since only equality comparison |
| - if (noSamp10ms != (unsigned int)noSamplesOut) { |
| - // Should never happen |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - "noSamp10ms (%u) != noSamplesOut (%d)", |
| - noSamp10ms, noSamplesOut); |
| - |
| - if (_playWarning > 0) { |
| - WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| - " Pending play warning exists"); |
| - } |
| - _playWarning = 1; |
| - } |
| - |
| - // 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; |
| -} |
| - |
| -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) { |
| - WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| - "error AU latency (result=%d)", 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) { |
| - WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| - "error AU latency (result=%d)", 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; |
| - } |
| - } |
| - } // for |
| - |
| - // Insert data into the Audio Device Buffer if found any |
| - if (foundBuf) { |
| - // Update recording delay |
| - UpdateRecordingDelay(); |
| - |
| - // Set the recorded buffer |
| - _ptrAudioBuffer->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 |
| - _ptrAudioBuffer->SetVQEData(_playoutDelay, _recordingDelay, 0); |
| - |
| - // Deliver recorded samples at specified sample rate, mic level |
| - // etc. to the observer using callback |
| - _ptrAudioBuffer->DeliverRecordedData(); |
| - |
| - // Make buffer available |
| - _recordingSeqNumber[lowestSeqBufPos] = 0; |
| - _recordingBufferTotalSize -= _recordingLength[lowestSeqBufPos]; |
| - // Must be done last to avoid interrupt problems between threads |
| - _recordingLength[lowestSeqBufPos] = 0; |
| - } |
| - } // while (foundBuf) |
| - } // if (_recording) |
| - |
| - { |
| - // 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, NULL); |
| - } |
| - |
| - return true; |
| + } |
| + |
| + { |
| + // 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 |