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 4390f4925832986d0cfa7a56349711e9febc8374..1c2fe829e68e0f2f7ee7ab8893d0079864b5de95 100644 |
--- a/webrtc/modules/audio_device/ios/audio_device_ios.mm |
+++ b/webrtc/modules/audio_device/ios/audio_device_ios.mm |
@@ -25,7 +25,9 @@ |
#include "webrtc/modules/audio_device/fine_audio_buffer.h" |
#include "webrtc/modules/utility/include/helpers_ios.h" |
+#import "webrtc/base/objc/RTCLogging.h" |
#import "webrtc/modules/audio_device/ios/objc/RTCAudioSession.h" |
+#import "webrtc/modules/audio_device/ios/objc/RTCAudioSessionConfiguration.h" |
namespace webrtc { |
@@ -48,38 +50,7 @@ namespace webrtc { |
} \ |
} while (0) |
-// Preferred hardware sample rate (unit is in Hertz). The client sample rate |
-// will be set to this value as well to avoid resampling the the audio unit's |
-// format converter. Note that, some devices, e.g. BT headsets, only supports |
-// 8000Hz as native sample rate. |
-const double kHighPerformanceSampleRate = 48000.0; |
-// A lower sample rate will be used for devices with only one core |
-// (e.g. iPhone 4). The goal is to reduce the CPU load of the application. |
-const double kLowComplexitySampleRate = 16000.0; |
-// Use a hardware I/O buffer size (unit is in seconds) that matches the 10ms |
-// size used by WebRTC. The exact actual size will differ between devices. |
-// Example: using 48kHz on iPhone 6 results in a native buffer size of |
-// ~10.6667ms or 512 audio frames per buffer. The FineAudioBuffer instance will |
-// take care of any buffering required to convert between native buffers and |
-// buffers used by WebRTC. It is beneficial for the performance if the native |
-// size is as close to 10ms as possible since it results in "clean" callback |
-// sequence without bursts of callbacks back to back. |
-const double kHighPerformanceIOBufferDuration = 0.01; |
-// Use a larger buffer size on devices with only one core (e.g. iPhone 4). |
-// It will result in a lower CPU consumption at the cost of a larger latency. |
-// The size of 60ms is based on instrumentation that shows a significant |
-// reduction in CPU load compared with 10ms on low-end devices. |
-// TODO(henrika): monitor this size and determine if it should be modified. |
-const double kLowComplexityIOBufferDuration = 0.06; |
-// Try to use mono to save resources. Also avoids channel format conversion |
-// in the I/O audio unit. Initial tests have shown that it is possible to use |
-// mono natively for built-in microphones and for BT headsets but not for |
-// wired headsets. Wired headsets only support stereo as native channel format |
-// but it is a low cost operation to do a format conversion to mono in the |
-// audio unit. Hence, we will not hit a RTC_CHECK in |
-// VerifyAudioParametersForActiveAudioSession() for a mismatch between the |
-// preferred number of channels and the actual number of channels. |
-const int kPreferredNumberOfChannels = 1; |
+ |
// Number of bytes per audio sample for 16-bit signed integer representation. |
const UInt32 kBytesPerSample = 2; |
// Hardcoded delay estimates based on real measurements. |
@@ -95,149 +66,6 @@ const int kMaxNumberOfAudioUnitInitializeAttempts = 5; |
using ios::CheckAndLogError; |
-// Return the preferred sample rate given number of CPU cores. Use highest |
-// possible if the CPU has more than one core. |
-static double GetPreferredSampleRate() { |
- return (ios::GetProcessorCount() > 1) ? kHighPerformanceSampleRate |
- : kLowComplexitySampleRate; |
-} |
- |
-// Return the preferred I/O buffer size given number of CPU cores. Use smallest |
-// possible if the CPU has more than one core. |
-static double GetPreferredIOBufferDuration() { |
- return (ios::GetProcessorCount() > 1) ? kHighPerformanceIOBufferDuration |
- : kLowComplexityIOBufferDuration; |
-} |
- |
-// Verifies that the current audio session supports input audio and that the |
-// required category and mode are enabled. |
-static bool VerifyAudioSession(RTCAudioSession* session) { |
- LOG(LS_INFO) << "VerifyAudioSession"; |
- // Ensure that the device currently supports audio input. |
- if (!session.inputAvailable) { |
- LOG(LS_ERROR) << "No audio input path is available!"; |
- return false; |
- } |
- |
- // Ensure that the required category and mode are actually activated. |
- if (![session.category isEqualToString:AVAudioSessionCategoryPlayAndRecord]) { |
- LOG(LS_ERROR) |
- << "Failed to set category to AVAudioSessionCategoryPlayAndRecord"; |
- return false; |
- } |
- if (![session.mode isEqualToString:AVAudioSessionModeVoiceChat]) { |
- LOG(LS_ERROR) << "Failed to set mode to AVAudioSessionModeVoiceChat"; |
- return false; |
- } |
- return true; |
-} |
- |
-// Activates an audio session suitable for full duplex VoIP sessions when |
-// |activate| is true. Also sets the preferred sample rate and IO buffer |
-// duration. Deactivates an active audio session if |activate| is set to false. |
-static bool ActivateAudioSession(RTCAudioSession* session, bool activate) { |
- LOG(LS_INFO) << "ActivateAudioSession(" << activate << ")"; |
- |
- NSError* error = nil; |
- BOOL success = NO; |
- |
- [session lockForConfiguration]; |
- if (!activate) { |
- success = [session setActive:NO |
- error:&error]; |
- [session unlockForConfiguration]; |
- return CheckAndLogError(success, error); |
- } |
- |
- // Go ahead and active our own audio session since |activate| is true. |
- // Use a category which supports simultaneous recording and playback. |
- // By default, using this category implies that our app’s audio is |
- // nonmixable, hence activating the session will interrupt any other |
- // audio sessions which are also nonmixable. |
- if (session.category != AVAudioSessionCategoryPlayAndRecord) { |
- error = nil; |
- success = [session setCategory:AVAudioSessionCategoryPlayAndRecord |
- withOptions:AVAudioSessionCategoryOptionAllowBluetooth |
- error:&error]; |
- RTC_DCHECK(CheckAndLogError(success, error)); |
- } |
- |
- // Specify mode for two-way voice communication (e.g. VoIP). |
- if (session.mode != AVAudioSessionModeVoiceChat) { |
- error = nil; |
- success = [session setMode:AVAudioSessionModeVoiceChat error:&error]; |
- RTC_DCHECK(CheckAndLogError(success, error)); |
- } |
- |
- // Set the session's sample rate or the hardware sample rate. |
- // It is essential that we use the same sample rate as stream format |
- // to ensure that the I/O unit does not have to do sample rate conversion. |
- error = nil; |
- success = |
- [session setPreferredSampleRate:GetPreferredSampleRate() error:&error]; |
- RTC_DCHECK(CheckAndLogError(success, error)); |
- |
- // Set the preferred audio I/O buffer duration, in seconds. |
- error = nil; |
- success = [session setPreferredIOBufferDuration:GetPreferredIOBufferDuration() |
- error:&error]; |
- RTC_DCHECK(CheckAndLogError(success, error)); |
- |
- // Activate the audio session. Activation can fail if another active audio |
- // session (e.g. phone call) has higher priority than ours. |
- error = nil; |
- success = [session setActive:YES error:&error]; |
- if (!CheckAndLogError(success, error)) { |
- [session unlockForConfiguration]; |
- return false; |
- } |
- |
- // Ensure that the active audio session has the correct category and mode. |
- if (!VerifyAudioSession(session)) { |
- LOG(LS_ERROR) << "Failed to verify audio session category and mode"; |
- [session unlockForConfiguration]; |
- return false; |
- } |
- |
- // Try to set the preferred number of hardware audio channels. These calls |
- // must be done after setting the audio session’s category and mode and |
- // activating the session. |
- // We try to use mono in both directions to save resources and format |
- // conversions in the audio unit. Some devices does only support stereo; |
- // e.g. wired headset on iPhone 6. |
- // TODO(henrika): add support for stereo if needed. |
- error = nil; |
- success = |
- [session setPreferredInputNumberOfChannels:kPreferredNumberOfChannels |
- error:&error]; |
- RTC_DCHECK(CheckAndLogError(success, error)); |
- error = nil; |
- success = |
- [session setPreferredOutputNumberOfChannels:kPreferredNumberOfChannels |
- error:&error]; |
- RTC_DCHECK(CheckAndLogError(success, error)); |
- [session unlockForConfiguration]; |
- return true; |
-} |
- |
-// An application can create more than one ADM and start audio streaming |
-// for all of them. It is essential that we only activate the app's audio |
-// session once (for the first one) and deactivate it once (for the last). |
-static bool ActivateAudioSession() { |
- LOGI() << "ActivateAudioSession"; |
- RTCAudioSession* session = [RTCAudioSession sharedInstance]; |
- return ActivateAudioSession(session, true); |
-} |
- |
-// If more than one object is using the audio session, ensure that only the |
-// last object deactivates. Apple recommends: "activate your audio session |
-// only as needed and deactivate it when you are not using audio". |
-static bool DeactivateAudioSession() { |
- LOGI() << "DeactivateAudioSession"; |
- RTCAudioSession* session = [RTCAudioSession sharedInstance]; |
- return ActivateAudioSession(session, false); |
-} |
- |
#if !defined(NDEBUG) |
// Helper method for printing out an AudioStreamBasicDescription structure. |
static void LogABSD(AudioStreamBasicDescription absd) { |
@@ -313,13 +141,15 @@ int32_t AudioDeviceIOS::Init() { |
LogDeviceInfo(); |
#endif |
// Store the preferred sample rate and preferred number of channels already |
- // here. They have not been set and confirmed yet since ActivateAudioSession() |
+ // here. They have not been set and confirmed yet since configureForWebRTC |
// is not called until audio is about to start. However, it makes sense to |
// store the parameters now and then verify at a later stage. |
- playout_parameters_.reset(GetPreferredSampleRate(), |
- kPreferredNumberOfChannels); |
- record_parameters_.reset(GetPreferredSampleRate(), |
- kPreferredNumberOfChannels); |
+ RTCAudioSessionConfiguration* config = |
+ [RTCAudioSessionConfiguration webRTCConfiguration]; |
+ playout_parameters_.reset(config.sampleRate, |
+ config.outputNumberOfChannels); |
+ record_parameters_.reset(config.sampleRate, |
+ config.inputNumberOfChannels); |
// Ensure that the audio device buffer (ADB) knows about the internal audio |
// parameters. Note that, even if we are unable to get a mono audio session, |
// we will always tell the I/O audio unit to do a channel format conversion |
@@ -673,7 +503,9 @@ void AudioDeviceIOS::SetupAudioBuffersForActiveAudioSession() { |
// hardware sample rate but continue and use the non-ideal sample rate after |
// reinitializing the audio parameters. Most BT headsets only support 8kHz or |
// 16kHz. |
- if (session.sampleRate != GetPreferredSampleRate()) { |
+ RTCAudioSessionConfiguration* webRTCConfig = |
+ [RTCAudioSessionConfiguration webRTCConfiguration]; |
+ if (session.sampleRate != webRTCConfig.sampleRate) { |
LOG(LS_WARNING) << "Unable to set the preferred sample rate"; |
} |
@@ -791,7 +623,7 @@ bool AudioDeviceIOS::SetupAndInitializeVoiceProcessingAudioUnit() { |
UInt32 size = sizeof(application_format); |
RTC_DCHECK_EQ(playout_parameters_.sample_rate(), |
record_parameters_.sample_rate()); |
- RTC_DCHECK_EQ(1, kPreferredNumberOfChannels); |
+ RTC_DCHECK_EQ(1, kRTCAudioSessionPreferredNumberOfChannels); |
application_format.mSampleRate = playout_parameters_.sample_rate(); |
application_format.mFormatID = kAudioFormatLinearPCM; |
application_format.mFormatFlags = |
@@ -799,7 +631,8 @@ bool AudioDeviceIOS::SetupAndInitializeVoiceProcessingAudioUnit() { |
application_format.mBytesPerPacket = kBytesPerSample; |
application_format.mFramesPerPacket = 1; // uncompressed |
application_format.mBytesPerFrame = kBytesPerSample; |
- application_format.mChannelsPerFrame = kPreferredNumberOfChannels; |
+ application_format.mChannelsPerFrame = |
+ kRTCAudioSessionPreferredNumberOfChannels; |
application_format.mBitsPerChannel = 8 * kBytesPerSample; |
// Store the new format. |
application_format_ = application_format; |
@@ -937,16 +770,16 @@ bool AudioDeviceIOS::RestartAudioUnitWithNewFormat(float sample_rate) { |
bool AudioDeviceIOS::InitPlayOrRecord() { |
LOGI() << "InitPlayOrRecord"; |
- // Activate the audio session if not already activated. |
- if (!ActivateAudioSession()) { |
- return false; |
- } |
- // Ensure that the active audio session has the correct category and mode. |
+ // Use the correct audio session configuration for WebRTC. |
+ // This will attempt to activate the audio session. |
RTCAudioSession* session = [RTCAudioSession sharedInstance]; |
- if (!VerifyAudioSession(session)) { |
- DeactivateAudioSession(); |
- LOG(LS_ERROR) << "Failed to verify audio session category and mode"; |
+ [session lockForConfiguration]; |
+ NSError* error = nil; |
+ if (![session configureWebRTCSession:&error]) { |
+ RTCLogError(@"Failed to configure WebRTC session: %@", |
+ error.localizedDescription); |
+ [session unlockForConfiguration]; |
return false; |
} |
@@ -958,11 +791,11 @@ bool AudioDeviceIOS::InitPlayOrRecord() { |
// Create, setup and initialize a new Voice-Processing I/O unit. |
if (!SetupAndInitializeVoiceProcessingAudioUnit()) { |
- // Reduce usage count for the audio session and possibly deactivate it if |
- // this object is the only user. |
- DeactivateAudioSession(); |
+ [session setActive:NO error:nil]; |
+ [session unlockForConfiguration]; |
return false; |
} |
+ [session unlockForConfiguration]; |
return true; |
} |
@@ -987,7 +820,10 @@ void AudioDeviceIOS::ShutdownPlayOrRecord() { |
// All I/O should be stopped or paused prior to deactivating the audio |
// session, hence we deactivate as last action. |
- DeactivateAudioSession(); |
+ RTCAudioSession* session = [RTCAudioSession sharedInstance]; |
+ [session lockForConfiguration]; |
+ [session setActive:NO error:nil]; |
+ [session unlockForConfiguration]; |
} |
void AudioDeviceIOS::DisposeAudioUnit() { |