Files
platform-external-webrtc/webrtc/modules/audio_device/ios/audio_device_ios.mm
Henrik Kjellander ff761fba82 modules: more interface -> include renames
This changes the following module directories:
* webrtc/modules/audio_conference_mixer/interface
* webrtc/modules/interface
* webrtc/modules/media_file/interface
* webrtc/modules/rtp_rtcp/interface
* webrtc/modules/utility/interface

To avoid breaking downstream, I followed this recipe:
1. Copy the interface dir to a new sibling directory: include
2. Update the header guards in the include directory to match the style guide.
3. Update the header guards in the interface directory to match the ones in include. This is required to avoid getting redefinitions in the not-yet-updated downstream code.
4. Add a pragma warning in the header files in the interface dir. Example:
#pragma message("WARNING: webrtc/modules/interface is DEPRECATED; "
                "use webrtc/modules/include")
5. Search for all source references to webrtc/modules/interface and update them to webrtc/modules/include (*.c*,*.h,*.mm,*.S)
6. Update all GYP+GN files. This required manual inspection since many subdirectories of webrtc/modules referenced the interface dir using ../interface etc(*.gyp*,*.gn*)

BUG=5095
TESTED=Passing compile-trybots with --clobber flag:
git cl try --clobber --bot=win_compile_rel --bot=linux_compile_rel --bot=android_compile_rel --bot=mac_compile_rel --bot=ios_rel -m tryserver.webrtc

R=stefan@webrtc.org, tommi@webrtc.org

Review URL: https://codereview.webrtc.org/1417683006 .

