Commit 8ee53db6 authored by Oskar Sundbom's avatar Oskar Sundbom Committed by Commit Bot

Implement AEC output device switching for macOS

CL 5 of 5 to plumb echo canceller output device information from the renderer into
native AEC implementations. For a full outline of the changes, see:
https://docs.google.com/document/d/1ZH3lk4MdoEtmOleFD3ip6X0JF2-6_24_NTUjxshvM3U/edit?usp=sharing

Cq-Include-Trybots: luci.chromium.try:android_optional_gpu_tests_rel;luci.chromium.try:linux_optional_gpu_tests_rel;luci.chromium.try:mac_optional_gpu_tests_rel;luci.chromium.try:win_optional_gpu_tests_rel
Change-Id: I945a8e30fbf1410a00167c2a50930639c20835be
Bug: 837661
Reviewed-on: https://chromium-review.googlesource.com/1032594Reviewed-by: default avatarDale Curtis <dalecurtis@chromium.org>
Reviewed-by: default avatarRobert Sesek <rsesek@chromium.org>
Commit-Queue: Oskar Sundbom <ossu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#561677}
parent 51c414eb
...@@ -12,14 +12,18 @@ ...@@ -12,14 +12,18 @@
#include "base/bind.h" #include "base/bind.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/mac/foundation_util.h"
#include "base/mac/mac_logging.h" #include "base/mac/mac_logging.h"
#include "base/mac/mac_util.h" #include "base/mac/mac_util.h"
#include "base/mac/scoped_cftyperef.h"
#include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h" #include "base/metrics/histogram_macros.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "base/strings/sys_string_conversions.h"
#include "base/sys_info.h" #include "base/sys_info.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "base/trace_event/trace_event.h" #include "base/trace_event/trace_event.h"
#include "media/audio/mac/core_audio_util_mac.h"
#include "media/audio/mac/scoped_audio_unit.h" #include "media/audio/mac/scoped_audio_unit.h"
#include "media/base/audio_bus.h" #include "media/base/audio_bus.h"
#include "media/base/audio_timestamp_helper.h" #include "media/base/audio_timestamp_helper.h"
...@@ -35,6 +39,13 @@ OSStatus AudioDeviceDuck(AudioDeviceID inDevice, ...@@ -35,6 +39,13 @@ OSStatus AudioDeviceDuck(AudioDeviceID inDevice,
Float32 inRampDuration) __attribute__((weak_import)); Float32 inRampDuration) __attribute__((weak_import));
} }
void UndoDucking(AudioDeviceID output_device_id) {
if (AudioDeviceDuck != nullptr) {
// Ramp the volume back up over half a second.
AudioDeviceDuck(output_device_id, 1.0, nullptr, 0.5);
}
}
} // namespace } // namespace
namespace media { namespace media {
...@@ -155,6 +166,45 @@ static void AddSystemInfoToUMA(bool is_on_battery, int num_resumes) { ...@@ -155,6 +166,45 @@ static void AddSystemInfoToUMA(bool is_on_battery, int num_resumes) {
DVLOG(1) << "resume events: " << num_resumes; DVLOG(1) << "resume events: " << num_resumes;
} }
// Finds the first subdevice, in an aggregate device, with output streams.
static AudioDeviceID FindFirstOutputSubdevice(
AudioDeviceID aggregate_device_id) {
const AudioObjectPropertyAddress property_address = {
kAudioAggregateDevicePropertyFullSubDeviceList,
kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster};
base::ScopedCFTypeRef<CFArrayRef> subdevices;
UInt32 size = sizeof(subdevices);
OSStatus result = AudioObjectGetPropertyData(
aggregate_device_id, &property_address, 0 /* inQualifierDataSize */,
nullptr /* inQualifierData */, &size, subdevices.InitializeInto());
if (result != noErr) {
OSSTATUS_LOG(WARNING, result)
<< "Failed to read property "
<< kAudioAggregateDevicePropertyFullSubDeviceList << " for device "
<< aggregate_device_id;
return kAudioObjectUnknown;
}
AudioDeviceID output_subdevice_id = kAudioObjectUnknown;
DCHECK_EQ(CFGetTypeID(subdevices), CFArrayGetTypeID());
const CFIndex count = CFArrayGetCount(subdevices);
for (CFIndex i = 0; i != count; ++i) {
CFStringRef value =
base::mac::CFCast<CFStringRef>(CFArrayGetValueAtIndex(subdevices, i));
if (value) {
std::string uid = base::SysCFStringRefToUTF8(value);
output_subdevice_id = AudioManagerMac::GetAudioDeviceIdByUId(false, uid);
if (output_subdevice_id != kAudioObjectUnknown &&
core_audio_mac::GetNumStreams(output_subdevice_id, false) > 0) {
break;
}
}
}
return output_subdevice_id;
}
// See "Technical Note TN2091 - Device input using the HAL Output Audio Unit" // See "Technical Note TN2091 - Device input using the HAL Output Audio Unit"
// http://developer.apple.com/library/mac/#technotes/tn2091/_index.html // http://developer.apple.com/library/mac/#technotes/tn2091/_index.html
// for more details and background regarding this implementation. // for more details and background regarding this implementation.
...@@ -184,6 +234,7 @@ AUAudioInputStream::AUAudioInputStream( ...@@ -184,6 +234,7 @@ AUAudioInputStream::AUAudioInputStream(
noise_reduction_suppressed_(false), noise_reduction_suppressed_(false),
use_voice_processing_(voice_processing_mode == use_voice_processing_(voice_processing_mode ==
AudioManagerBase::VoiceProcessingMode::kEnabled), AudioManagerBase::VoiceProcessingMode::kEnabled),
output_device_id_for_aec_(kAudioObjectUnknown),
last_sample_time_(0.0), last_sample_time_(0.0),
last_number_of_frames_(0), last_number_of_frames_(0),
total_lost_frames_(0), total_lost_frames_(0),
...@@ -194,6 +245,9 @@ AUAudioInputStream::AUAudioInputStream( ...@@ -194,6 +245,9 @@ AUAudioInputStream::AUAudioInputStream(
CHECK(!log_callback_.Equals(AudioManager::LogCallback())); CHECK(!log_callback_.Equals(AudioManager::LogCallback()));
if (use_voice_processing_) { if (use_voice_processing_) {
DCHECK(input_params.channels() == 1 || input_params.channels() == 2); DCHECK(input_params.channels() == 1 || input_params.channels() == 2);
const bool got_default_device =
AudioManagerMac::GetDefaultOutputDevice(&output_device_id_for_aec_);
DCHECK(got_default_device);
} }
const SampleFormat kSampleFormat = kSampleFormatS16; const SampleFormat kSampleFormat = kSampleFormatS16;
...@@ -484,7 +538,7 @@ bool AUAudioInputStream::OpenVoiceProcessingAU() { ...@@ -484,7 +538,7 @@ bool AUAudioInputStream::OpenVoiceProcessingAU() {
return false; return false;
} }
// Next, set the audio device to be the Audio Unit's current device. // Next, set the audio device to be the Audio Unit's input device.
result = result =
AudioUnitSetProperty(audio_unit_, kAudioOutputUnitProperty_CurrentDevice, AudioUnitSetProperty(audio_unit_, kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global, AUElement::INPUT, kAudioUnitScope_Global, AUElement::INPUT,
...@@ -495,6 +549,17 @@ bool AUAudioInputStream::OpenVoiceProcessingAU() { ...@@ -495,6 +549,17 @@ bool AUAudioInputStream::OpenVoiceProcessingAU() {
return false; return false;
} }
// Followed by the audio device to be the Audio Unit's output device.
result = AudioUnitSetProperty(
audio_unit_, kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global, AUElement::OUTPUT, &output_device_id_for_aec_,
sizeof(output_device_id_for_aec_));
if (result != noErr) {
HandleError(result);
return false;
}
// Register the input procedure for the AUHAL. This procedure will be called // Register the input procedure for the AUHAL. This procedure will be called
// when the AUHAL has received new data from the input device. // when the AUHAL has received new data from the input device.
AURenderCallbackStruct callback; AURenderCallbackStruct callback;
...@@ -604,21 +669,7 @@ bool AUAudioInputStream::OpenVoiceProcessingAU() { ...@@ -604,21 +669,7 @@ bool AUAudioInputStream::OpenVoiceProcessingAU() {
return false; return false;
} }
if (AudioDeviceDuck != nullptr) { UndoDucking(output_device_id_for_aec_);
// Undo the ducking.
// Obtain the AudioDeviceID of the default output AudioDevice.
const AudioObjectPropertyAddress pa = {
kAudioHardwarePropertyDefaultOutputDevice,
kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster};
AudioDeviceID output_device = 0;
UInt32 size = sizeof(output_device);
OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &pa,
0, 0, &size, &output_device);
if (result == noErr) {
// Ramp the volume back up over half a second.
AudioDeviceDuck(output_device, 1.0, nullptr, 0.5);
}
}
return true; return true;
} }
...@@ -894,7 +945,76 @@ bool AUAudioInputStream::IsMuted() { ...@@ -894,7 +945,76 @@ bool AUAudioInputStream::IsMuted() {
void AUAudioInputStream::SetOutputDeviceForAec( void AUAudioInputStream::SetOutputDeviceForAec(
const std::string& output_device_id) { const std::string& output_device_id) {
// TODO(ossu): Implement. if (!use_voice_processing_)
return;
AudioDeviceID audio_device_id =
AudioManagerMac::GetAudioDeviceIdByUId(false, output_device_id);
if (audio_device_id == output_device_id_for_aec_)
return;
if (audio_device_id == kAudioObjectUnknown) {
log_callback_.Run(
base::StringPrintf("AU in: Unable to resolve output device id '%s'",
output_device_id.c_str()));
return;
}
// If the selected device is an aggregate device, try to use the first output
// device of the aggregate device instead.
if (core_audio_mac::GetDeviceTransportType(audio_device_id) ==
kAudioDeviceTransportTypeAggregate) {
const AudioDeviceID output_subdevice_id =
FindFirstOutputSubdevice(audio_device_id);
if (output_subdevice_id == kAudioObjectUnknown) {
log_callback_.Run(base::StringPrintf(
"AU in: Unable to find an output subdevice in aggregate devie '%s'",
output_device_id.c_str()));
return;
}
audio_device_id = output_subdevice_id;
}
if (audio_device_id != output_device_id_for_aec_) {
log_callback_.Run(
base::StringPrintf("AU in: Output device for AEC changed to '%s' (%d)",
output_device_id.c_str(), audio_device_id));
SwitchVoiceProcessingOutputDevice(audio_device_id);
}
}
void AUAudioInputStream::SwitchVoiceProcessingOutputDevice(
AudioDeviceID output_device_id) {
DCHECK(use_voice_processing_);
output_device_id_for_aec_ = output_device_id;
if (!audio_unit_)
return;
OSStatus result = noErr;
if (IsRunning()) {
result = AudioOutputUnitStop(audio_unit_);
DCHECK_EQ(result, noErr);
}
CloseAudioUnit();
SetInputCallbackIsActive(false);
ReportAndResetStats();
io_buffer_frame_size_ = 0;
got_input_callback_ = false;
OpenVoiceProcessingAU();
result = AudioOutputUnitStart(audio_unit_);
if (result != noErr) {
OSSTATUS_DLOG(ERROR, result) << "Failed to start acquiring data";
Stop();
return;
}
log_callback_.Run(base::StringPrintf(
"AU in: Successfully reinitialized AEC for output device id=%d.",
output_device_id));
} }
// static // static
......
...@@ -148,6 +148,9 @@ class MEDIA_EXPORT AUAudioInputStream ...@@ -148,6 +148,9 @@ class MEDIA_EXPORT AUAudioInputStream
// Uninitializes the audio unit if needed. // Uninitializes the audio unit if needed.
void CloseAudioUnit(); void CloseAudioUnit();
// Reinitializes the AudioUnit to use a new output device.
void SwitchVoiceProcessingOutputDevice(AudioDeviceID output_device_id);
// Adds extra UMA stats when it has been detected that startup failed. // Adds extra UMA stats when it has been detected that startup failed.
void AddHistogramsForFailedStartup(); void AddHistogramsForFailedStartup();
...@@ -253,6 +256,9 @@ class MEDIA_EXPORT AUAudioInputStream ...@@ -253,6 +256,9 @@ class MEDIA_EXPORT AUAudioInputStream
// and gain control on Sierra and later. // and gain control on Sierra and later.
const bool use_voice_processing_; const bool use_voice_processing_;
// The of the output device to cancel echo from.
AudioDeviceID output_device_id_for_aec_;
// Stores the timestamp of the previous audio buffer provided by the OS. // Stores the timestamp of the previous audio buffer provided by the OS.
// We use this in combination with |last_number_of_frames_| to detect when // We use this in combination with |last_number_of_frames_| to detect when
// the OS has decided to skip providing frames (i.e. a glitch). // the OS has decided to skip providing frames (i.e. a glitch).
......
...@@ -160,8 +160,9 @@ static void GetAudioDeviceInfo(bool is_input, ...@@ -160,8 +160,9 @@ static void GetAudioDeviceInfo(bool is_input,
} }
} }
static AudioDeviceID GetAudioDeviceIdByUId(bool is_input, AudioDeviceID AudioManagerMac::GetAudioDeviceIdByUId(
const std::string& device_id) { bool is_input,
const std::string& device_id) {
DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread()); DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
AudioObjectPropertyAddress property_address = { AudioObjectPropertyAddress property_address = {
kAudioHardwarePropertyDevices, kAudioHardwarePropertyDevices,
...@@ -233,7 +234,7 @@ static bool GetDefaultDevice(AudioDeviceID* device, bool input) { ...@@ -233,7 +234,7 @@ static bool GetDefaultDevice(AudioDeviceID* device, bool input) {
return true; return true;
} }
static bool GetDefaultOutputDevice(AudioDeviceID* device) { bool AudioManagerMac::GetDefaultOutputDevice(AudioDeviceID* device) {
return GetDefaultDevice(device, false); return GetDefaultDevice(device, false);
} }
......
...@@ -77,6 +77,9 @@ class MEDIA_EXPORT AudioManagerMac : public AudioManagerBase { ...@@ -77,6 +77,9 @@ class MEDIA_EXPORT AudioManagerMac : public AudioManagerBase {
static int HardwareSampleRateForDevice(AudioDeviceID device_id); static int HardwareSampleRateForDevice(AudioDeviceID device_id);
static int HardwareSampleRate(); static int HardwareSampleRate();
static bool GetDefaultOutputDevice(AudioDeviceID* device);
static AudioDeviceID GetAudioDeviceIdByUId(bool is_input,
const std::string& device_id);
// OSX has issues with starting streams as the system goes into suspend and // OSX has issues with starting streams as the system goes into suspend and
// immediately after it wakes up from resume. See http://crbug.com/160920. // immediately after it wakes up from resume. See http://crbug.com/160920.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment