Moving iOS Audio Device to sdk.

This change forks the existing iOS audio device module and audio device
from modules/audio_device/ into sdk/objc/Framework. It also updates
RTCPeerConnectionFactory to use the forked implementation.

The unit tests are re-implemented as XCTests.

(was: https://webrtc-review.googlesource.com/c/src/+/67300)

Bug: webrtc:9120
Change-Id: I07340505137b16c2dd487569ad0112f984557bba
Reviewed-on: https://webrtc-review.googlesource.com/75125
Reviewed-by: Kári Helgason <kthelgason@webrtc.org>
Commit-Queue: Peter Hanspers <peterhanspers@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#23208}
This commit is contained in:
Peter Hanspers
2018-05-11 10:58:45 +02:00
committed by Commit Bot
parent c6ce9c5938
commit 08da28dd60
19 changed files with 3863 additions and 10 deletions

View File

@ -0,0 +1,293 @@
/*
* 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.
*/
#ifndef MODULES_AUDIO_DEVICE_IOS_AUDIO_DEVICE_IOS_H_
#define MODULES_AUDIO_DEVICE_IOS_AUDIO_DEVICE_IOS_H_
#include <memory>
#include "sdk/objc/Framework/Headers/WebRTC/RTCMacros.h"
#include "modules/audio_device/audio_device_generic.h"
#include "audio_session_observer.h"
#include "voice_processing_audio_unit.h"
#include "rtc_base/buffer.h"
#include "rtc_base/thread.h"
#include "rtc_base/thread_annotations.h"
#include "rtc_base/thread_checker.h"
RTC_FWD_DECL_OBJC_CLASS(RTCNativeAudioSessionDelegateAdapter);
namespace webrtc {
class FineAudioBuffer;
namespace ios_adm {
// Implements full duplex 16-bit mono PCM audio support for iOS using a
// Voice-Processing (VP) I/O audio unit in Core Audio. The VP I/O audio unit
// supports audio echo cancellation. It also adds automatic gain control,
// adjustment of voice-processing quality and muting.
//
// An instance must be created and destroyed on one and the same thread.
// All supported public methods must also be called on the same thread.
// A thread checker will RTC_DCHECK if any supported method is called on an
// invalid thread.
//
// Recorded audio will be delivered on a real-time internal I/O thread in the
// audio unit. The audio unit will also ask for audio data to play out on this
// same thread.
class AudioDeviceIOS : public AudioDeviceGeneric,
public AudioSessionObserver,
public VoiceProcessingAudioUnitObserver,
public rtc::MessageHandler {
public:
AudioDeviceIOS();
~AudioDeviceIOS();
void AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) override;
InitStatus Init() override;
int32_t Terminate() override;
bool Initialized() const override;
int32_t InitPlayout() override;
bool PlayoutIsInitialized() const override;
int32_t InitRecording() override;
bool RecordingIsInitialized() const override;
int32_t StartPlayout() override;
int32_t StopPlayout() override;
bool Playing() const override { return playing_; }
int32_t StartRecording() override;
int32_t StopRecording() override;
bool Recording() const override { return recording_; }
// These methods returns hard-coded delay values and not dynamic delay
// estimates. The reason is that iOS supports a built-in AEC and the WebRTC
// AEC will always be disabled in the Libjingle layer to avoid running two
// AEC implementations at the same time. And, it saves resources to avoid
// updating these delay values continuously.
// TODO(henrika): it would be possible to mark these two methods as not
// implemented since they are only called for A/V-sync purposes today and
// A/V-sync is not supported on iOS. However, we avoid adding error messages
// the log by using these dummy implementations instead.
int32_t PlayoutDelay(uint16_t& delayMS) const override;
// Native audio parameters stored during construction.
// These methods are unique for the iOS implementation.
int GetPlayoutAudioParameters(AudioParameters* params) const override;
int GetRecordAudioParameters(AudioParameters* params) const override;
// These methods are currently not fully implemented on iOS:
// See audio_device_not_implemented.cc for trivial implementations.
int32_t ActiveAudioLayer(
AudioDeviceModule::AudioLayer& audioLayer) const override;
int32_t PlayoutIsAvailable(bool& available) override;
int32_t RecordingIsAvailable(bool& available) override;
int16_t PlayoutDevices() override;
int16_t RecordingDevices() override;
int32_t PlayoutDeviceName(uint16_t index,
char name[kAdmMaxDeviceNameSize],
char guid[kAdmMaxGuidSize]) override;
int32_t RecordingDeviceName(uint16_t index,
char name[kAdmMaxDeviceNameSize],
char guid[kAdmMaxGuidSize]) override;
int32_t SetPlayoutDevice(uint16_t index) override;
int32_t SetPlayoutDevice(
AudioDeviceModule::WindowsDeviceType device) override;
int32_t SetRecordingDevice(uint16_t index) override;
int32_t SetRecordingDevice(
AudioDeviceModule::WindowsDeviceType device) override;
int32_t InitSpeaker() override;
bool SpeakerIsInitialized() const override;
int32_t InitMicrophone() override;
bool MicrophoneIsInitialized() const override;
int32_t SpeakerVolumeIsAvailable(bool& available) override;
int32_t SetSpeakerVolume(uint32_t volume) override;
int32_t SpeakerVolume(uint32_t& volume) const override;
int32_t MaxSpeakerVolume(uint32_t& maxVolume) const override;
int32_t MinSpeakerVolume(uint32_t& minVolume) const override;
int32_t MicrophoneVolumeIsAvailable(bool& available) override;
int32_t SetMicrophoneVolume(uint32_t volume) override;
int32_t MicrophoneVolume(uint32_t& volume) const override;
int32_t MaxMicrophoneVolume(uint32_t& maxVolume) const override;
int32_t MinMicrophoneVolume(uint32_t& minVolume) const override;
int32_t MicrophoneMuteIsAvailable(bool& available) override;
int32_t SetMicrophoneMute(bool enable) override;
int32_t MicrophoneMute(bool& enabled) const override;
int32_t SpeakerMuteIsAvailable(bool& available) override;
int32_t SetSpeakerMute(bool enable) override;
int32_t SpeakerMute(bool& enabled) const override;
int32_t StereoPlayoutIsAvailable(bool& available) override;
int32_t SetStereoPlayout(bool enable) override;
int32_t StereoPlayout(bool& enabled) const override;
int32_t StereoRecordingIsAvailable(bool& available) override;
int32_t SetStereoRecording(bool enable) override;
int32_t StereoRecording(bool& enabled) const override;
// AudioSessionObserver methods. May be called from any thread.
void OnInterruptionBegin() override;
void OnInterruptionEnd() override;
void OnValidRouteChange() override;
void OnCanPlayOrRecordChange(bool can_play_or_record) override;
void OnChangedOutputVolume() override;
// VoiceProcessingAudioUnitObserver methods.
OSStatus OnDeliverRecordedData(AudioUnitRenderActionFlags* flags,
const AudioTimeStamp* time_stamp,
UInt32 bus_number,
UInt32 num_frames,
AudioBufferList* io_data) override;
OSStatus OnGetPlayoutData(AudioUnitRenderActionFlags* flags,
const AudioTimeStamp* time_stamp,
UInt32 bus_number,
UInt32 num_frames,
AudioBufferList* io_data) override;
// Handles messages from posts.
void OnMessage(rtc::Message *msg) override;
bool IsInterrupted();
private:
// Called by the relevant AudioSessionObserver methods on |thread_|.
void HandleInterruptionBegin();
void HandleInterruptionEnd();
void HandleValidRouteChange();
void HandleCanPlayOrRecordChange(bool can_play_or_record);
void HandleSampleRateChange(float sample_rate);
void HandlePlayoutGlitchDetected();
void HandleOutputVolumeChange();
// Uses current |playout_parameters_| and |record_parameters_| to inform the
// audio device buffer (ADB) about our internal audio parameters.
void UpdateAudioDeviceBuffer();
// Since the preferred audio parameters are only hints to the OS, the actual
// values may be different once the AVAudioSession has been activated.
// This method asks for the current hardware parameters and takes actions
// if they should differ from what we have asked for initially. It also
// defines |playout_parameters_| and |record_parameters_|.
void SetupAudioBuffersForActiveAudioSession();
// Creates the audio unit.
bool CreateAudioUnit();
// Updates the audio unit state based on current state.
void UpdateAudioUnit(bool can_play_or_record);
// Configures the audio session for WebRTC.
bool ConfigureAudioSession();
// Unconfigures the audio session.
void UnconfigureAudioSession();
// Activates our audio session, creates and initializes the voice-processing
// audio unit and verifies that we got the preferred native audio parameters.
bool InitPlayOrRecord();
// Closes and deletes the voice-processing I/O unit.
void ShutdownPlayOrRecord();
// Ensures that methods are called from the same thread as this object is
// created on.
rtc::ThreadChecker thread_checker_;
// Native I/O audio thread checker.
rtc::ThreadChecker io_thread_checker_;
// Thread that this object is created on.
rtc::Thread* thread_;
// Raw pointer handle provided to us in AttachAudioBuffer(). Owned by the
// AudioDeviceModuleImpl class and called by AudioDeviceModule::Create().
// The AudioDeviceBuffer is a member of the AudioDeviceModuleImpl instance
// and therefore outlives this object.
AudioDeviceBuffer* audio_device_buffer_;
// Contains audio parameters (sample rate, #channels, buffer size etc.) for
// the playout and recording sides. These structure is set in two steps:
// first, native sample rate and #channels are defined in Init(). Next, the
// audio session is activated and we verify that the preferred parameters
// were granted by the OS. At this stage it is also possible to add a third
// component to the parameters; the native I/O buffer duration.
// A RTC_CHECK will be hit if we for some reason fail to open an audio session
// using the specified parameters.
AudioParameters playout_parameters_;
AudioParameters record_parameters_;
// The AudioUnit used to play and record audio.
std::unique_ptr<VoiceProcessingAudioUnit> audio_unit_;
// FineAudioBuffer takes an AudioDeviceBuffer which delivers audio data
// in chunks of 10ms. It then allows for this data to be pulled in
// a finer or coarser granularity. I.e. interacting with this class instead
// of directly with the AudioDeviceBuffer one can ask for any number of
// audio data samples. Is also supports a similar scheme for the recording
// side.
// Example: native buffer size can be 128 audio frames at 16kHz sample rate.
// WebRTC will provide 480 audio frames per 10ms but iOS asks for 128
// in each callback (one every 8ms). This class can then ask for 128 and the
// FineAudioBuffer will ask WebRTC for new data only when needed and also
// cache non-utilized audio between callbacks. On the recording side, iOS
// can provide audio data frames of size 128 and these are accumulated until
// enough data to supply one 10ms call exists. This 10ms chunk is then sent
// to WebRTC and the remaining part is stored.
std::unique_ptr<FineAudioBuffer> fine_audio_buffer_;
// Temporary storage for recorded data. AudioUnitRender() renders into this
// array as soon as a frame of the desired buffer size has been recorded.
// On real iOS devices, the size will be fixed and set once. For iOS
// simulators, the size can vary from callback to callback and the size
// will be changed dynamically to account for this behavior.
rtc::BufferT<int16_t> record_audio_buffer_;
// Set to 1 when recording is active and 0 otherwise.
volatile int recording_;
// Set to 1 when playout is active and 0 otherwise.
volatile int playing_;
// Set to true after successful call to Init(), false otherwise.
bool initialized_ RTC_GUARDED_BY(thread_checker_);
// Set to true after successful call to InitRecording() or InitPlayout(),
// false otherwise.
bool audio_is_initialized_;
// Set to true if audio session is interrupted, false otherwise.
bool is_interrupted_;
// Audio interruption observer instance.
RTCNativeAudioSessionDelegateAdapter* audio_session_observer_
RTC_GUARDED_BY(thread_checker_);
// Set to true if we've activated the audio session.
bool has_configured_session_ RTC_GUARDED_BY(thread_checker_);
// Counts number of detected audio glitches on the playout side.
int64_t num_detected_playout_glitches_ RTC_GUARDED_BY(thread_checker_);
int64_t last_playout_time_ RTC_GUARDED_BY(io_thread_checker_);
// Counts number of playout callbacks per call.
// The value isupdated on the native I/O thread and later read on the
// creating thread (see thread_checker_) but at this stage no audio is
// active. Hence, it is a "thread safe" design and no lock is needed.
int64_t num_playout_callbacks_;
// Contains the time for when the last output volume change was detected.
int64_t last_output_volume_change_time_ RTC_GUARDED_BY(thread_checker_);
};
} // namespace ios_adm
} // namespace webrtc
#endif // MODULES_AUDIO_DEVICE_IOS_AUDIO_DEVICE_IOS_H_

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,143 @@
/*
* 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.
*/
#ifndef SDK_IOS_NATIVE_API_AUDIO_DEVICE_MODULE_AUDIO_DEVICE_IOS_H_
#define SDK_IOS_NATIVE_API_AUDIO_DEVICE_MODULE_AUDIO_DEVICE_IOS_H_
#include <memory>
#include "audio_device_ios.h"
#include "modules/audio_device/audio_device_buffer.h"
#include "modules/audio_device/include/audio_device.h"
#include "rtc_base/checks.h"
#include "rtc_base/criticalsection.h"
namespace webrtc {
class AudioDeviceGeneric;
namespace ios_adm {
class AudioDeviceModuleIOS : public AudioDeviceModule {
public:
int32_t AttachAudioBuffer();
AudioDeviceModuleIOS();
~AudioDeviceModuleIOS() override;
// Retrieve the currently utilized audio layer
int32_t ActiveAudioLayer(AudioLayer* audioLayer) const override;
// Full-duplex transportation of PCM audio
int32_t RegisterAudioCallback(AudioTransport* audioCallback) override;
// Main initializaton and termination
int32_t Init() override;
int32_t Terminate() override;
bool Initialized() const override;
// Device enumeration
int16_t PlayoutDevices() override;
int16_t RecordingDevices() override;
int32_t PlayoutDeviceName(uint16_t index,
char name[kAdmMaxDeviceNameSize],
char guid[kAdmMaxGuidSize]) override;
int32_t RecordingDeviceName(uint16_t index,
char name[kAdmMaxDeviceNameSize],
char guid[kAdmMaxGuidSize]) override;
// Device selection
int32_t SetPlayoutDevice(uint16_t index) override;
int32_t SetPlayoutDevice(WindowsDeviceType device) override;
int32_t SetRecordingDevice(uint16_t index) override;
int32_t SetRecordingDevice(WindowsDeviceType device) override;
// Audio transport initialization
int32_t PlayoutIsAvailable(bool* available) override;
int32_t InitPlayout() override;
bool PlayoutIsInitialized() const override;
int32_t RecordingIsAvailable(bool* available) override;
int32_t InitRecording() override;
bool RecordingIsInitialized() const override;
// Audio transport control
int32_t StartPlayout() override;
int32_t StopPlayout() override;
bool Playing() const override;
int32_t StartRecording() override;
int32_t StopRecording() override;
bool Recording() const override;
// Audio mixer initialization
int32_t InitSpeaker() override;
bool SpeakerIsInitialized() const override;
int32_t InitMicrophone() override;
bool MicrophoneIsInitialized() const override;
// Speaker volume controls
int32_t SpeakerVolumeIsAvailable(bool* available) override;
int32_t SetSpeakerVolume(uint32_t volume) override;
int32_t SpeakerVolume(uint32_t* volume) const override;
int32_t MaxSpeakerVolume(uint32_t* maxVolume) const override;
int32_t MinSpeakerVolume(uint32_t* minVolume) const override;
// Microphone volume controls
int32_t MicrophoneVolumeIsAvailable(bool* available) override;
int32_t SetMicrophoneVolume(uint32_t volume) override;
int32_t MicrophoneVolume(uint32_t* volume) const override;
int32_t MaxMicrophoneVolume(uint32_t* maxVolume) const override;
int32_t MinMicrophoneVolume(uint32_t* minVolume) const override;
// Speaker mute control
int32_t SpeakerMuteIsAvailable(bool* available) override;
int32_t SetSpeakerMute(bool enable) override;
int32_t SpeakerMute(bool* enabled) const override;
// Microphone mute control
int32_t MicrophoneMuteIsAvailable(bool* available) override;
int32_t SetMicrophoneMute(bool enable) override;
int32_t MicrophoneMute(bool* enabled) const override;
// Stereo support
int32_t StereoPlayoutIsAvailable(bool* available) const override;
int32_t SetStereoPlayout(bool enable) override;
int32_t StereoPlayout(bool* enabled) const override;
int32_t StereoRecordingIsAvailable(bool* available) const override;
int32_t SetStereoRecording(bool enable) override;
int32_t StereoRecording(bool* enabled) const override;
// Delay information and control
int32_t PlayoutDelay(uint16_t* delayMS) const override;
bool BuiltInAECIsAvailable() const override;
int32_t EnableBuiltInAEC(bool enable) override;
bool BuiltInAGCIsAvailable() const override;
int32_t EnableBuiltInAGC(bool enable) override;
bool BuiltInNSIsAvailable() const override;
int32_t EnableBuiltInNS(bool enable) override;
#if defined(WEBRTC_IOS)
int GetPlayoutAudioParameters(AudioParameters* params) const override;
int GetRecordAudioParameters(AudioParameters* params) const override;
#endif // WEBRTC_IOS
private:
bool initialized_ = false;
std::unique_ptr<AudioDeviceIOS> audio_device_;
std::unique_ptr<AudioDeviceBuffer> audio_device_buffer_;
};
} // namespace ios_adm
} // namespace webrtc
#endif // SDK_IOS_NATIVE_API_AUDIO_DEVICE_MODULE_AUDIO_DEVICE_IOS_H_