Cr-Commit-Position: refs/heads/master@{#10500}
2015-11-04 07:32:04 +00:00

927 lines
38 KiB
Plaintext

/*
* Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
#import <AVFoundation/AVFoundation.h>
#import <Foundation/Foundation.h>
#include "webrtc/modules/audio_device/ios/audio_device_ios.h"
#include "webrtc/base/atomicops.h"
#include "webrtc/base/checks.h"
#include "webrtc/base/logging.h"
#include "webrtc/modules/audio_device/fine_audio_buffer.h"
#include "webrtc/modules/utility/include/helpers_ios.h"
namespace webrtc {
#define LOGI() LOG(LS_INFO) << "AudioDeviceIOS::"
#define LOG_AND_RETURN_IF_ERROR(error, message) \
do { \
OSStatus err = error; \
if (err) { \
LOG(LS_ERROR) << message << ": " << err; \
return false; \
} \
} while (0)
#define LOG_IF_ERROR(error, message) \
do { \
OSStatus err = error; \
if (err) { \
LOG(LS_ERROR) << message << ": " << err; \
} \
} 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 kPreferredSampleRate = 48000.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 kPreferredIOBufferDuration = 0.01;
// 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.
// TODO(henrika): these value is not used in combination with built-in AEC.
// Can most likely be removed.
const UInt16 kFixedPlayoutDelayEstimate = 30;
const UInt16 kFixedRecordDelayEstimate = 30;
using ios::CheckAndLogError;
// 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 void ActivateAudioSession(AVAudioSession* session, bool activate) {
LOG(LS_INFO) << "ActivateAudioSession(" << activate << ")";
@autoreleasepool {
NSError* error = nil;
BOOL success = NO;
// Deactivate the audio session and return if |activate| is false.
if (!activate) {
success = [session setActive:NO error:&error];
RTC_DCHECK(CheckAndLogError(success, error));
return;
}
// 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:kPreferredSampleRate error:&error];
RTC_DCHECK(CheckAndLogError(success, error));
// Set the preferred audio I/O buffer duration, in seconds.
// TODO(henrika): add more comments here.
error = nil;
success = [session setPreferredIOBufferDuration:kPreferredIOBufferDuration
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];
RTC_DCHECK(CheckAndLogError(success, error));
RTC_CHECK(session.isInputAvailable) << "No input path is available!";
// Ensure that category and mode are actually activated.
RTC_DCHECK(
[session.category isEqualToString:AVAudioSessionCategoryPlayAndRecord]);
RTC_DCHECK([session.mode isEqualToString:AVAudioSessionModeVoiceChat]);
// 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));
}
}
#if !defined(NDEBUG)
// Helper method for printing out an AudioStreamBasicDescription structure.
static void LogABSD(AudioStreamBasicDescription absd) {
char formatIDString[5];
UInt32 formatID = CFSwapInt32HostToBig(absd.mFormatID);
bcopy(&formatID, formatIDString, 4);
formatIDString[4] = '\0';
LOG(LS_INFO) << "LogABSD";
LOG(LS_INFO) << " sample rate: " << absd.mSampleRate;
LOG(LS_INFO) << " format ID: " << formatIDString;
LOG(LS_INFO) << " format flags: " << std::hex << absd.mFormatFlags;
LOG(LS_INFO) << " bytes per packet: " << absd.mBytesPerPacket;
LOG(LS_INFO) << " frames per packet: " << absd.mFramesPerPacket;
LOG(LS_INFO) << " bytes per frame: " << absd.mBytesPerFrame;
LOG(LS_INFO) << " channels per packet: " << absd.mChannelsPerFrame;
LOG(LS_INFO) << " bits per channel: " << absd.mBitsPerChannel;
LOG(LS_INFO) << " reserved: " << absd.mReserved;
}
// Helper method that logs essential device information strings.
static void LogDeviceInfo() {
LOG(LS_INFO) << "LogDeviceInfo";
@autoreleasepool {
LOG(LS_INFO) << " system name: " << ios::GetSystemName();
LOG(LS_INFO) << " system version: " << ios::GetSystemVersion();
LOG(LS_INFO) << " device type: " << ios::GetDeviceType();
LOG(LS_INFO) << " device name: " << ios::GetDeviceName();
}
}
#endif // !defined(NDEBUG)
AudioDeviceIOS::AudioDeviceIOS()
: audio_device_buffer_(nullptr),
vpio_unit_(nullptr),
recording_(0),
playing_(0),
initialized_(false),
rec_is_initialized_(false),
play_is_initialized_(false),
audio_interruption_observer_(nullptr) {
LOGI() << "ctor" << ios::GetCurrentThreadDescription();
}
AudioDeviceIOS::~AudioDeviceIOS() {
LOGI() << "~dtor";
RTC_DCHECK(thread_checker_.CalledOnValidThread());
Terminate();
}
void AudioDeviceIOS::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) {
LOGI() << "AttachAudioBuffer";
RTC_DCHECK(audioBuffer);
RTC_DCHECK(thread_checker_.CalledOnValidThread());
audio_device_buffer_ = audioBuffer;
}
int32_t AudioDeviceIOS::Init() {
LOGI() << "Init";
RTC_DCHECK(thread_checker_.CalledOnValidThread());
if (initialized_) {
return 0;
}
#if !defined(NDEBUG)
LogDeviceInfo();
#endif
// Store the preferred sample rate and preferred number of channels already
// here. They have not been set and confirmed yet since ActivateAudioSession()
// 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(kPreferredSampleRate, kPreferredNumberOfChannels);
record_parameters_.reset(kPreferredSampleRate, kPreferredNumberOfChannels);
// 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
// to guarantee mono on the "input side" of the audio unit.
UpdateAudioDeviceBuffer();
initialized_ = true;
return 0;
}
int32_t AudioDeviceIOS::Terminate() {
LOGI() << "Terminate";
RTC_DCHECK(thread_checker_.CalledOnValidThread());
if (!initialized_) {
return 0;
}
ShutdownPlayOrRecord();
initialized_ = false;
return 0;
}
int32_t AudioDeviceIOS::InitPlayout() {
LOGI() << "InitPlayout";
RTC_DCHECK(thread_checker_.CalledOnValidThread());
RTC_DCHECK(initialized_);
RTC_DCHECK(!play_is_initialized_);
RTC_DCHECK(!playing_);
if (!rec_is_initialized_) {
if (!InitPlayOrRecord()) {
LOG_F(LS_ERROR) << "InitPlayOrRecord failed!";
return -1;
}
}
play_is_initialized_ = true;
return 0;
}
int32_t AudioDeviceIOS::InitRecording() {
LOGI() << "InitRecording";
RTC_DCHECK(thread_checker_.CalledOnValidThread());
RTC_DCHECK(initialized_);
RTC_DCHECK(!rec_is_initialized_);
RTC_DCHECK(!recording_);
if (!play_is_initialized_) {
if (!InitPlayOrRecord()) {
LOG_F(LS_ERROR) << "InitPlayOrRecord failed!";
return -1;
}
}
rec_is_initialized_ = true;
return 0;
}
int32_t AudioDeviceIOS::StartPlayout() {
LOGI() << "StartPlayout";
RTC_DCHECK(thread_checker_.CalledOnValidThread());
RTC_DCHECK(play_is_initialized_);
RTC_DCHECK(!playing_);
fine_audio_buffer_->ResetPlayout();
if (!recording_) {
OSStatus result = AudioOutputUnitStart(vpio_unit_);
if (result != noErr) {
LOG_F(LS_ERROR) << "AudioOutputUnitStart failed: " << result;
return -1;
}
}
rtc::AtomicOps::ReleaseStore(&playing_, 1);
return 0;
}
int32_t AudioDeviceIOS::StopPlayout() {
LOGI() << "StopPlayout";
RTC_DCHECK(thread_checker_.CalledOnValidThread());
if (!play_is_initialized_ || !playing_) {
return 0;
}
if (!recording_) {
ShutdownPlayOrRecord();
}
play_is_initialized_ = false;
rtc::AtomicOps::ReleaseStore(&playing_, 0);
return 0;
}
int32_t AudioDeviceIOS::StartRecording() {
LOGI() << "StartRecording";
RTC_DCHECK(thread_checker_.CalledOnValidThread());
RTC_DCHECK(rec_is_initialized_);
RTC_DCHECK(!recording_);
fine_audio_buffer_->ResetRecord();
if (!playing_) {
OSStatus result = AudioOutputUnitStart(vpio_unit_);
if (result != noErr) {
LOG_F(LS_ERROR) << "AudioOutputUnitStart failed: " << result;
return -1;
}
}
rtc::AtomicOps::ReleaseStore(&recording_, 1);
return 0;
}
int32_t AudioDeviceIOS::StopRecording() {
LOGI() << "StopRecording";
RTC_DCHECK(thread_checker_.CalledOnValidThread());
if (!rec_is_initialized_ || !recording_) {
return 0;
}
if (!playing_) {
ShutdownPlayOrRecord();
}
rec_is_initialized_ = false;
rtc::AtomicOps::ReleaseStore(&recording_, 0);
return 0;
}
// Change the default receiver playout route to speaker.
int32_t AudioDeviceIOS::SetLoudspeakerStatus(bool enable) {
LOGI() << "SetLoudspeakerStatus(" << enable << ")";
AVAudioSession* session = [AVAudioSession sharedInstance];
NSString* category = session.category;
AVAudioSessionCategoryOptions options = session.categoryOptions;
// Respect old category options if category is
// AVAudioSessionCategoryPlayAndRecord. Otherwise reset it since old options
// might not be valid for this category.
if ([category isEqualToString:AVAudioSessionCategoryPlayAndRecord]) {
if (enable) {
options |= AVAudioSessionCategoryOptionDefaultToSpeaker;
} else {
options &= ~AVAudioSessionCategoryOptionDefaultToSpeaker;
}
} else {
options = AVAudioSessionCategoryOptionDefaultToSpeaker;
}
NSError* error = nil;
BOOL success = [session setCategory:AVAudioSessionCategoryPlayAndRecord
withOptions:options
error:&error];
ios::CheckAndLogError(success, error);
return (error == nil) ? 0 : -1;
}
int32_t AudioDeviceIOS::GetLoudspeakerStatus(bool& enabled) const {
LOGI() << "GetLoudspeakerStatus";
AVAudioSession* session = [AVAudioSession sharedInstance];
AVAudioSessionCategoryOptions options = session.categoryOptions;
enabled = options & AVAudioSessionCategoryOptionDefaultToSpeaker;
return 0;
}
int32_t AudioDeviceIOS::PlayoutDelay(uint16_t& delayMS) const {
delayMS = kFixedPlayoutDelayEstimate;
return 0;
}
int32_t AudioDeviceIOS::RecordingDelay(uint16_t& delayMS) const {
delayMS = kFixedRecordDelayEstimate;
return 0;
}
int AudioDeviceIOS::GetPlayoutAudioParameters(AudioParameters* params) const {
LOGI() << "GetPlayoutAudioParameters";
RTC_DCHECK(playout_parameters_.is_valid());
RTC_DCHECK(thread_checker_.CalledOnValidThread());
*params = playout_parameters_;
return 0;
}
int AudioDeviceIOS::GetRecordAudioParameters(AudioParameters* params) const {
LOGI() << "GetRecordAudioParameters";
RTC_DCHECK(record_parameters_.is_valid());
RTC_DCHECK(thread_checker_.CalledOnValidThread());
*params = record_parameters_;
return 0;
}
void AudioDeviceIOS::UpdateAudioDeviceBuffer() {
LOGI() << "UpdateAudioDevicebuffer";
// AttachAudioBuffer() is called at construction by the main class but check
// just in case.
RTC_DCHECK(audio_device_buffer_) << "AttachAudioBuffer must be called first";
// Inform the audio device buffer (ADB) about the new audio format.
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());
}
void AudioDeviceIOS::RegisterNotificationObservers() {
LOGI() << "RegisterNotificationObservers";
// This code block will be called when AVAudioSessionInterruptionNotification
// is observed.
void (^interrupt_block)(NSNotification*) = ^(NSNotification* notification) {
NSNumber* type_number =
notification.userInfo[AVAudioSessionInterruptionTypeKey];
AVAudioSessionInterruptionType type =
(AVAudioSessionInterruptionType)type_number.unsignedIntegerValue;
LOG(LS_INFO) << "Audio session interruption:";
switch (type) {
case AVAudioSessionInterruptionTypeBegan:
// The system has deactivated our audio session.
// Stop the active audio unit.
LOG(LS_INFO) << " Began => stopping the audio unit";
LOG_IF_ERROR(AudioOutputUnitStop(vpio_unit_),
"Failed to stop the the Voice-Processing I/O unit");
break;
case AVAudioSessionInterruptionTypeEnded:
// The interruption has ended. Restart the audio session and start the
// initialized audio unit again.
LOG(LS_INFO) << " Ended => restarting audio session and audio unit";
NSError* error = nil;
BOOL success = NO;
AVAudioSession* session = [AVAudioSession sharedInstance];
success = [session setActive:YES error:&error];
if (CheckAndLogError(success, error)) {
LOG_IF_ERROR(AudioOutputUnitStart(vpio_unit_),
"Failed to start the the Voice-Processing I/O unit");
}
break;
}
};
// This code block will be called when AVAudioSessionRouteChangeNotification
// is observed.
void (^route_change_block)(NSNotification*) =
^(NSNotification* notification) {
// Get reason for current route change.
NSNumber* reason_number =
notification.userInfo[AVAudioSessionRouteChangeReasonKey];
AVAudioSessionRouteChangeReason reason =
(AVAudioSessionRouteChangeReason)reason_number.unsignedIntegerValue;
bool valid_route_change = true;
LOG(LS_INFO) << "Route change:";
switch (reason) {
case AVAudioSessionRouteChangeReasonUnknown:
LOG(LS_INFO) << " ReasonUnknown";
break;
case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
LOG(LS_INFO) << " NewDeviceAvailable";
break;
case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
LOG(LS_INFO) << " OldDeviceUnavailable";
break;
case AVAudioSessionRouteChangeReasonCategoryChange:
LOG(LS_INFO) << " CategoryChange";
LOG(LS_INFO) << " New category: " << ios::GetAudioSessionCategory();
// Don't see this as route change since it can be triggered in
// combination with session interruptions as well.
valid_route_change = false;
break;
case AVAudioSessionRouteChangeReasonOverride:
LOG(LS_INFO) << " Override";
break;
case AVAudioSessionRouteChangeReasonWakeFromSleep:
LOG(LS_INFO) << " WakeFromSleep";
break;
case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory:
LOG(LS_INFO) << " NoSuitableRouteForCategory";
break;
case AVAudioSessionRouteChangeReasonRouteConfigurationChange:
// Ignore this type of route change since we are focusing
// on detecting headset changes.
LOG(LS_INFO) << " RouteConfigurationChange";
valid_route_change = false;
break;
}
if (valid_route_change) {
// Log previous route configuration.
AVAudioSessionRouteDescription* prev_route =
notification.userInfo[AVAudioSessionRouteChangePreviousRouteKey];
LOG(LS_INFO) << "Previous route:";
LOG(LS_INFO) << ios::StdStringFromNSString(
[NSString stringWithFormat:@"%@", prev_route]);
// Only restart audio for a valid route change and if the
// session sample rate has changed.
AVAudioSession* session = [AVAudioSession sharedInstance];
const double session_sample_rate = session.sampleRate;
LOG(LS_INFO) << "session sample rate: " << session_sample_rate;
if (playout_parameters_.sample_rate() != session_sample_rate) {
if (!RestartAudioUnitWithNewFormat(session_sample_rate)) {
LOG(LS_ERROR) << "Audio restart failed";
}
}
}
};
// Get the default notification center of the current process.
NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
// Add AVAudioSessionInterruptionNotification observer.
id interruption_observer =
[center addObserverForName:AVAudioSessionInterruptionNotification
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:interrupt_block];
// Add AVAudioSessionRouteChangeNotification observer.
id route_change_observer =
[center addObserverForName:AVAudioSessionRouteChangeNotification
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:route_change_block];
// Increment refcount on observers using ARC bridge. Instance variable is a
// void* instead of an id because header is included in other pure C++
// files.
audio_interruption_observer_ = (__bridge_retained void*)interruption_observer;
route_change_observer_ = (__bridge_retained void*)route_change_observer;
}
void AudioDeviceIOS::UnregisterNotificationObservers() {
LOGI() << "UnregisterNotificationObservers";
// Transfer ownership of observer back to ARC, which will deallocate the
// observer once it exits this scope.
NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
if (audio_interruption_observer_ != nullptr) {
id observer = (__bridge_transfer id)audio_interruption_observer_;
[center removeObserver:observer];
audio_interruption_observer_ = nullptr;
}
if (route_change_observer_ != nullptr) {
id observer = (__bridge_transfer id)route_change_observer_;
[center removeObserver:observer];
route_change_observer_ = nullptr;
}
}
void AudioDeviceIOS::SetupAudioBuffersForActiveAudioSession() {
LOGI() << "SetupAudioBuffersForActiveAudioSession";
// Verify the current values once the audio session has been activated.
AVAudioSession* session = [AVAudioSession sharedInstance];
LOG(LS_INFO) << " sample rate: " << session.sampleRate;
LOG(LS_INFO) << " IO buffer duration: " << session.IOBufferDuration;
LOG(LS_INFO) << " output channels: " << session.outputNumberOfChannels;
LOG(LS_INFO) << " input channels: " << session.inputNumberOfChannels;
LOG(LS_INFO) << " output latency: " << session.outputLatency;
LOG(LS_INFO) << " input latency: " << session.inputLatency;
// Log a warning message for the case when we are unable to set the preferred
// 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 != kPreferredSampleRate) {
LOG(LS_WARNING) << "Unable to set the preferred sample rate";
}
// At this stage, we also know the exact IO buffer duration and can add
// that info to the existing audio parameters where it is converted into
// number of audio frames.
// Example: IO buffer size = 0.008 seconds <=> 128 audio frames at 16kHz.
// Hence, 128 is the size we expect to see in upcoming render callbacks.
playout_parameters_.reset(session.sampleRate, playout_parameters_.channels(),
session.IOBufferDuration);
RTC_DCHECK(playout_parameters_.is_complete());
record_parameters_.reset(session.sampleRate, record_parameters_.channels(),
session.IOBufferDuration);
RTC_DCHECK(record_parameters_.is_complete());
LOG(LS_INFO) << " frames per I/O buffer: "
<< playout_parameters_.frames_per_buffer();
LOG(LS_INFO) << " bytes per I/O buffer: "
<< playout_parameters_.GetBytesPerBuffer();
RTC_DCHECK_EQ(playout_parameters_.GetBytesPerBuffer(),
record_parameters_.GetBytesPerBuffer());
// Update the ADB parameters since the sample rate might have changed.
UpdateAudioDeviceBuffer();
// Create a modified audio buffer class which allows us to ask for,
// or deliver, any number of samples (and not only multiple of 10ms) to match
// the native audio unit buffer size.
RTC_DCHECK(audio_device_buffer_);
fine_audio_buffer_.reset(new FineAudioBuffer(
audio_device_buffer_, playout_parameters_.GetBytesPerBuffer(),
playout_parameters_.sample_rate()));
// The extra/temporary playoutbuffer must be of this size to avoid
// unnecessary memcpy while caching data between successive callbacks.
const int required_playout_buffer_size =
fine_audio_buffer_->RequiredPlayoutBufferSizeBytes();
LOG(LS_INFO) << " required playout buffer size: "
<< required_playout_buffer_size;
playout_audio_buffer_.reset(new SInt8[required_playout_buffer_size]);
// Allocate AudioBuffers to be used as storage for the received audio.
// The AudioBufferList structure works as a placeholder for the
// AudioBuffer structure, which holds a pointer to the actual data buffer
// in |record_audio_buffer_|. Recorded audio will be rendered into this memory
// at each input callback when calling AudioUnitRender().
const int data_byte_size = record_parameters_.GetBytesPerBuffer();
record_audio_buffer_.reset(new SInt8[data_byte_size]);
audio_record_buffer_list_.mNumberBuffers = 1;
AudioBuffer* audio_buffer = &audio_record_buffer_list_.mBuffers[0];
audio_buffer->mNumberChannels = record_parameters_.channels();
audio_buffer->mDataByteSize = data_byte_size;
audio_buffer->mData = record_audio_buffer_.get();
}
bool AudioDeviceIOS::SetupAndInitializeVoiceProcessingAudioUnit() {
LOGI() << "SetupAndInitializeVoiceProcessingAudioUnit";
RTC_DCHECK(!vpio_unit_);
// Create an audio component description to identify the Voice-Processing
// I/O audio unit.
AudioComponentDescription vpio_unit_description;
vpio_unit_description.componentType = kAudioUnitType_Output;
vpio_unit_description.componentSubType = kAudioUnitSubType_VoiceProcessingIO;
vpio_unit_description.componentManufacturer = kAudioUnitManufacturer_Apple;
vpio_unit_description.componentFlags = 0;
vpio_unit_description.componentFlagsMask = 0;
// Obtain an audio unit instance given the description.
AudioComponent found_vpio_unit_ref =
AudioComponentFindNext(nullptr, &vpio_unit_description);
// Create a Voice-Processing IO audio unit.
LOG_AND_RETURN_IF_ERROR(
AudioComponentInstanceNew(found_vpio_unit_ref, &vpio_unit_),
"Failed to create a VoiceProcessingIO audio unit");
// A VP I/O unit's bus 1 connects to input hardware (microphone). Enable
// input on the input scope of the input element.
AudioUnitElement input_bus = 1;
UInt32 enable_input = 1;
LOG_AND_RETURN_IF_ERROR(
AudioUnitSetProperty(vpio_unit_, kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Input, input_bus, &enable_input,
sizeof(enable_input)),
"Failed to enable input on input scope of input element");
// A VP I/O unit's bus 0 connects to output hardware (speaker). Enable
// output on the output scope of the output element.
AudioUnitElement output_bus = 0;
UInt32 enable_output = 1;
LOG_AND_RETURN_IF_ERROR(
AudioUnitSetProperty(vpio_unit_, kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Output, output_bus, &enable_output,
sizeof(enable_output)),
"Failed to enable output on output scope of output element");
// Set the application formats for input and output:
// - use same format in both directions
// - avoid resampling in the I/O unit by using the hardware sample rate
// - linear PCM => noncompressed audio data format with one frame per packet
// - no need to specify interleaving since only mono is supported
AudioStreamBasicDescription application_format = {0};
UInt32 size = sizeof(application_format);
RTC_DCHECK_EQ(playout_parameters_.sample_rate(),
record_parameters_.sample_rate());
RTC_DCHECK_EQ(1, kPreferredNumberOfChannels);
application_format.mSampleRate = playout_parameters_.sample_rate();
application_format.mFormatID = kAudioFormatLinearPCM;
application_format.mFormatFlags =
kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
application_format.mBytesPerPacket = kBytesPerSample;
application_format.mFramesPerPacket = 1; // uncompressed
application_format.mBytesPerFrame = kBytesPerSample;
application_format.mChannelsPerFrame = kPreferredNumberOfChannels;
application_format.mBitsPerChannel = 8 * kBytesPerSample;
// Store the new format.
application_format_ = application_format;
#if !defined(NDEBUG)
LogABSD(application_format_);
#endif
// Set the application format on the output scope of the input element/bus.
LOG_AND_RETURN_IF_ERROR(
AudioUnitSetProperty(vpio_unit_, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Output, input_bus,
&application_format, size),
"Failed to set application format on output scope of input element");
// Set the application format on the input scope of the output element/bus.
LOG_AND_RETURN_IF_ERROR(
AudioUnitSetProperty(vpio_unit_, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input, output_bus,
&application_format, size),
"Failed to set application format on input scope of output element");
// Specify the callback function that provides audio samples to the audio
// unit.
AURenderCallbackStruct render_callback;
render_callback.inputProc = GetPlayoutData;
render_callback.inputProcRefCon = this;
LOG_AND_RETURN_IF_ERROR(
AudioUnitSetProperty(vpio_unit_, kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input, output_bus, &render_callback,
sizeof(render_callback)),
"Failed to specify the render callback on the output element");
// Disable AU buffer allocation for the recorder, we allocate our own.
// TODO(henrika): not sure that it actually saves resource to make this call.
UInt32 flag = 0;
LOG_AND_RETURN_IF_ERROR(
AudioUnitSetProperty(vpio_unit_, kAudioUnitProperty_ShouldAllocateBuffer,
kAudioUnitScope_Output, input_bus, &flag,
sizeof(flag)),
"Failed to disable buffer allocation on the input element");
// Specify the callback to be called by the I/O thread to us when input audio
// is available. The recorded samples can then be obtained by calling the
// AudioUnitRender() method.
AURenderCallbackStruct input_callback;
input_callback.inputProc = RecordedDataIsAvailable;
input_callback.inputProcRefCon = this;
LOG_AND_RETURN_IF_ERROR(
AudioUnitSetProperty(vpio_unit_,
kAudioOutputUnitProperty_SetInputCallback,
kAudioUnitScope_Global, input_bus, &input_callback,
sizeof(input_callback)),
"Failed to specify the input callback on the input element");
// Initialize the Voice-Processing I/O unit instance.
LOG_AND_RETURN_IF_ERROR(AudioUnitInitialize(vpio_unit_),
"Failed to initialize the Voice-Processing I/O unit");
return true;
}
bool AudioDeviceIOS::RestartAudioUnitWithNewFormat(float sample_rate) {
LOGI() << "RestartAudioUnitWithNewFormat(sample_rate=" << sample_rate << ")";
// Stop the active audio unit.
LOG_AND_RETURN_IF_ERROR(AudioOutputUnitStop(vpio_unit_),
"Failed to stop the the Voice-Processing I/O unit");
// The stream format is about to be changed and it requires that we first
// uninitialize it to deallocate its resources.
LOG_AND_RETURN_IF_ERROR(
AudioUnitUninitialize(vpio_unit_),
"Failed to uninitialize the the Voice-Processing I/O unit");
// Allocate new buffers given the new stream format.
SetupAudioBuffersForActiveAudioSession();
// Update the existing application format using the new sample rate.
application_format_.mSampleRate = playout_parameters_.sample_rate();
UInt32 size = sizeof(application_format_);
AudioUnitSetProperty(vpio_unit_, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Output, 1, &application_format_, size);
AudioUnitSetProperty(vpio_unit_, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input, 0, &application_format_, size);
// Prepare the audio unit to render audio again.
LOG_AND_RETURN_IF_ERROR(AudioUnitInitialize(vpio_unit_),
"Failed to initialize the Voice-Processing I/O unit");
// Start rendering audio using the new format.
LOG_AND_RETURN_IF_ERROR(AudioOutputUnitStart(vpio_unit_),
"Failed to start the Voice-Processing I/O unit");
return true;
}
bool AudioDeviceIOS::InitPlayOrRecord() {
LOGI() << "InitPlayOrRecord";
AVAudioSession* session = [AVAudioSession sharedInstance];
// Activate the audio session and ask for a set of preferred audio parameters.
ActivateAudioSession(session, true);
// Start observing audio session interruptions and route changes.
RegisterNotificationObservers();
// Ensure that we got what what we asked for in our active audio session.
SetupAudioBuffersForActiveAudioSession();
// Create, setup and initialize a new Voice-Processing I/O unit.
if (!SetupAndInitializeVoiceProcessingAudioUnit()) {
return false;
}
return true;
}
bool AudioDeviceIOS::ShutdownPlayOrRecord() {
LOGI() << "ShutdownPlayOrRecord";
// Remove audio session notification observers.
UnregisterNotificationObservers();
// Close and delete the voice-processing I/O unit.
OSStatus result = -1;
if (nullptr != vpio_unit_) {
result = AudioOutputUnitStop(vpio_unit_);
if (result != noErr) {
LOG_F(LS_ERROR) << "AudioOutputUnitStop failed: " << result;
}
result = AudioUnitUninitialize(vpio_unit_);
if (result != noErr) {
LOG_F(LS_ERROR) << "AudioUnitUninitialize failed: " << result;
}
result = AudioComponentInstanceDispose(vpio_unit_);
if (result != noErr) {
LOG_F(LS_ERROR) << "AudioComponentInstanceDispose failed: " << result;
}
vpio_unit_ = nullptr;
}
// All I/O should be stopped or paused prior to deactivating the audio
// session, hence we deactivate as last action.
AVAudioSession* session = [AVAudioSession sharedInstance];
ActivateAudioSession(session, false);
return true;
}
OSStatus AudioDeviceIOS::RecordedDataIsAvailable(
void* in_ref_con,
AudioUnitRenderActionFlags* io_action_flags,
const AudioTimeStamp* in_time_stamp,
UInt32 in_bus_number,
UInt32 in_number_frames,
AudioBufferList* io_data) {
RTC_DCHECK_EQ(1u, in_bus_number);
RTC_DCHECK(
!io_data); // no buffer should be allocated for input at this stage
AudioDeviceIOS* audio_device_ios = static_cast<AudioDeviceIOS*>(in_ref_con);
return audio_device_ios->OnRecordedDataIsAvailable(
io_action_flags, in_time_stamp, in_bus_number, in_number_frames);
}
OSStatus AudioDeviceIOS::OnRecordedDataIsAvailable(
AudioUnitRenderActionFlags* io_action_flags,
const AudioTimeStamp* in_time_stamp,
UInt32 in_bus_number,
UInt32 in_number_frames) {
OSStatus result = noErr;
// Simply return if recording is not enabled.
if (!rtc::AtomicOps::AcquireLoad(&recording_))
return result;
if (in_number_frames != record_parameters_.frames_per_buffer()) {
// We have seen short bursts (1-2 frames) where |in_number_frames| changes.
// Add a log to keep track of longer sequences if that should ever happen.
LOG(LS_WARNING) << "in_number_frames (" << in_number_frames
<< ") != " << record_parameters_.frames_per_buffer();
}
// Obtain the recorded audio samples by initiating a rendering cycle.
// Since it happens on the input bus, the |io_data| parameter is a reference
// to the preallocated audio buffer list that the audio unit renders into.
// TODO(henrika): should error handling be improved?
AudioBufferList* io_data = &audio_record_buffer_list_;
result = AudioUnitRender(vpio_unit_, io_action_flags, in_time_stamp,
in_bus_number, in_number_frames, io_data);
if (result != noErr) {
LOG_F(LS_ERROR) << "AudioOutputUnitStart failed: " << result;
return result;
}
// Get a pointer to the recorded audio and send it to the WebRTC ADB.
// Use the FineAudioBuffer instance to convert between native buffer size
// and the 10ms buffer size used by WebRTC.
const UInt32 data_size_in_bytes = io_data->mBuffers[0].mDataByteSize;
RTC_CHECK_EQ(data_size_in_bytes / kBytesPerSample, in_number_frames);
SInt8* data = static_cast<SInt8*>(io_data->mBuffers[0].mData);
fine_audio_buffer_->DeliverRecordedData(data, data_size_in_bytes,
kFixedPlayoutDelayEstimate,
kFixedRecordDelayEstimate);
return noErr;
}
OSStatus AudioDeviceIOS::GetPlayoutData(
void* in_ref_con,
AudioUnitRenderActionFlags* io_action_flags,
const AudioTimeStamp* in_time_stamp,
UInt32 in_bus_number,
UInt32 in_number_frames,
AudioBufferList* io_data) {
RTC_DCHECK_EQ(0u, in_bus_number);
RTC_DCHECK(io_data);
AudioDeviceIOS* audio_device_ios = static_cast<AudioDeviceIOS*>(in_ref_con);
return audio_device_ios->OnGetPlayoutData(io_action_flags, in_number_frames,
io_data);
}
OSStatus AudioDeviceIOS::OnGetPlayoutData(
AudioUnitRenderActionFlags* io_action_flags,
UInt32 in_number_frames,
AudioBufferList* io_data) {
// Verify 16-bit, noninterleaved mono PCM signal format.
RTC_DCHECK_EQ(1u, io_data->mNumberBuffers);
RTC_DCHECK_EQ(1u, io_data->mBuffers[0].mNumberChannels);
// Get pointer to internal audio buffer to which new audio data shall be
// written.
const UInt32 dataSizeInBytes = io_data->mBuffers[0].mDataByteSize;
RTC_CHECK_EQ(dataSizeInBytes / kBytesPerSample, in_number_frames);
SInt8* destination = static_cast<SInt8*>(io_data->mBuffers[0].mData);
// Produce silence and give audio unit a hint about it if playout is not
// activated.
if (!rtc::AtomicOps::AcquireLoad(&playing_)) {
*io_action_flags |= kAudioUnitRenderAction_OutputIsSilence;
memset(destination, 0, dataSizeInBytes);
return noErr;
}
// Read decoded 16-bit PCM samples from WebRTC (using a size that matches
// the native I/O audio unit) to a preallocated intermediate buffer and
// copy the result to the audio buffer in the |io_data| destination.
SInt8* source = playout_audio_buffer_.get();
fine_audio_buffer_->GetPlayoutData(source);
memcpy(destination, source, dataSizeInBytes);
return noErr;
}
} // namespace webrtc