| /* |
| * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. |
| * |
| * Use of this source code is governed by a BSD-style license |
| * that can be found in the LICENSE file in the root of the source |
| * tree. An additional intellectual property rights grant can be found |
| * in the file PATENTS. All contributing project authors may |
| * be found in the AUTHORS file in the root of the source tree. |
| */ |
| |
| #include <AudioToolbox/AudioServices.h> // AudioSession |
| |
| #include "audio_device_ios.h" |
| |
| #include "trace.h" |
| #include "thread_wrapper.h" |
| |
| namespace webrtc { |
| AudioDeviceIPhone::AudioDeviceIPhone(const WebRtc_Word32 id) |
| : |
| _ptrAudioBuffer(NULL), |
| _critSect(*CriticalSectionWrapper::CreateCriticalSection()), |
| _captureWorkerThread(NULL), |
| _captureWorkerThreadId(0), |
| _id(id), |
| _auRemoteIO(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)); |
| } |
| |
| AudioDeviceIPhone::~AudioDeviceIPhone() { |
| WEBRTC_TRACE(kTraceMemory, kTraceAudioDevice, _id, |
| "%s destroyed", __FUNCTION__); |
| |
| Terminate(); |
| |
| delete &_critSect; |
| } |
| |
| |
| // ============================================================================ |
| // API |
| // ============================================================================ |
| |
| void AudioDeviceIPhone::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); |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::ActiveAudioLayer( |
| AudioDeviceModule::AudioLayer& audioLayer) const { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "%s", __FUNCTION__); |
| audioLayer = AudioDeviceModule::kPlatformDefaultAudio; |
| return 0; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::Init() { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "%s", __FUNCTION__); |
| |
| CriticalSectionScoped lock(&_critSect); |
| |
| if (_initialized) { |
| return 0; |
| } |
| |
| _isShutDown = false; |
| |
| // Create and start capture thread |
| if (_captureWorkerThread == NULL) { |
| _captureWorkerThread |
| = ThreadWrapper::CreateThread(RunCapture, this, kRealtimePriority, |
| "CaptureWorkerThread"); |
| |
| if (_captureWorkerThread == NULL) { |
| WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice, |
| _id, "CreateThread() error"); |
| return -1; |
| } |
| |
| unsigned int threadID(0); |
| bool res = _captureWorkerThread->Start(threadID); |
| _captureWorkerThreadId = static_cast<WebRtc_UWord32>(threadID); |
| WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, |
| _id, "CaptureWorkerThread started (res=%d)", res); |
| } else { |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, |
| _id, "Thread already created"); |
| } |
| |
| // Set preferred hardware sample rate to 16 kHz |
| Float64 sampleRate(16000.0); |
| OSStatus result = AudioSessionSetProperty( |
| kAudioSessionProperty_PreferredHardwareSampleRate, |
| sizeof(sampleRate), &sampleRate); |
| if (0 != result) { |
| WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, |
| "Could not set preferred sample rate (result=%d)", result); |
| } |
| |
| _playWarning = 0; |
| _playError = 0; |
| _recWarning = 0; |
| _recError = 0; |
| |
| _initialized = true; |
| |
| return 0; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::Terminate() { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "%s", __FUNCTION__); |
| |
| if (!_initialized) { |
| return 0; |
| } |
| |
| |
| // Stop capture thread |
| if (_captureWorkerThread != NULL) { |
| WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, |
| _id, "Stopping CaptureWorkerThread"); |
| bool res = _captureWorkerThread->Stop(); |
| WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, |
| _id, "CaptureWorkerThread stopped (res=%d)", res); |
| delete _captureWorkerThread; |
| _captureWorkerThread = NULL; |
| } |
| |
| // Shut down AU Remote IO |
| ShutdownPlayOrRecord(); |
| |
| _isShutDown = true; |
| _initialized = false; |
| _speakerIsInitialized = false; |
| _micIsInitialized = false; |
| _playoutDeviceIsSpecified = false; |
| _recordingDeviceIsSpecified = false; |
| return 0; |
| } |
| |
| bool AudioDeviceIPhone::Initialized() const { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "%s", __FUNCTION__); |
| return (_initialized); |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::SpeakerIsAvailable(bool& available) { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "%s", __FUNCTION__); |
| |
| // speaker is always available in IOS |
| available = true; |
| return 0; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::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; |
| } |
| |
| // Do nothing |
| _speakerIsInitialized = true; |
| |
| return 0; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::MicrophoneIsAvailable(bool& available) { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "%s", __FUNCTION__); |
| |
| available = false; |
| |
| OSStatus result = -1; |
| UInt32 channel = 0; |
| UInt32 size = sizeof(channel); |
| result = AudioSessionGetProperty(kAudioSessionProperty_AudioInputAvailable, |
| &size, &channel); |
| if (channel != 0) { |
| // API is not supported on this platform, we return available = true |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, |
| _id, " API call not supported on this version"); |
| available = true; |
| return 0; |
| } |
| |
| available = (channel == 0) ? false : true; |
| |
| return 0; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::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; |
| } |
| |
| // Do nothing |
| |
| _micIsInitialized = true; |
| |
| return 0; |
| } |
| |
| bool AudioDeviceIPhone::SpeakerIsInitialized() const { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "%s", __FUNCTION__); |
| return _speakerIsInitialized; |
| } |
| |
| bool AudioDeviceIPhone::MicrophoneIsInitialized() const { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "%s", __FUNCTION__); |
| return _micIsInitialized; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::SpeakerVolumeIsAvailable(bool& available) { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "%s", __FUNCTION__); |
| |
| available = false; // Speaker volume not supported on iOS |
| |
| return 0; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::SetSpeakerVolume(WebRtc_UWord32 volume) { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "AudioDeviceIPhone::SetSpeakerVolume(volume=%u)", volume); |
| |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| " API call not supported on this platform"); |
| return -1; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::SpeakerVolume(WebRtc_UWord32& volume) const { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "%s", __FUNCTION__); |
| |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| " API call not supported on this platform"); |
| return -1; |
| } |
| |
| WebRtc_Word32 |
| AudioDeviceIPhone::SetWaveOutVolume(WebRtc_UWord16 volumeLeft, |
| WebRtc_UWord16 volumeRight) { |
| WEBRTC_TRACE( |
| kTraceModuleCall, |
| kTraceAudioDevice, |
| _id, |
| "AudioDeviceIPhone::SetWaveOutVolume(volumeLeft=%u, volumeRight=%u)", |
| volumeLeft, volumeRight); |
| |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| " API call not supported on this platform"); |
| |
| return -1; |
| } |
| |
| WebRtc_Word32 |
| AudioDeviceIPhone::WaveOutVolume(WebRtc_UWord16& /*volumeLeft*/, |
| WebRtc_UWord16& /*volumeRight*/) const { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "%s", __FUNCTION__); |
| |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| " API call not supported on this platform"); |
| return -1; |
| } |
| |
| WebRtc_Word32 |
| AudioDeviceIPhone::MaxSpeakerVolume(WebRtc_UWord32& maxVolume) const { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "%s", __FUNCTION__); |
| |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| " API call not supported on this platform"); |
| return -1; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::MinSpeakerVolume( |
| WebRtc_UWord32& minVolume) const { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "%s", __FUNCTION__); |
| |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| " API call not supported on this platform"); |
| return -1; |
| } |
| |
| WebRtc_Word32 |
| AudioDeviceIPhone::SpeakerVolumeStepSize(WebRtc_UWord16& stepSize) const { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "%s", __FUNCTION__); |
| |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| " API call not supported on this platform"); |
| return -1; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::SpeakerMuteIsAvailable(bool& available) { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "%s", __FUNCTION__); |
| |
| available = false; // Speaker mute not supported on iOS |
| |
| return 0; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::SetSpeakerMute(bool enable) { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "%s", __FUNCTION__); |
| |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| " API call not supported on this platform"); |
| return -1; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::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; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::MicrophoneMuteIsAvailable(bool& available) { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "%s", __FUNCTION__); |
| |
| available = false; // Mic mute not supported on iOS |
| |
| return 0; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::SetMicrophoneMute(bool enable) { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "%s", __FUNCTION__); |
| |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| " API call not supported on this platform"); |
| return -1; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::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; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::MicrophoneBoostIsAvailable(bool& available) { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "%s", __FUNCTION__); |
| |
| available = false; // Mic boost not supported on iOS |
| |
| return 0; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::SetMicrophoneBoost(bool enable) { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "AudioDeviceIPhone::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; |
| } |
| |
| return 0; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::MicrophoneBoost(bool& enabled) const { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "%s", __FUNCTION__); |
| if (!_micIsInitialized) { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " Microphone not initialized"); |
| return -1; |
| } |
| |
| enabled = false; |
| |
| return 0; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::StereoRecordingIsAvailable(bool& available) { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "%s", __FUNCTION__); |
| |
| available = false; // Stereo recording not supported on iOS |
| |
| return 0; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::SetStereoRecording(bool enable) { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "AudioDeviceIPhone::SetStereoRecording(enable=%u)", enable); |
| |
| if (enable) { |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| " Stereo recording is not supported on this platform"); |
| return -1; |
| } |
| return 0; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::StereoRecording(bool& enabled) const { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "%s", __FUNCTION__); |
| |
| enabled = false; |
| return 0; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::StereoPlayoutIsAvailable(bool& available) { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "%s", __FUNCTION__); |
| |
| available = false; // Stereo playout not supported on iOS |
| |
| return 0; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::SetStereoPlayout(bool enable) { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "AudioDeviceIPhone::SetStereoPlayout(enable=%u)", enable); |
| |
| if (enable) { |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| " Stereo playout is not supported on this platform"); |
| return -1; |
| } |
| return 0; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::StereoPlayout(bool& enabled) const { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "%s", __FUNCTION__); |
| |
| enabled = false; |
| return 0; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::SetAGC(bool enable) { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "AudioDeviceIPhone::SetAGC(enable=%d)", enable); |
| |
| _AGC = enable; |
| |
| return 0; |
| } |
| |
| bool AudioDeviceIPhone::AGC() const { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "%s", __FUNCTION__); |
| |
| return _AGC; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::MicrophoneVolumeIsAvailable(bool& available) { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "%s", __FUNCTION__); |
| |
| available = false; // Mic volume not supported on IOS |
| |
| return 0; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::SetMicrophoneVolume(WebRtc_UWord32 volume) { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "AudioDeviceIPhone::SetMicrophoneVolume(volume=%u)", volume); |
| |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| " API call not supported on this platform"); |
| return -1; |
| } |
| |
| WebRtc_Word32 |
| AudioDeviceIPhone::MicrophoneVolume(WebRtc_UWord32& volume) const { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "%s", __FUNCTION__); |
| |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| " API call not supported on this platform"); |
| return -1; |
| } |
| |
| WebRtc_Word32 |
| AudioDeviceIPhone::MaxMicrophoneVolume(WebRtc_UWord32& maxVolume) const { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "%s", __FUNCTION__); |
| |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| " API call not supported on this platform"); |
| return -1; |
| } |
| |
| WebRtc_Word32 |
| AudioDeviceIPhone::MinMicrophoneVolume(WebRtc_UWord32& minVolume) const { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "%s", __FUNCTION__); |
| |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| " API call not supported on this platform"); |
| return -1; |
| } |
| |
| WebRtc_Word32 |
| AudioDeviceIPhone::MicrophoneVolumeStepSize( |
| WebRtc_UWord16& stepSize) const { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "%s", __FUNCTION__); |
| |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| " API call not supported on this platform"); |
| return -1; |
| } |
| |
| WebRtc_Word16 AudioDeviceIPhone::PlayoutDevices() { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "%s", __FUNCTION__); |
| |
| return (WebRtc_Word16)1; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::SetPlayoutDevice(WebRtc_UWord16 index) { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "AudioDeviceIPhone::SetPlayoutDevice(index=%u)", index); |
| |
| if (_playIsInitialized) { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " Playout already initialized"); |
| return -1; |
| } |
| |
| if (index !=0) { |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| " SetPlayoutDevice invalid index"); |
| return -1; |
| } |
| _playoutDeviceIsSpecified = true; |
| |
| return 0; |
| } |
| |
| WebRtc_Word32 |
| AudioDeviceIPhone::SetPlayoutDevice(AudioDeviceModule::WindowsDeviceType) { |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| "WindowsDeviceType not supported"); |
| return -1; |
| } |
| |
| WebRtc_Word32 |
| AudioDeviceIPhone::PlayoutDeviceName(WebRtc_UWord16 index, |
| char name[kAdmMaxDeviceNameSize], |
| char guid[kAdmMaxGuidSize]) { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "AudioDeviceIPhone::PlayoutDeviceName(index=%u)", index); |
| |
| if (index != 0) { |
| return -1; |
| } |
| // return empty strings |
| memset(name, 0, kAdmMaxDeviceNameSize); |
| if (guid != NULL) { |
| memset(guid, 0, kAdmMaxGuidSize); |
| } |
| |
| return 0; |
| } |
| |
| WebRtc_Word32 |
| AudioDeviceIPhone::RecordingDeviceName(WebRtc_UWord16 index, |
| char name[kAdmMaxDeviceNameSize], |
| char guid[kAdmMaxGuidSize]) { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "AudioDeviceIPhone::RecordingDeviceName(index=%u)", index); |
| |
| if (index != 0) { |
| return -1; |
| } |
| // return empty strings |
| memset(name, 0, kAdmMaxDeviceNameSize); |
| if (guid != NULL) { |
| memset(guid, 0, kAdmMaxGuidSize); |
| } |
| |
| return 0; |
| } |
| |
| WebRtc_Word16 AudioDeviceIPhone::RecordingDevices() { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); |
| |
| return (WebRtc_Word16)1; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::SetRecordingDevice(WebRtc_UWord16 index) { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "AudioDeviceIPhone::SetRecordingDevice(index=%u)", index); |
| |
| if (_recIsInitialized) { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " Recording already initialized"); |
| return -1; |
| } |
| |
| if (index !=0) { |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| " SetRecordingDevice invalid index"); |
| return -1; |
| } |
| |
| _recordingDeviceIsSpecified = true; |
| |
| return 0; |
| } |
| |
| WebRtc_Word32 |
| AudioDeviceIPhone::SetRecordingDevice( |
| AudioDeviceModule::WindowsDeviceType) { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| "WindowsDeviceType not supported"); |
| return -1; |
| } |
| |
| // ---------------------------------------------------------------------------- |
| // SetLoudspeakerStatus |
| // |
| // Overrides the receiver playout route to speaker instead. See |
| // kAudioSessionProperty_OverrideCategoryDefaultToSpeaker in CoreAudio |
| // documentation. |
| // ---------------------------------------------------------------------------- |
| |
| WebRtc_Word32 AudioDeviceIPhone::SetLoudspeakerStatus(bool enable) { |
| WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, |
| "AudioDeviceIPhone::SetLoudspeakerStatus(enable=%d)", enable); |
| |
| UInt32 doChangeDefaultRoute = enable ? 1 : 0; |
| OSStatus err = AudioSessionSetProperty( |
| kAudioSessionProperty_OverrideCategoryDefaultToSpeaker, |
| sizeof(doChangeDefaultRoute), &doChangeDefaultRoute); |
| |
| if (err != noErr) { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| "Error changing default output route " \ |
| "(only available on iOS 3.1 or later)"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::GetLoudspeakerStatus(bool &enabled) const { |
| WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, |
| "AudioDeviceIPhone::SetLoudspeakerStatus(enabled=?)"); |
| |
| UInt32 route(0); |
| UInt32 size = sizeof(route); |
| OSStatus err = AudioSessionGetProperty( |
| kAudioSessionProperty_OverrideCategoryDefaultToSpeaker, |
| &size, &route); |
| if (err != noErr) { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| "Error changing default output route " \ |
| "(only available on iOS 3.1 or later)"); |
| return -1; |
| } |
| |
| enabled = route == 1 ? true: false; |
| |
| return 0; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::PlayoutIsAvailable(bool& available) { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); |
| |
| available = false; |
| |
| // Try to initialize the playout side |
| WebRtc_Word32 res = InitPlayout(); |
| |
| // Cancel effect of initialization |
| StopPlayout(); |
| |
| if (res != -1) { |
| available = true; |
| } |
| |
| return 0; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::RecordingIsAvailable(bool& available) { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); |
| |
| available = false; |
| |
| // Try to initialize the recording side |
| WebRtc_Word32 res = InitRecording(); |
| |
| // Cancel effect of initialization |
| StopRecording(); |
| |
| if (res != -1) { |
| available = true; |
| } |
| |
| return 0; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::InitPlayout() { |
| 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(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 AudioDeviceIPhone::PlayoutIsInitialized() const { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); |
| return (_playIsInitialized); |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::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 AudioDeviceIPhone::RecordingIsInitialized() const { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); |
| return (_recIsInitialized); |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::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 AU Remote IO |
| WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, |
| " Starting AU Remote IO"); |
| OSStatus result = AudioOutputUnitStart(_auRemoteIO); |
| if (0 != result) { |
| WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice, _id, |
| " Error starting AU Remote IO (result=%d)", result); |
| return -1; |
| } |
| } |
| |
| _recording = true; |
| |
| return 0; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::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 AudioDeviceIPhone::Recording() const { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); |
| return (_recording); |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::StartPlayout() { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); |
| |
| // This lock is (among other things) needed to avoid concurrency issues |
| // with capture thread |
| // shutting down AU Remote IO |
| 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 AU Remote IO |
| WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, |
| " Starting AU Remote IO"); |
| OSStatus result = AudioOutputUnitStart(_auRemoteIO); |
| if (0 != result) { |
| WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice, _id, |
| " Error starting AU Remote IO (result=%d)", result); |
| return -1; |
| } |
| } |
| |
| _playing = true; |
| |
| return 0; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::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 AudioDeviceIPhone::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. |
| // ---------------------------------------------------------------------------- |
| WebRtc_Word32 AudioDeviceIPhone::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; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::PlayoutDelay(WebRtc_UWord16& delayMS) const { |
| delayMS = _playoutDelay; |
| return 0; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::RecordingDelay(WebRtc_UWord16& delayMS) const { |
| delayMS = _recordingDelay; |
| return 0; |
| } |
| |
| WebRtc_Word32 |
| AudioDeviceIPhone::SetPlayoutBuffer( |
| const AudioDeviceModule::BufferType type, |
| WebRtc_UWord16 sizeMS) { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, |
| "AudioDeviceIPhone::SetPlayoutBuffer(type=%u, sizeMS=%u)", |
| type, sizeMS); |
| |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| " API call not supported on this platform"); |
| return -1; |
| } |
| |
| WebRtc_Word32 |
| AudioDeviceIPhone::PlayoutBuffer(AudioDeviceModule::BufferType& type, |
| WebRtc_UWord16& sizeMS) const { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); |
| |
| type = AudioDeviceModule::kAdaptiveBufferSize; |
| |
| sizeMS = _playoutDelay; |
| |
| return 0; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::CPULoad(WebRtc_UWord16& /*load*/) const { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); |
| |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| " API call not supported on this platform"); |
| return -1; |
| } |
| |
| bool AudioDeviceIPhone::PlayoutWarning() const { |
| return (_playWarning > 0); |
| } |
| |
| bool AudioDeviceIPhone::PlayoutError() const { |
| return (_playError > 0); |
| } |
| |
| bool AudioDeviceIPhone::RecordingWarning() const { |
| return (_recWarning > 0); |
| } |
| |
| bool AudioDeviceIPhone::RecordingError() const { |
| return (_recError > 0); |
| } |
| |
| void AudioDeviceIPhone::ClearPlayoutWarning() { |
| _playWarning = 0; |
| } |
| |
| void AudioDeviceIPhone::ClearPlayoutError() { |
| _playError = 0; |
| } |
| |
| void AudioDeviceIPhone::ClearRecordingWarning() { |
| _recWarning = 0; |
| } |
| |
| void AudioDeviceIPhone::ClearRecordingError() { |
| _recError = 0; |
| } |
| |
| // ============================================================================ |
| // Private Methods |
| // ============================================================================ |
| |
| WebRtc_Word32 AudioDeviceIPhone::InitPlayOrRecord() { |
| WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); |
| |
| OSStatus result = -1; |
| |
| // Check if already initialized |
| if (NULL != _auRemoteIO) { |
| // 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 AU Remote IO |
| AudioComponentDescription desc; |
| AudioComponent comp; |
| |
| desc.componentType = kAudioUnitType_Output; |
| desc.componentSubType = kAudioUnitSubType_RemoteIO; |
| 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 AU Remote IO"); |
| return -1; |
| } |
| |
| result = AudioComponentInstanceNew(comp, &_auRemoteIO); |
| if (0 != result) { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " Could not create AU Remote IO instance (result=%d)", |
| result); |
| return -1; |
| } |
| |
| ////////////////////// |
| // Setup AU remote IO |
| |
| // Note: For AU Remote IO 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(_auRemoteIO, |
| 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(_auRemoteIO, |
| 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( |
| _auRemoteIO, 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 |
| } |
| |
| // Initialize here already to be able to get/set stream properties. |
| result = AudioUnitInitialize(_auRemoteIO); |
| if (0 != result) { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " Could not init AU Remote IO (result=%d)", result); |
| } |
| |
| // Get stream format for out/0 |
| AudioStreamBasicDescription playoutDesc; |
| UInt32 size = sizeof(playoutDesc); |
| result = AudioUnitGetProperty(_auRemoteIO, kAudioUnitProperty_StreamFormat, |
| kAudioUnitScope_Output, 0, &playoutDesc, |
| &size); |
| if (0 != result) { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " Could not get stream format AU Remote IO out/0 (result=%d)", |
| result); |
| } |
| WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, |
| " AU Remote IO playout opened in sampling rate %f", |
| playoutDesc.mSampleRate); |
| |
| // 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 = 44000; |
| } 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, |
| " AU Remote IO 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(_auRemoteIO, kAudioUnitProperty_StreamFormat, |
| kAudioUnitScope_Input, 0, &playoutDesc, size); |
| if (0 != result) { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " Could not set stream format AU Remote IO in/0 (result=%d)", |
| result); |
| } |
| |
| // Get stream format for in/1 |
| AudioStreamBasicDescription recordingDesc; |
| size = sizeof(recordingDesc); |
| result = AudioUnitGetProperty(_auRemoteIO, kAudioUnitProperty_StreamFormat, |
| kAudioUnitScope_Input, 1, &recordingDesc, |
| &size); |
| if (0 != result) { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " Could not get stream format AU Remote IO in/1 (result=%d)", |
| result); |
| } |
| WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, |
| " AU Remote IO recording opened in sampling rate %f", |
| recordingDesc.mSampleRate); |
| |
| if (static_cast<int>(playoutDesc.mSampleRate) |
| != static_cast<int>(recordingDesc.mSampleRate)) { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " AU Remote IO recording and playout opened " \ |
| "in different sampling rates"); |
| // todo: Bail out if rec and play sampling rates are not the same? |
| // Add handling of different sampling rates? |
| } |
| |
| // 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(_auRemoteIO, kAudioUnitProperty_StreamFormat, |
| kAudioUnitScope_Output, 1, &recordingDesc, |
| size); |
| if (0 != result) { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " Could not set stream format AU Remote IO out/1 (result=%d)", |
| result); |
| } |
| |
| // Set recording callback |
| AURenderCallbackStruct auCbS; |
| memset(&auCbS, 0, sizeof(auCbS)); |
| auCbS.inputProc = RecordProcess; |
| auCbS.inputProcRefCon = this; |
| result = AudioUnitSetProperty(_auRemoteIO, |
| kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, 1, |
| &auCbS, sizeof(auCbS)); |
| if (0 != result) { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " Could not set record callback for AU Remote IO (result=%d)", |
| result); |
| } |
| |
| // Set playout callback |
| memset(&auCbS, 0, sizeof(auCbS)); |
| auCbS.inputProc = PlayoutProcess; |
| auCbS.inputProcRefCon = this; |
| result = AudioUnitSetProperty(_auRemoteIO, |
| kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, 0, |
| &auCbS, sizeof(auCbS)); |
| if (0 != result) { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " Could not set play callback for AU Remote IO (result=%d)", |
| result); |
| } |
| |
| // Get hardware sample rate for logging (see if we get what we asked for) |
| Float64 sampleRate(0.0); |
| size = sizeof(sampleRate); |
| result = AudioSessionGetProperty( |
| kAudioSessionProperty_CurrentHardwareSampleRate, &size, &sampleRate); |
| if (0 != result) { |
| WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, |
| " Could not get current HW sample rate (result=%d)", result); |
| } |
| WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, |
| " Current HW sample rate is %f, ADB sample rate is %d", |
| sampleRate, _adbSampFreq); |
| |
| return 0; |
| } |
| |
| WebRtc_Word32 AudioDeviceIPhone::ShutdownPlayOrRecord() { |
| WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "%s", __FUNCTION__); |
| |
| // Close and delete AU |
| OSStatus result = -1; |
| if (NULL != _auRemoteIO) { |
| result = AudioOutputUnitStop(_auRemoteIO); |
| if (0 != result) { |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| " Error stopping AU Remote IO (result=%d)", result); |
| } |
| result = AudioUnitUninitialize(_auRemoteIO); |
| if (0 != result) { |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| " Error uninitializing AU Remote IO (result=%d)", result); |
| } |
| result = AudioComponentInstanceDispose(_auRemoteIO); |
| if (0 != result) { |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| " Error disposing AU Remote IO (result=%d)", result); |
| } |
| _auRemoteIO = NULL; |
| } |
| |
| return 0; |
| } |
| |
| // ============================================================================ |
| // Thread Methods |
| // ============================================================================ |
| |
| OSStatus |
| AudioDeviceIPhone::RecordProcess(void *inRefCon, |
| AudioUnitRenderActionFlags *ioActionFlags, |
| const AudioTimeStamp *inTimeStamp, |
| UInt32 inBusNumber, |
| UInt32 inNumberFrames, |
| AudioBufferList *ioData) { |
| AudioDeviceIPhone* ptrThis = static_cast<AudioDeviceIPhone*>(inRefCon); |
| |
| return ptrThis->RecordProcessImpl(ioActionFlags, |
| inTimeStamp, |
| inBusNumber, |
| inNumberFrames); |
| } |
| |
| |
| OSStatus |
| AudioDeviceIPhone::RecordProcessImpl( |
| AudioUnitRenderActionFlags *ioActionFlags, |
| const AudioTimeStamp *inTimeStamp, |
| WebRtc_UWord32 inBusNumber, |
| WebRtc_UWord32 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? |
| WebRtc_Word16* dataTmp = new WebRtc_Word16[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(_auRemoteIO, 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; |
| WebRtc_UWord16 bufPos = 0; |
| WebRtc_Word16 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<WebRtc_Word16>(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<WebRtc_Word16>(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(WebRtc_Word16)); |
| 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 |
| AudioDeviceIPhone::PlayoutProcess(void *inRefCon, |
| AudioUnitRenderActionFlags *ioActionFlags, |
| const AudioTimeStamp *inTimeStamp, |
| UInt32 inBusNumber, |
| UInt32 inNumberFrames, |
| AudioBufferList *ioData) { |
| AudioDeviceIPhone* ptrThis = static_cast<AudioDeviceIPhone*>(inRefCon); |
| |
| return ptrThis->PlayoutProcessImpl(inNumberFrames, ioData); |
| } |
| |
| OSStatus |
| AudioDeviceIPhone::PlayoutProcessImpl(WebRtc_UWord32 inNumberFrames, |
| AudioBufferList *ioData) { |
| // Setup some basic stuff |
| // assert(sizeof(short) == 2); // Assumption for implementation |
| |
| WebRtc_Word16* data = |
| static_cast<WebRtc_Word16*>(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 |
| WebRtc_Word16* dataTmp = new WebRtc_Word16[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<WebRtc_Word8*>(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 AudioDeviceIPhone::UpdatePlayoutDelay() { |
| ++_playoutDelayMeasurementCounter; |
| |
| if (_playoutDelayMeasurementCounter >= 100) { |
| // Update HW and OS delay every second, unlikely to change |
| |
| _playoutDelay = 0; |
| |
| // HW output latency |
| Float32 f32(0); |
| UInt32 size = sizeof(f32); |
| OSStatus result = AudioSessionGetProperty( |
| kAudioSessionProperty_CurrentHardwareOutputLatency, &size, &f32); |
| if (0 != result) { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| "error HW latency (result=%d)", result); |
| } |
| _playoutDelay += static_cast<int>(f32 * 1000000); |
| |
| // HW buffer duration |
| f32 = 0; |
| result = AudioSessionGetProperty( |
| kAudioSessionProperty_CurrentHardwareIOBufferDuration, &size, &f32); |
| if (0 != result) { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| "error HW buffer duration (result=%d)", result); |
| } |
| _playoutDelay += static_cast<int>(f32 * 1000000); |
| |
| // AU latency |
| Float64 f64(0); |
| size = sizeof(f64); |
| result = AudioUnitGetProperty(_auRemoteIO, |
| kAudioUnitProperty_Latency, kAudioUnitScope_Global, 0, &f64, &size); |
| if (0 != result) { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| "error AU latency (result=%d)", result); |
| } |
| _playoutDelay += static_cast<int>(f64 * 1000000); |
| |
| // To ms |
| _playoutDelay = (_playoutDelay - 500) / 1000; |
| |
| // Reset counter |
| _playoutDelayMeasurementCounter = 0; |
| } |
| |
| // todo: Add playout buffer? (Only used for 44.1 kHz) |
| } |
| |
| void AudioDeviceIPhone::UpdateRecordingDelay() { |
| ++_recordingDelayMeasurementCounter; |
| |
| if (_recordingDelayMeasurementCounter >= 100) { |
| // Update HW and OS delay every second, unlikely to change |
| |
| _recordingDelayHWAndOS = 0; |
| |
| // HW input latency |
| Float32 f32(0); |
| UInt32 size = sizeof(f32); |
| OSStatus result = AudioSessionGetProperty( |
| kAudioSessionProperty_CurrentHardwareInputLatency, &size, &f32); |
| if (0 != result) { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| "error HW latency (result=%d)", result); |
| } |
| _recordingDelayHWAndOS += static_cast<int>(f32 * 1000000); |
| |
| // HW buffer duration |
| f32 = 0; |
| result = AudioSessionGetProperty( |
| kAudioSessionProperty_CurrentHardwareIOBufferDuration, &size, &f32); |
| if (0 != result) { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| "error HW buffer duration (result=%d)", result); |
| } |
| _recordingDelayHWAndOS += static_cast<int>(f32 * 1000000); |
| |
| // AU latency |
| Float64 f64(0); |
| size = sizeof(f64); |
| result = AudioUnitGetProperty(_auRemoteIO, kAudioUnitProperty_Latency, |
| kAudioUnitScope_Global, 0, &f64, &size); |
| if (0 != result) { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| "error AU latency (result=%d)", result); |
| } |
| _recordingDelayHWAndOS += static_cast<int>(f64 * 1000000); |
| |
| // To ms |
| _recordingDelayHWAndOS = (_recordingDelayHWAndOS - 500) / 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 WebRtc_UWord32 noSamp10ms = _adbSampFreq / 100; |
| if (_recordingBufferTotalSize > noSamp10ms) { |
| _recordingDelay += |
| (_recordingBufferTotalSize - noSamp10ms) / (_adbSampFreq / 1000); |
| } |
| } |
| |
| bool AudioDeviceIPhone::RunCapture(void* ptrThis) { |
| return static_cast<AudioDeviceIPhone*>(ptrThis)->CaptureWorkerThread(); |
| } |
| |
| bool AudioDeviceIPhone::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<WebRtc_Word8*>( |
| _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; |
| } |
| |
| } // namespace webrtc |
| |