View File

@ -0,0 +1,673 @@
/*
* 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 "audio_device_module_ios.h"
#include "modules/audio_device/audio_device_config.h"
#include "modules/audio_device/audio_device_generic.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/refcount.h"
#include "rtc_base/refcountedobject.h"
#include "system_wrappers/include/metrics.h"
#if defined(WEBRTC_IOS)
#include "audio_device_ios.h"
#endif
#define CHECKinitialized_() \
{ \
if (!initialized_) { \
return -1; \
}; \
}
#define CHECKinitialized__BOOL() \
{ \
if (!initialized_) { \
return false; \
}; \
}
namespace webrtc {
namespace ios_adm {
AudioDeviceModuleIOS::AudioDeviceModuleIOS() {
RTC_LOG(INFO) << "current platform is IOS";
RTC_LOG(INFO) << "iPhone Audio APIs will be utilized.";
}
int32_t AudioDeviceModuleIOS::AttachAudioBuffer() {
RTC_LOG(INFO) << __FUNCTION__;
audio_device_->AttachAudioBuffer(audio_device_buffer_.get());
return 0;
}
AudioDeviceModuleIOS::~AudioDeviceModuleIOS() {
RTC_LOG(INFO) << __FUNCTION__;
}
int32_t AudioDeviceModuleIOS::ActiveAudioLayer(AudioLayer* audioLayer) const {
RTC_LOG(INFO) << __FUNCTION__;
AudioLayer activeAudio;
if (audio_device_->ActiveAudioLayer(activeAudio) == -1) {
return -1;
}
*audioLayer = activeAudio;
return 0;
}
int32_t AudioDeviceModuleIOS::Init() {
RTC_LOG(INFO) << __FUNCTION__;
if (initialized_)
return 0;
audio_device_buffer_.reset(new webrtc::AudioDeviceBuffer());
audio_device_.reset(new ios_adm::AudioDeviceIOS());
RTC_CHECK(audio_device_);
this->AttachAudioBuffer();
AudioDeviceGeneric::InitStatus status = audio_device_->Init();
RTC_HISTOGRAM_ENUMERATION(
"WebRTC.Audio.InitializationResult", static_cast<int>(status),
static_cast<int>(AudioDeviceGeneric::InitStatus::NUM_STATUSES));
if (status != AudioDeviceGeneric::InitStatus::OK) {
RTC_LOG(LS_ERROR) << "Audio device initialization failed.";
return -1;
}
initialized_ = true;
return 0;
}
int32_t AudioDeviceModuleIOS::Terminate() {
RTC_LOG(INFO) << __FUNCTION__;
if (!initialized_)
return 0;
if (audio_device_->Terminate() == -1) {
return -1;
}
initialized_ = false;
return 0;
}
bool AudioDeviceModuleIOS::Initialized() const {
RTC_LOG(INFO) << __FUNCTION__ << ": " << initialized_;
return initialized_;
}
int32_t AudioDeviceModuleIOS::InitSpeaker() {
RTC_LOG(INFO) << __FUNCTION__;
CHECKinitialized_();
return audio_device_->InitSpeaker();
}
int32_t AudioDeviceModuleIOS::InitMicrophone() {
RTC_LOG(INFO) << __FUNCTION__;
CHECKinitialized_();
return audio_device_->InitMicrophone();
}
int32_t AudioDeviceModuleIOS::SpeakerVolumeIsAvailable(bool* available) {
RTC_LOG(INFO) << __FUNCTION__;
CHECKinitialized_();
bool isAvailable = false;
if (audio_device_->SpeakerVolumeIsAvailable(isAvailable) == -1) {
return -1;
}
*available = isAvailable;
RTC_LOG(INFO) << "output: " << isAvailable;
return 0;
}
int32_t AudioDeviceModuleIOS::SetSpeakerVolume(uint32_t volume) {
RTC_LOG(INFO) << __FUNCTION__ << "(" << volume << ")";
CHECKinitialized_();
return audio_device_->SetSpeakerVolume(volume);
}
int32_t AudioDeviceModuleIOS::SpeakerVolume(uint32_t* volume) const {
RTC_LOG(INFO) << __FUNCTION__;
CHECKinitialized_();
uint32_t level = 0;
if (audio_device_->SpeakerVolume(level) == -1) {
return -1;
}
*volume = level;
RTC_LOG(INFO) << "output: " << *volume;
return 0;
}
bool AudioDeviceModuleIOS::SpeakerIsInitialized() const {
RTC_LOG(INFO) << __FUNCTION__;
CHECKinitialized__BOOL();
bool isInitialized = audio_device_->SpeakerIsInitialized();
RTC_LOG(INFO) << "output: " << isInitialized;
return isInitialized;
}
bool AudioDeviceModuleIOS::MicrophoneIsInitialized() const {
RTC_LOG(INFO) << __FUNCTION__;
CHECKinitialized__BOOL();
bool isInitialized = audio_device_->MicrophoneIsInitialized();
RTC_LOG(INFO) << "output: " << isInitialized;
return isInitialized;
}
int32_t AudioDeviceModuleIOS::MaxSpeakerVolume(uint32_t* maxVolume) const {
CHECKinitialized_();
uint32_t maxVol = 0;
if (audio_device_->MaxSpeakerVolume(maxVol) == -1) {
return -1;
}
*maxVolume = maxVol;
return 0;
}
int32_t AudioDeviceModuleIOS::MinSpeakerVolume(uint32_t* minVolume) const {
CHECKinitialized_();
uint32_t minVol = 0;
if (audio_device_->MinSpeakerVolume(minVol) == -1) {
return -1;
}
*minVolume = minVol;
return 0;
}
int32_t AudioDeviceModuleIOS::SpeakerMuteIsAvailable(bool* available) {
RTC_LOG(INFO) << __FUNCTION__;
CHECKinitialized_();
bool isAvailable = false;
if (audio_device_->SpeakerMuteIsAvailable(isAvailable) == -1) {
return -1;
}
*available = isAvailable;
RTC_LOG(INFO) << "output: " << isAvailable;
return 0;
}
int32_t AudioDeviceModuleIOS::SetSpeakerMute(bool enable) {
RTC_LOG(INFO) << __FUNCTION__ << "(" << enable << ")";
CHECKinitialized_();
return audio_device_->SetSpeakerMute(enable);
}
int32_t AudioDeviceModuleIOS::SpeakerMute(bool* enabled) const {
RTC_LOG(INFO) << __FUNCTION__;
CHECKinitialized_();
bool muted = false;
if (audio_device_->SpeakerMute(muted) == -1) {
return -1;
}
*enabled = muted;
RTC_LOG(INFO) << "output: " << muted;
return 0;
}
int32_t AudioDeviceModuleIOS::MicrophoneMuteIsAvailable(bool* available) {
RTC_LOG(INFO) << __FUNCTION__;
CHECKinitialized_();
bool isAvailable = false;
if (audio_device_->MicrophoneMuteIsAvailable(isAvailable) == -1) {
return -1;
}
*available = isAvailable;
RTC_LOG(INFO) << "output: " << isAvailable;
return 0;
}
int32_t AudioDeviceModuleIOS::SetMicrophoneMute(bool enable) {
RTC_LOG(INFO) << __FUNCTION__ << "(" << enable << ")";
CHECKinitialized_();
return (audio_device_->SetMicrophoneMute(enable));
}
int32_t AudioDeviceModuleIOS::MicrophoneMute(bool* enabled) const {
RTC_LOG(INFO) << __FUNCTION__;
CHECKinitialized_();
bool muted = false;
if (audio_device_->MicrophoneMute(muted) == -1) {
return -1;
}
*enabled = muted;
RTC_LOG(INFO) << "output: " << muted;
return 0;
}
int32_t AudioDeviceModuleIOS::MicrophoneVolumeIsAvailable(bool* available) {
RTC_LOG(INFO) << __FUNCTION__;
CHECKinitialized_();
bool isAvailable = false;
if (audio_device_->MicrophoneVolumeIsAvailable(isAvailable) == -1) {
return -1;
}
*available = isAvailable;
RTC_LOG(INFO) << "output: " << isAvailable;
return 0;
}
int32_t AudioDeviceModuleIOS::SetMicrophoneVolume(uint32_t volume) {
RTC_LOG(INFO) << __FUNCTION__ << "(" << volume << ")";
CHECKinitialized_();
return (audio_device_->SetMicrophoneVolume(volume));
}
int32_t AudioDeviceModuleIOS::MicrophoneVolume(uint32_t* volume) const {
RTC_LOG(INFO) << __FUNCTION__;
CHECKinitialized_();
uint32_t level = 0;
if (audio_device_->MicrophoneVolume(level) == -1) {
return -1;
}
*volume = level;
RTC_LOG(INFO) << "output: " << *volume;
return 0;
}
int32_t AudioDeviceModuleIOS::StereoRecordingIsAvailable(
bool* available) const {
RTC_LOG(INFO) << __FUNCTION__;
CHECKinitialized_();
bool isAvailable = false;
if (audio_device_->StereoRecordingIsAvailable(isAvailable) == -1) {
return -1;
}
*available = isAvailable;
RTC_LOG(INFO) << "output: " << isAvailable;
return 0;
}
int32_t AudioDeviceModuleIOS::SetStereoRecording(bool enable) {
RTC_LOG(INFO) << __FUNCTION__ << "(" << enable << ")";
CHECKinitialized_();
if (audio_device_->RecordingIsInitialized()) {
RTC_LOG(WARNING) << "recording in stereo is not supported";
return -1;
}
if (audio_device_->SetStereoRecording(enable) == -1) {
RTC_LOG(WARNING) << "failed to change stereo recording";
return -1;
}
int8_t nChannels(1);
if (enable) {
nChannels = 2;
}
audio_device_buffer_.get()->SetRecordingChannels(nChannels);
return 0;
}
int32_t AudioDeviceModuleIOS::StereoRecording(bool* enabled) const {
RTC_LOG(INFO) << __FUNCTION__;
CHECKinitialized_();
bool stereo = false;
if (audio_device_->StereoRecording(stereo) == -1) {
return -1;
}
*enabled = stereo;
RTC_LOG(INFO) << "output: " << stereo;
return 0;
}
int32_t AudioDeviceModuleIOS::StereoPlayoutIsAvailable(bool* available) const {
RTC_LOG(INFO) << __FUNCTION__;
CHECKinitialized_();
bool isAvailable = false;
if (audio_device_->StereoPlayoutIsAvailable(isAvailable) == -1) {
return -1;
}
*available = isAvailable;
RTC_LOG(INFO) << "output: " << isAvailable;
return 0;
}
int32_t AudioDeviceModuleIOS::SetStereoPlayout(bool enable) {
RTC_LOG(INFO) << __FUNCTION__ << "(" << enable << ")";
CHECKinitialized_();
if (audio_device_->PlayoutIsInitialized()) {
RTC_LOG(LERROR)
<< "unable to set stereo mode while playing side is initialized";
return -1;
}
if (audio_device_->SetStereoPlayout(enable)) {
RTC_LOG(WARNING) << "stereo playout is not supported";
return -1;
}
int8_t nChannels(1);
if (enable) {
nChannels = 2;
}
audio_device_buffer_.get()->SetPlayoutChannels(nChannels);
return 0;
}
int32_t AudioDeviceModuleIOS::StereoPlayout(bool* enabled) const {
RTC_LOG(INFO) << __FUNCTION__;
CHECKinitialized_();
bool stereo = false;
if (audio_device_->StereoPlayout(stereo) == -1) {
return -1;
}
*enabled = stereo;
RTC_LOG(INFO) << "output: " << stereo;
return 0;
}
int32_t AudioDeviceModuleIOS::PlayoutIsAvailable(bool* available) {
RTC_LOG(INFO) << __FUNCTION__;
CHECKinitialized_();
bool isAvailable = false;
if (audio_device_->PlayoutIsAvailable(isAvailable) == -1) {
return -1;
}
*available = isAvailable;
RTC_LOG(INFO) << "output: " << isAvailable;
return 0;
}
int32_t AudioDeviceModuleIOS::RecordingIsAvailable(bool* available) {
RTC_LOG(INFO) << __FUNCTION__;
CHECKinitialized_();
bool isAvailable = false;
if (audio_device_->RecordingIsAvailable(isAvailable) == -1) {
return -1;
}
*available = isAvailable;
RTC_LOG(INFO) << "output: " << isAvailable;
return 0;
}
int32_t AudioDeviceModuleIOS::MaxMicrophoneVolume(uint32_t* maxVolume) const {
CHECKinitialized_();
uint32_t maxVol(0);
if (audio_device_->MaxMicrophoneVolume(maxVol) == -1) {
return -1;
}
*maxVolume = maxVol;
return 0;
}
int32_t AudioDeviceModuleIOS::MinMicrophoneVolume(uint32_t* minVolume) const {
CHECKinitialized_();
uint32_t minVol(0);
if (audio_device_->MinMicrophoneVolume(minVol) == -1) {
return -1;
}
*minVolume = minVol;
return 0;
}
int16_t AudioDeviceModuleIOS::PlayoutDevices() {
RTC_LOG(INFO) << __FUNCTION__;
CHECKinitialized_();
uint16_t nPlayoutDevices = audio_device_->PlayoutDevices();
RTC_LOG(INFO) << "output: " << nPlayoutDevices;
return (int16_t)(nPlayoutDevices);
}
int32_t AudioDeviceModuleIOS::SetPlayoutDevice(uint16_t index) {
RTC_LOG(INFO) << __FUNCTION__ << "(" << index << ")";
CHECKinitialized_();
return audio_device_->SetPlayoutDevice(index);
}
int32_t AudioDeviceModuleIOS::SetPlayoutDevice(WindowsDeviceType device) {
RTC_LOG(INFO) << __FUNCTION__;
CHECKinitialized_();
return audio_device_->SetPlayoutDevice(device);
}
int32_t AudioDeviceModuleIOS::PlayoutDeviceName(
uint16_t index,
char name[kAdmMaxDeviceNameSize],
char guid[kAdmMaxGuidSize]) {
RTC_LOG(INFO) << __FUNCTION__ << "(" << index << ", ...)";
CHECKinitialized_();
if (name == NULL) {
return -1;
}
if (audio_device_->PlayoutDeviceName(index, name, guid) == -1) {
return -1;
}
if (name != NULL) {
RTC_LOG(INFO) << "output: name = " << name;
}
if (guid != NULL) {
RTC_LOG(INFO) << "output: guid = " << guid;
}
return 0;
}
int32_t AudioDeviceModuleIOS::RecordingDeviceName(
uint16_t index,
char name[kAdmMaxDeviceNameSize],
char guid[kAdmMaxGuidSize]) {
RTC_LOG(INFO) << __FUNCTION__ << "(" << index << ", ...)";
CHECKinitialized_();
if (name == NULL) {
return -1;
}
if (audio_device_->RecordingDeviceName(index, name, guid) == -1) {
return -1;
}
if (name != NULL) {
RTC_LOG(INFO) << "output: name = " << name;
}
if (guid != NULL) {
RTC_LOG(INFO) << "output: guid = " << guid;
}
return 0;
}
int16_t AudioDeviceModuleIOS::RecordingDevices() {
RTC_LOG(INFO) << __FUNCTION__;
CHECKinitialized_();
uint16_t nRecordingDevices = audio_device_->RecordingDevices();
RTC_LOG(INFO) << "output: " << nRecordingDevices;
return (int16_t)nRecordingDevices;
}
int32_t AudioDeviceModuleIOS::SetRecordingDevice(uint16_t index) {
RTC_LOG(INFO) << __FUNCTION__ << "(" << index << ")";
CHECKinitialized_();
return audio_device_->SetRecordingDevice(index);
}
int32_t AudioDeviceModuleIOS::SetRecordingDevice(WindowsDeviceType device) {
RTC_LOG(INFO) << __FUNCTION__;
CHECKinitialized_();
return audio_device_->SetRecordingDevice(device);
}
int32_t AudioDeviceModuleIOS::InitPlayout() {
RTC_LOG(INFO) << __FUNCTION__;
CHECKinitialized_();
if (PlayoutIsInitialized()) {
return 0;
}
int32_t result = audio_device_->InitPlayout();
RTC_LOG(INFO) << "output: " << result;
RTC_HISTOGRAM_BOOLEAN("WebRTC.Audio.InitPlayoutSuccess",
static_cast<int>(result == 0));
return result;
}
int32_t AudioDeviceModuleIOS::InitRecording() {
RTC_LOG(INFO) << __FUNCTION__;
CHECKinitialized_();
if (RecordingIsInitialized()) {
return 0;
}
int32_t result = audio_device_->InitRecording();
RTC_LOG(INFO) << "output: " << result;
RTC_HISTOGRAM_BOOLEAN("WebRTC.Audio.InitRecordingSuccess",
static_cast<int>(result == 0));
return result;
}
bool AudioDeviceModuleIOS::PlayoutIsInitialized() const {
RTC_LOG(INFO) << __FUNCTION__;
CHECKinitialized__BOOL();
return audio_device_->PlayoutIsInitialized();
}
bool AudioDeviceModuleIOS::RecordingIsInitialized() const {
RTC_LOG(INFO) << __FUNCTION__;
CHECKinitialized__BOOL();
return audio_device_->RecordingIsInitialized();
}
int32_t AudioDeviceModuleIOS::StartPlayout() {
RTC_LOG(INFO) << __FUNCTION__;
CHECKinitialized_();
if (Playing()) {
return 0;
}
audio_device_buffer_.get()->StartPlayout();
int32_t result = audio_device_->StartPlayout();
RTC_LOG(INFO) << "output: " << result;
RTC_HISTOGRAM_BOOLEAN("WebRTC.Audio.StartPlayoutSuccess",
static_cast<int>(result == 0));
return result;
}
int32_t AudioDeviceModuleIOS::StopPlayout() {
RTC_LOG(INFO) << __FUNCTION__;
CHECKinitialized_();
int32_t result = audio_device_->StopPlayout();
audio_device_buffer_.get()->StopPlayout();
RTC_LOG(INFO) << "output: " << result;
RTC_HISTOGRAM_BOOLEAN("WebRTC.Audio.StopPlayoutSuccess",
static_cast<int>(result == 0));
return result;
}
bool AudioDeviceModuleIOS::Playing() const {
RTC_LOG(INFO) << __FUNCTION__;
CHECKinitialized__BOOL();
return audio_device_->Playing();
}
int32_t AudioDeviceModuleIOS::StartRecording() {
RTC_LOG(INFO) << __FUNCTION__;
CHECKinitialized_();
if (Recording()) {
return 0;
}
audio_device_buffer_.get()->StartRecording();
int32_t result = audio_device_->StartRecording();
RTC_LOG(INFO) << "output: " << result;
RTC_HISTOGRAM_BOOLEAN("WebRTC.Audio.StartRecordingSuccess",
static_cast<int>(result == 0));
return result;
}
int32_t AudioDeviceModuleIOS::StopRecording() {
RTC_LOG(INFO) << __FUNCTION__;
CHECKinitialized_();
int32_t result = audio_device_->StopRecording();
audio_device_buffer_.get()->StopRecording();
RTC_LOG(INFO) << "output: " << result;
RTC_HISTOGRAM_BOOLEAN("WebRTC.Audio.StopRecordingSuccess",
static_cast<int>(result == 0));
return result;
}
bool AudioDeviceModuleIOS::Recording() const {
RTC_LOG(INFO) << __FUNCTION__;
CHECKinitialized__BOOL();
return audio_device_->Recording();
}
int32_t AudioDeviceModuleIOS::RegisterAudioCallback(
AudioTransport* audioCallback) {
RTC_LOG(INFO) << __FUNCTION__;
return audio_device_buffer_.get()->RegisterAudioCallback(audioCallback);
}
int32_t AudioDeviceModuleIOS::PlayoutDelay(uint16_t* delayMS) const {
CHECKinitialized_();
uint16_t delay = 0;
if (audio_device_->PlayoutDelay(delay) == -1) {
RTC_LOG(LERROR) << "failed to retrieve the playout delay";
return -1;
}
*delayMS = delay;
return 0;
}
bool AudioDeviceModuleIOS::BuiltInAECIsAvailable() const {
RTC_LOG(INFO) << __FUNCTION__;
CHECKinitialized__BOOL();
bool isAvailable = audio_device_->BuiltInAECIsAvailable();
RTC_LOG(INFO) << "output: " << isAvailable;
return isAvailable;
}
int32_t AudioDeviceModuleIOS::EnableBuiltInAEC(bool enable) {
RTC_LOG(INFO) << __FUNCTION__ << "(" << enable << ")";
CHECKinitialized_();
int32_t ok = audio_device_->EnableBuiltInAEC(enable);
RTC_LOG(INFO) << "output: " << ok;
return ok;
}
bool AudioDeviceModuleIOS::BuiltInAGCIsAvailable() const {
RTC_LOG(INFO) << __FUNCTION__;
CHECKinitialized__BOOL();
bool isAvailable = audio_device_->BuiltInAGCIsAvailable();
RTC_LOG(INFO) << "output: " << isAvailable;
return isAvailable;
}
int32_t AudioDeviceModuleIOS::EnableBuiltInAGC(bool enable) {
RTC_LOG(INFO) << __FUNCTION__ << "(" << enable << ")";
CHECKinitialized_();
int32_t ok = audio_device_->EnableBuiltInAGC(enable);
RTC_LOG(INFO) << "output: " << ok;
return ok;
}
bool AudioDeviceModuleIOS::BuiltInNSIsAvailable() const {
RTC_LOG(INFO) << __FUNCTION__;
CHECKinitialized__BOOL();
bool isAvailable = audio_device_->BuiltInNSIsAvailable();
RTC_LOG(INFO) << "output: " << isAvailable;
return isAvailable;
}
int32_t AudioDeviceModuleIOS::EnableBuiltInNS(bool enable) {
RTC_LOG(INFO) << __FUNCTION__ << "(" << enable << ")";
CHECKinitialized_();
int32_t ok = audio_device_->EnableBuiltInNS(enable);
RTC_LOG(INFO) << "output: " << ok;
return ok;
}
#if defined(WEBRTC_IOS)
int AudioDeviceModuleIOS::GetPlayoutAudioParameters(
AudioParameters* params) const {
RTC_LOG(INFO) << __FUNCTION__;
int r = audio_device_->GetPlayoutAudioParameters(params);
RTC_LOG(INFO) << "output: " << r;
return r;
}
int AudioDeviceModuleIOS::GetRecordAudioParameters(
AudioParameters* params) const {
RTC_LOG(INFO) << __FUNCTION__;
int r = audio_device_->GetRecordAudioParameters(params);
RTC_LOG(INFO) << "output: " << r;
return r;
}
#endif // WEBRTC_IOS
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright 2016 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.
*/
#ifndef MODULES_AUDIO_DEVICE_IOS_AUDIO_SESSION_OBSERVER_H_
#define MODULES_AUDIO_DEVICE_IOS_AUDIO_SESSION_OBSERVER_H_
#include "rtc_base/asyncinvoker.h"
#include "rtc_base/thread.h"
namespace webrtc {
// Observer interface for listening to AVAudioSession events.
class AudioSessionObserver {
public:
// Called when audio session interruption begins.
virtual void OnInterruptionBegin() = 0;
// Called when audio session interruption ends.
virtual void OnInterruptionEnd() = 0;
// Called when audio route changes.
virtual void OnValidRouteChange() = 0;
// Called when the ability to play or record changes.
virtual void OnCanPlayOrRecordChange(bool can_play_or_record) = 0;
virtual void OnChangedOutputVolume() = 0;
protected:
virtual ~AudioSessionObserver() {}
};
} // namespace webrtc
#endif // MODULES_AUDIO_DEVICE_IOS_AUDIO_SESSION_OBSERVER_H_

View File

@ -0,0 +1,139 @@
/*
* Copyright 2016 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.
*/
#ifndef MODULES_AUDIO_DEVICE_IOS_VOICE_PROCESSING_AUDIO_UNIT_H_
#define MODULES_AUDIO_DEVICE_IOS_VOICE_PROCESSING_AUDIO_UNIT_H_
#include <AudioUnit/AudioUnit.h>
namespace webrtc {
namespace ios_adm {
class VoiceProcessingAudioUnitObserver {
public:
// Callback function called on a real-time priority I/O thread from the audio
// unit. This method is used to signal that recorded audio is available.
virtual OSStatus OnDeliverRecordedData(AudioUnitRenderActionFlags* flags,
const AudioTimeStamp* time_stamp,
UInt32 bus_number,
UInt32 num_frames,
AudioBufferList* io_data) = 0;
// Callback function called on a real-time priority I/O thread from the audio
// unit. This method is used to provide audio samples to the audio unit.
virtual OSStatus OnGetPlayoutData(AudioUnitRenderActionFlags* io_action_flags,
const AudioTimeStamp* time_stamp,
UInt32 bus_number,
UInt32 num_frames,
AudioBufferList* io_data) = 0;
protected:
~VoiceProcessingAudioUnitObserver() {}
};
// Convenience class to abstract away the management of a Voice Processing
// I/O Audio Unit. The Voice Processing I/O unit has the same characteristics
// as the Remote I/O unit (supports full duplex low-latency audio input and
// output) and adds AEC for for two-way duplex communication. It also adds AGC,
// adjustment of voice-processing quality, and muting. Hence, ideal for
// VoIP applications.
class VoiceProcessingAudioUnit {
public:
explicit VoiceProcessingAudioUnit(VoiceProcessingAudioUnitObserver* observer);
~VoiceProcessingAudioUnit();
// TODO(tkchin): enum for state and state checking.
enum State : int32_t {
// Init() should be called.
kInitRequired,
// Audio unit created but not initialized.
kUninitialized,
// Initialized but not started. Equivalent to stopped.
kInitialized,
// Initialized and started.
kStarted,
};
// Number of bytes per audio sample for 16-bit signed integer representation.
static const UInt32 kBytesPerSample;
// Initializes this class by creating the underlying audio unit instance.
// Creates a Voice-Processing I/O unit and configures it for full-duplex
// audio. The selected stream format is selected to avoid internal resampling
// and to match the 10ms callback rate for WebRTC as well as possible.
// Does not intialize the audio unit.
bool Init();
VoiceProcessingAudioUnit::State GetState() const;
// Initializes the underlying audio unit with the given sample rate.
bool Initialize(Float64 sample_rate);
// Starts the underlying audio unit.
bool Start();
// Stops the underlying audio unit.
bool Stop();
// Uninitializes the underlying audio unit.
bool Uninitialize();
// Calls render on the underlying audio unit.
OSStatus Render(AudioUnitRenderActionFlags* flags,
const AudioTimeStamp* time_stamp,
UInt32 output_bus_number,
UInt32 num_frames,
AudioBufferList* io_data);
private:
// The C API used to set callbacks requires static functions. When these are
// called, they will invoke the relevant instance method by casting
// in_ref_con to VoiceProcessingAudioUnit*.
static OSStatus OnGetPlayoutData(void* in_ref_con,
AudioUnitRenderActionFlags* flags,
const AudioTimeStamp* time_stamp,
UInt32 bus_number,
UInt32 num_frames,
AudioBufferList* io_data);
static OSStatus OnDeliverRecordedData(void* in_ref_con,
AudioUnitRenderActionFlags* flags,
const AudioTimeStamp* time_stamp,
UInt32 bus_number,
UInt32 num_frames,
AudioBufferList* io_data);
// Notifies observer that samples are needed for playback.
OSStatus NotifyGetPlayoutData(AudioUnitRenderActionFlags* flags,
const AudioTimeStamp* time_stamp,
UInt32 bus_number,
UInt32 num_frames,
AudioBufferList* io_data);
// Notifies observer that recorded samples are available for render.
OSStatus NotifyDeliverRecordedData(AudioUnitRenderActionFlags* flags,
const AudioTimeStamp* time_stamp,
UInt32 bus_number,
UInt32 num_frames,
AudioBufferList* io_data);
// Returns the predetermined format with a specific sample rate. See
// implementation file for details on format.
AudioStreamBasicDescription GetFormat(Float64 sample_rate) const;
// Deletes the underlying audio unit.
void DisposeAudioUnit();
VoiceProcessingAudioUnitObserver* observer_;
AudioUnit vpio_unit_;
VoiceProcessingAudioUnit::State state_;
};
} // namespace ios_adm
} // namespace webrtc
#endif // MODULES_AUDIO_DEVICE_IOS_VOICE_PROCESSING_AUDIO_UNIT_H_

View File

@ -0,0 +1,470 @@
/*
* Copyright 2016 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.
*/
#import "voice_processing_audio_unit.h"
#include "rtc_base/checks.h"
#include "rtc_base/system/fallthrough.h"
#include "system_wrappers/include/metrics.h"
#import "WebRTC/RTCLogging.h"
#import "sdk/objc/Framework/Headers/WebRTC/RTCAudioSessionConfiguration.h"
#if !defined(NDEBUG)
static void LogStreamDescription(AudioStreamBasicDescription description) {
char formatIdString[5];
UInt32 formatId = CFSwapInt32HostToBig(description.mFormatID);
bcopy(&formatId, formatIdString, 4);
formatIdString[4] = '\0';
RTCLog(@"AudioStreamBasicDescription: {\n"
" mSampleRate: %.2f\n"
" formatIDString: %s\n"
" mFormatFlags: 0x%X\n"
" mBytesPerPacket: %u\n"
" mFramesPerPacket: %u\n"
" mBytesPerFrame: %u\n"
" mChannelsPerFrame: %u\n"
" mBitsPerChannel: %u\n"
" mReserved: %u\n}",
description.mSampleRate, formatIdString,
static_cast<unsigned int>(description.mFormatFlags),
static_cast<unsigned int>(description.mBytesPerPacket),
static_cast<unsigned int>(description.mFramesPerPacket),
static_cast<unsigned int>(description.mBytesPerFrame),
static_cast<unsigned int>(description.mChannelsPerFrame),
static_cast<unsigned int>(description.mBitsPerChannel),
static_cast<unsigned int>(description.mReserved));
}
#endif
namespace webrtc {
namespace ios_adm {
// Calls to AudioUnitInitialize() can fail if called back-to-back on different
// ADM instances. A fall-back solution is to allow multiple sequential calls
// with as small delay between each. This factor sets the max number of allowed
// initialization attempts.
static const int kMaxNumberOfAudioUnitInitializeAttempts = 5;
// A VP I/O unit's bus 1 connects to input hardware (microphone).
static const AudioUnitElement kInputBus = 1;
// A VP I/O unit's bus 0 connects to output hardware (speaker).
static const AudioUnitElement kOutputBus = 0;
// Returns the automatic gain control (AGC) state on the processed microphone
// signal. Should be on by default for Voice Processing audio units.
static OSStatus GetAGCState(AudioUnit audio_unit, UInt32* enabled) {
RTC_DCHECK(audio_unit);
UInt32 size = sizeof(*enabled);
OSStatus result = AudioUnitGetProperty(audio_unit,
kAUVoiceIOProperty_VoiceProcessingEnableAGC,
kAudioUnitScope_Global,
kInputBus,
enabled,
&size);
RTCLog(@"VPIO unit AGC: %u", static_cast<unsigned int>(*enabled));
return result;
}
VoiceProcessingAudioUnit::VoiceProcessingAudioUnit(
VoiceProcessingAudioUnitObserver* observer)
: observer_(observer), vpio_unit_(nullptr), state_(kInitRequired) {
RTC_DCHECK(observer);
}
VoiceProcessingAudioUnit::~VoiceProcessingAudioUnit() {
DisposeAudioUnit();
}
const UInt32 VoiceProcessingAudioUnit::kBytesPerSample = 2;
bool VoiceProcessingAudioUnit::Init() {
RTC_DCHECK_EQ(state_, kInitRequired);
// 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.
OSStatus result = noErr;
result = AudioComponentInstanceNew(found_vpio_unit_ref, &vpio_unit_);
if (result != noErr) {
vpio_unit_ = nullptr;
RTCLogError(@"AudioComponentInstanceNew failed. Error=%ld.", (long)result);
return false;
}
// Enable input on the input scope of the input element.
UInt32 enable_input = 1;
result = AudioUnitSetProperty(vpio_unit_, kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Input, kInputBus, &enable_input,
sizeof(enable_input));
if (result != noErr) {
DisposeAudioUnit();
RTCLogError(@"Failed to enable input on input scope of input element. "
"Error=%ld.",
(long)result);
return false;
}
// Enable output on the output scope of the output element.
UInt32 enable_output = 1;
result = AudioUnitSetProperty(vpio_unit_, kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Output, kOutputBus,
&enable_output, sizeof(enable_output));
if (result != noErr) {
DisposeAudioUnit();
RTCLogError(@"Failed to enable output on output scope of output element. "
"Error=%ld.",
(long)result);
return false;
}
// Specify the callback function that provides audio samples to the audio
// unit.
AURenderCallbackStruct render_callback;
render_callback.inputProc = OnGetPlayoutData;
render_callback.inputProcRefCon = this;
result = AudioUnitSetProperty(
vpio_unit_, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input,
kOutputBus, &render_callback, sizeof(render_callback));
if (result != noErr) {
DisposeAudioUnit();
RTCLogError(@"Failed to specify the render callback on the output bus. "
"Error=%ld.",
(long)result);
return false;
}
// 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;
result = AudioUnitSetProperty(
vpio_unit_, kAudioUnitProperty_ShouldAllocateBuffer,
kAudioUnitScope_Output, kInputBus, &flag, sizeof(flag));
if (result != noErr) {
DisposeAudioUnit();
RTCLogError(@"Failed to disable buffer allocation on the input bus. "
"Error=%ld.",
(long)result);
return false;
}
// 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 = OnDeliverRecordedData;
input_callback.inputProcRefCon = this;
result = AudioUnitSetProperty(vpio_unit_,
kAudioOutputUnitProperty_SetInputCallback,
kAudioUnitScope_Global, kInputBus,
&input_callback, sizeof(input_callback));
if (result != noErr) {
DisposeAudioUnit();
RTCLogError(@"Failed to specify the input callback on the input bus. "
"Error=%ld.",
(long)result);
return false;
}
state_ = kUninitialized;
return true;
}
VoiceProcessingAudioUnit::State VoiceProcessingAudioUnit::GetState() const {
return state_;
}
bool VoiceProcessingAudioUnit::Initialize(Float64 sample_rate) {
RTC_DCHECK_GE(state_, kUninitialized);
RTCLog(@"Initializing audio unit with sample rate: %f", sample_rate);
OSStatus result = noErr;
AudioStreamBasicDescription format = GetFormat(sample_rate);
UInt32 size = sizeof(format);
#if !defined(NDEBUG)
LogStreamDescription(format);
#endif
// Set the format on the output scope of the input element/bus.
result =
AudioUnitSetProperty(vpio_unit_, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Output, kInputBus, &format, size);
if (result != noErr) {
RTCLogError(@"Failed to set format on output scope of input bus. "
"Error=%ld.",
(long)result);
return false;
}
// Set the format on the input scope of the output element/bus.
result =
AudioUnitSetProperty(vpio_unit_, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input, kOutputBus, &format, size);
if (result != noErr) {
RTCLogError(@"Failed to set format on input scope of output bus. "
"Error=%ld.",
(long)result);
return false;
}
// Initialize the Voice Processing I/O unit instance.
// Calls to AudioUnitInitialize() can fail if called back-to-back on
// different ADM instances. The error message in this case is -66635 which is
// undocumented. Tests have shown that calling AudioUnitInitialize a second
// time, after a short sleep, avoids this issue.
// See webrtc:5166 for details.
int failed_initalize_attempts = 0;
result = AudioUnitInitialize(vpio_unit_);
while (result != noErr) {
RTCLogError(@"Failed to initialize the Voice Processing I/O unit. "
"Error=%ld.",
(long)result);
++failed_initalize_attempts;
if (failed_initalize_attempts == kMaxNumberOfAudioUnitInitializeAttempts) {
// Max number of initialization attempts exceeded, hence abort.
RTCLogError(@"Too many initialization attempts.");
return false;
}
RTCLog(@"Pause 100ms and try audio unit initialization again...");
[NSThread sleepForTimeInterval:0.1f];
result = AudioUnitInitialize(vpio_unit_);
}
if (result == noErr) {
RTCLog(@"Voice Processing I/O unit is now initialized.");
}
// AGC should be enabled by default for Voice Processing I/O units but it is
// checked below and enabled explicitly if needed. This scheme is used
// to be absolutely sure that the AGC is enabled since we have seen cases
// where only zeros are recorded and a disabled AGC could be one of the
// reasons why it happens.
int agc_was_enabled_by_default = 0;
UInt32 agc_is_enabled = 0;
result = GetAGCState(vpio_unit_, &agc_is_enabled);
if (result != noErr) {
RTCLogError(@"Failed to get AGC state (1st attempt). "
"Error=%ld.",
(long)result);
// Example of error code: kAudioUnitErr_NoConnection (-10876).
// All error codes related to audio units are negative and are therefore
// converted into a postive value to match the UMA APIs.
RTC_HISTOGRAM_COUNTS_SPARSE_100000(
"WebRTC.Audio.GetAGCStateErrorCode1", (-1) * result);
} else if (agc_is_enabled) {
// Remember that the AGC was enabled by default. Will be used in UMA.
agc_was_enabled_by_default = 1;
} else {
// AGC was initially disabled => try to enable it explicitly.
UInt32 enable_agc = 1;
result =
AudioUnitSetProperty(vpio_unit_,
kAUVoiceIOProperty_VoiceProcessingEnableAGC,
kAudioUnitScope_Global, kInputBus, &enable_agc,
sizeof(enable_agc));
if (result != noErr) {
RTCLogError(@"Failed to enable the built-in AGC. "
"Error=%ld.",
(long)result);
RTC_HISTOGRAM_COUNTS_SPARSE_100000(
"WebRTC.Audio.SetAGCStateErrorCode", (-1) * result);
}
result = GetAGCState(vpio_unit_, &agc_is_enabled);
if (result != noErr) {
RTCLogError(@"Failed to get AGC state (2nd attempt). "
"Error=%ld.",
(long)result);
RTC_HISTOGRAM_COUNTS_SPARSE_100000(
"WebRTC.Audio.GetAGCStateErrorCode2", (-1) * result);
}
}
// Track if the built-in AGC was enabled by default (as it should) or not.
RTC_HISTOGRAM_BOOLEAN("WebRTC.Audio.BuiltInAGCWasEnabledByDefault",
agc_was_enabled_by_default);
RTCLog(@"WebRTC.Audio.BuiltInAGCWasEnabledByDefault: %d",
agc_was_enabled_by_default);
// As a final step, add an UMA histogram for tracking the AGC state.
// At this stage, the AGC should be enabled, and if it is not, more work is
// needed to find out the root cause.
RTC_HISTOGRAM_BOOLEAN("WebRTC.Audio.BuiltInAGCIsEnabled", agc_is_enabled);
RTCLog(@"WebRTC.Audio.BuiltInAGCIsEnabled: %u",
static_cast<unsigned int>(agc_is_enabled));
state_ = kInitialized;
return true;
}
bool VoiceProcessingAudioUnit::Start() {
RTC_DCHECK_GE(state_, kUninitialized);
RTCLog(@"Starting audio unit.");
OSStatus result = AudioOutputUnitStart(vpio_unit_);
if (result != noErr) {
RTCLogError(@"Failed to start audio unit. Error=%ld", (long)result);
return false;
} else {
RTCLog(@"Started audio unit");
}
state_ = kStarted;
return true;
}
bool VoiceProcessingAudioUnit::Stop() {
RTC_DCHECK_GE(state_, kUninitialized);
RTCLog(@"Stopping audio unit.");
OSStatus result = AudioOutputUnitStop(vpio_unit_);
if (result != noErr) {
RTCLogError(@"Failed to stop audio unit. Error=%ld", (long)result);
return false;
} else {
RTCLog(@"Stopped audio unit");
}
state_ = kInitialized;
return true;
}
bool VoiceProcessingAudioUnit::Uninitialize() {
RTC_DCHECK_GE(state_, kUninitialized);
RTCLog(@"Unintializing audio unit.");
OSStatus result = AudioUnitUninitialize(vpio_unit_);
if (result != noErr) {
RTCLogError(@"Failed to uninitialize audio unit. Error=%ld", (long)result);
return false;
} else {
RTCLog(@"Uninitialized audio unit.");
}
state_ = kUninitialized;
return true;
}
OSStatus VoiceProcessingAudioUnit::Render(AudioUnitRenderActionFlags* flags,
const AudioTimeStamp* time_stamp,
UInt32 output_bus_number,
UInt32 num_frames,
AudioBufferList* io_data) {
RTC_DCHECK(vpio_unit_) << "Init() not called.";
OSStatus result = AudioUnitRender(vpio_unit_, flags, time_stamp,
output_bus_number, num_frames, io_data);
if (result != noErr) {
RTCLogError(@"Failed to render audio unit. Error=%ld", (long)result);
}
return result;
}
OSStatus VoiceProcessingAudioUnit::OnGetPlayoutData(
void* in_ref_con,
AudioUnitRenderActionFlags* flags,
const AudioTimeStamp* time_stamp,
UInt32 bus_number,
UInt32 num_frames,
AudioBufferList* io_data) {
VoiceProcessingAudioUnit* audio_unit =
static_cast<VoiceProcessingAudioUnit*>(in_ref_con);
return audio_unit->NotifyGetPlayoutData(flags, time_stamp, bus_number,
num_frames, io_data);
}
OSStatus VoiceProcessingAudioUnit::OnDeliverRecordedData(
void* in_ref_con,
AudioUnitRenderActionFlags* flags,
const AudioTimeStamp* time_stamp,
UInt32 bus_number,
UInt32 num_frames,
AudioBufferList* io_data) {
VoiceProcessingAudioUnit* audio_unit =
static_cast<VoiceProcessingAudioUnit*>(in_ref_con);
return audio_unit->NotifyDeliverRecordedData(flags, time_stamp, bus_number,
num_frames, io_data);
}
OSStatus VoiceProcessingAudioUnit::NotifyGetPlayoutData(
AudioUnitRenderActionFlags* flags,
const AudioTimeStamp* time_stamp,
UInt32 bus_number,
UInt32 num_frames,
AudioBufferList* io_data) {
return observer_->OnGetPlayoutData(flags, time_stamp, bus_number, num_frames,
io_data);
}
OSStatus VoiceProcessingAudioUnit::NotifyDeliverRecordedData(
AudioUnitRenderActionFlags* flags,
const AudioTimeStamp* time_stamp,
UInt32 bus_number,
UInt32 num_frames,
AudioBufferList* io_data) {
return observer_->OnDeliverRecordedData(flags, time_stamp, bus_number,
num_frames, io_data);
}
AudioStreamBasicDescription VoiceProcessingAudioUnit::GetFormat(
Float64 sample_rate) const {
// 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 format;
RTC_DCHECK_EQ(1, kRTCAudioSessionPreferredNumberOfChannels);
format.mSampleRate = sample_rate;
format.mFormatID = kAudioFormatLinearPCM;
format.mFormatFlags =
kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
format.mBytesPerPacket = kBytesPerSample;
format.mFramesPerPacket = 1; // uncompressed.
format.mBytesPerFrame = kBytesPerSample;
format.mChannelsPerFrame = kRTCAudioSessionPreferredNumberOfChannels;
format.mBitsPerChannel = 8 * kBytesPerSample;
return format;
}
void VoiceProcessingAudioUnit::DisposeAudioUnit() {
if (vpio_unit_) {
switch (state_) {
case kStarted:
Stop();
// Fall through.
RTC_FALLTHROUGH();
case kInitialized:
Uninitialize();
break;
case kUninitialized:
RTC_FALLTHROUGH();
case kInitRequired:
break;
}
RTCLog(@"Disposing audio unit.");
OSStatus result = AudioComponentInstanceDispose(vpio_unit_);
if (result != noErr) {
RTCLogError(@"AudioComponentInstanceDispose failed. Error=%ld.",
(long)result);
}
vpio_unit_ = nullptr;
}
}
} // namespace ios_adm
} // namespace webrtc