Index: media/audio/mac/aggregate_device_manager.cc |
diff --git a/media/audio/mac/aggregate_device_manager.cc b/media/audio/mac/aggregate_device_manager.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..78841040f8430096b7c4fc34b460a42afe1f6ffa |
--- /dev/null |
+++ b/media/audio/mac/aggregate_device_manager.cc |
@@ -0,0 +1,363 @@ |
+// Copyright 2013 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "media/audio/mac/aggregate_device_manager.h" |
+ |
+#include <CoreAudio/AudioHardware.h> |
+#include <string> |
+ |
+#include "base/mac/mac_logging.h" |
+#include "base/mac/scoped_cftyperef.h" |
+#include "media/audio/audio_parameters.h" |
+#include "media/audio/mac/audio_manager_mac.h" |
+ |
+using base::mac::ScopedCFTypeRef; |
+ |
+namespace media { |
+ |
+AggregateDeviceManager::AggregateDeviceManager() |
+ : plugin_id_(kAudioObjectUnknown), |
+ input_device_(kAudioDeviceUnknown), |
+ output_device_(kAudioDeviceUnknown), |
+ aggregate_device_(kAudioObjectUnknown) { |
+} |
+ |
+AggregateDeviceManager::~AggregateDeviceManager() { |
+ DestroyAggregateDevice(); |
+} |
+ |
+AudioDeviceID AggregateDeviceManager::GetDefaultAggregateDevice() { |
+ // Use a lazily created aggregate device if it's already available |
+ // and still appropriate. |
+ if (aggregate_device_ != kAudioObjectUnknown) { |
+ // TODO(crogers): handle default device changes for synchronized I/O. |
+ // For now, we check to make sure the default devices haven't changed |
+ // since we lazily created the aggregate device. |
+ AudioDeviceID current_input_device; |
+ AudioDeviceID current_output_device; |
+ AudioManagerMac::GetDefaultInputDevice(¤t_input_device); |
+ AudioManagerMac::GetDefaultOutputDevice(¤t_output_device); |
+ |
+ if (current_input_device == input_device_ && |
+ current_output_device == output_device_) |
+ return aggregate_device_; |
+ |
+ // For now, once lazily created don't attempt to create another |
+ // aggregate device. |
+ return kAudioDeviceUnknown; |
+ } |
+ |
+ AudioManagerMac::GetDefaultInputDevice(&input_device_); |
+ AudioManagerMac::GetDefaultOutputDevice(&output_device_); |
+ |
+ // Only create an aggregrate device if the clock domains match. |
+ UInt32 input_clockdomain = GetClockDomain(input_device_); |
+ UInt32 output_clockdomain = GetClockDomain(output_device_); |
+ DVLOG(1) << "input_clockdomain: " << input_clockdomain; |
+ DVLOG(1) << "output_clockdomain: " << output_clockdomain; |
+ |
+ if (input_clockdomain == 0 || input_clockdomain != output_clockdomain) |
+ return kAudioDeviceUnknown; |
+ |
+ OSStatus result = CreateAggregateDevice( |
+ input_device_, |
+ output_device_, |
+ &aggregate_device_); |
+ if (result != noErr) |
+ DestroyAggregateDevice(); |
+ |
+ return aggregate_device_; |
+} |
+ |
+CFStringRef AggregateDeviceManager::GetDeviceUID(AudioDeviceID id) { |
+ static const AudioObjectPropertyAddress kDeviceUIDAddress = { |
+ kAudioDevicePropertyDeviceUID, |
+ kAudioObjectPropertyScopeGlobal, |
+ kAudioObjectPropertyElementMaster |
+ }; |
+ |
+ // As stated in the CoreAudio header (AudioHardwareBase.h), |
+ // the caller is responsible for releasing the device_UID. |
+ CFStringRef device_UID; |
+ UInt32 size = sizeof(device_UID); |
+ OSStatus result = AudioObjectGetPropertyData( |
+ id, |
+ &kDeviceUIDAddress, |
+ 0, |
+ 0, |
+ &size, |
+ &device_UID); |
+ |
+ return (result == noErr) ? device_UID : NULL; |
+} |
+ |
+void AggregateDeviceManager::GetDeviceName( |
+ AudioDeviceID id, char* name, UInt32 size) { |
+ static const AudioObjectPropertyAddress kDeviceNameAddress = { |
+ kAudioDevicePropertyDeviceName, |
+ kAudioObjectPropertyScopeGlobal, |
+ kAudioObjectPropertyElementMaster |
+ }; |
+ |
+ OSStatus result = AudioObjectGetPropertyData( |
+ id, |
+ &kDeviceNameAddress, |
+ 0, |
+ 0, |
+ &size, |
+ name); |
+ |
+ if (result != noErr && size > 0) |
+ name[0] = 0; |
+} |
+ |
+UInt32 AggregateDeviceManager::GetClockDomain(AudioDeviceID device_id) { |
+ static const AudioObjectPropertyAddress kClockDomainAddress = { |
+ kAudioDevicePropertyClockDomain, |
+ kAudioObjectPropertyScopeGlobal, |
+ kAudioObjectPropertyElementMaster |
+ }; |
+ |
+ UInt32 clockdomain = 0; |
+ UInt32 size = sizeof(UInt32); |
+ OSStatus result = AudioObjectGetPropertyData( |
+ device_id, |
+ &kClockDomainAddress, |
+ 0, |
+ 0, |
+ &size, |
+ &clockdomain); |
+ |
+ return (result == noErr) ? clockdomain : 0; |
+} |
+ |
+OSStatus AggregateDeviceManager::GetPluginID(AudioObjectID* id) { |
+ DCHECK(id); |
+ |
+ // Get the audio hardware plugin. |
+ CFStringRef bundle_name = CFSTR("com.apple.audio.CoreAudio"); |
+ |
+ AudioValueTranslation plugin_translation; |
+ plugin_translation.mInputData = &bundle_name; |
+ plugin_translation.mInputDataSize = sizeof(bundle_name); |
+ plugin_translation.mOutputData = id; |
+ plugin_translation.mOutputDataSize = sizeof(*id); |
+ |
+ static const AudioObjectPropertyAddress kPlugInAddress = { |
+ kAudioHardwarePropertyPlugInForBundleID, |
+ kAudioObjectPropertyScopeGlobal, |
+ kAudioObjectPropertyElementMaster |
+ }; |
+ |
+ UInt32 size = sizeof(plugin_translation); |
+ OSStatus result = AudioObjectGetPropertyData( |
+ kAudioObjectSystemObject, |
+ &kPlugInAddress, |
+ 0, |
+ 0, |
+ &size, |
+ &plugin_translation); |
+ |
+ DVLOG(1) << "CoreAudio plugin ID: " << *id; |
+ |
+ return result; |
+} |
+ |
+CFMutableDictionaryRef |
+AggregateDeviceManager::CreateAggregateDeviceDictionary( |
+ AudioDeviceID input_id, |
+ AudioDeviceID output_id) { |
+ CFMutableDictionaryRef aggregate_device_dict = CFDictionaryCreateMutable( |
+ NULL, |
+ 0, |
+ &kCFTypeDictionaryKeyCallBacks, |
+ &kCFTypeDictionaryValueCallBacks); |
+ if (!aggregate_device_dict) |
+ return NULL; |
+ |
+ const CFStringRef kAggregateDeviceName = |
+ CFSTR("ChromeAggregateAudioDevice"); |
+ const CFStringRef kAggregateDeviceUID = |
+ CFSTR("com.google.chrome.AggregateAudioDevice"); |
+ |
+ // Add name and UID of the device to the dictionary. |
+ CFDictionaryAddValue( |
+ aggregate_device_dict, |
+ CFSTR(kAudioAggregateDeviceNameKey), |
+ kAggregateDeviceName); |
+ CFDictionaryAddValue( |
+ aggregate_device_dict, |
+ CFSTR(kAudioAggregateDeviceUIDKey), |
+ kAggregateDeviceUID); |
+ |
+ // Add a "private aggregate key" to the dictionary. |
+ // The 1 value means that the created aggregate device will |
+ // only be accessible from the process that created it, and |
+ // won't be visible to outside processes. |
+ int value = 1; |
+ ScopedCFTypeRef<CFNumberRef> aggregate_device_number(CFNumberCreate( |
+ NULL, |
+ kCFNumberIntType, |
+ &value)); |
+ CFDictionaryAddValue( |
+ aggregate_device_dict, |
+ CFSTR(kAudioAggregateDeviceIsPrivateKey), |
+ aggregate_device_number); |
+ |
+ return aggregate_device_dict; |
+} |
+ |
+CFMutableArrayRef |
+AggregateDeviceManager::CreateSubDeviceArray( |
+ CFStringRef input_device_UID, CFStringRef output_device_UID) { |
+ CFMutableArrayRef sub_devices_array = CFArrayCreateMutable( |
+ NULL, |
+ 0, |
+ &kCFTypeArrayCallBacks); |
+ |
+ CFArrayAppendValue(sub_devices_array, input_device_UID); |
+ CFArrayAppendValue(sub_devices_array, output_device_UID); |
+ |
+ return sub_devices_array; |
+} |
+ |
+OSStatus AggregateDeviceManager::CreateAggregateDevice( |
+ AudioDeviceID input_id, |
+ AudioDeviceID output_id, |
+ AudioDeviceID* aggregate_device) { |
+ DCHECK(aggregate_device); |
+ |
+ const size_t kMaxDeviceNameLength = 256; |
+ |
+ scoped_ptr<char[]> input_device_name(new char[kMaxDeviceNameLength]); |
+ GetDeviceName( |
+ input_id, |
+ input_device_name.get(), |
+ sizeof(input_device_name)); |
+ DVLOG(1) << "Input device: \n" << input_device_name; |
+ |
+ scoped_ptr<char[]> output_device_name(new char[kMaxDeviceNameLength]); |
+ GetDeviceName( |
+ output_id, |
+ output_device_name.get(), |
+ sizeof(output_device_name)); |
+ DVLOG(1) << "Output device: \n" << output_device_name; |
+ |
+ OSStatus result = GetPluginID(&plugin_id_); |
+ if (result != noErr) |
+ return result; |
+ |
+ // Create a dictionary for the aggregate device. |
+ ScopedCFTypeRef<CFMutableDictionaryRef> aggregate_device_dict( |
+ CreateAggregateDeviceDictionary(input_id, output_id)); |
+ if (!aggregate_device_dict) |
+ return -1; |
+ |
+ // Create the aggregate device. |
+ static const AudioObjectPropertyAddress kCreateAggregateDeviceAddress = { |
+ kAudioPlugInCreateAggregateDevice, |
+ kAudioObjectPropertyScopeGlobal, |
+ kAudioObjectPropertyElementMaster |
+ }; |
+ |
+ UInt32 size = sizeof(*aggregate_device); |
+ result = AudioObjectGetPropertyData( |
+ plugin_id_, |
+ &kCreateAggregateDeviceAddress, |
+ sizeof(aggregate_device_dict), |
+ &aggregate_device_dict, |
+ &size, |
+ aggregate_device); |
+ if (result != noErr) { |
+ DLOG(ERROR) << "Error creating aggregate audio device!"; |
+ return result; |
+ } |
+ |
+ // Set the sub-devices for the aggregate device. |
+ // In this case we use two: the input and output devices. |
+ |
+ ScopedCFTypeRef<CFStringRef> input_device_UID(GetDeviceUID(input_id)); |
+ ScopedCFTypeRef<CFStringRef> output_device_UID(GetDeviceUID(output_id)); |
+ if (!input_device_UID || !output_device_UID) { |
+ DLOG(ERROR) << "Error getting audio device UID strings."; |
+ return -1; |
+ } |
+ |
+ ScopedCFTypeRef<CFMutableArrayRef> sub_devices_array( |
+ CreateSubDeviceArray(input_device_UID, output_device_UID)); |
+ if (sub_devices_array == NULL) { |
+ DLOG(ERROR) << "Error creating sub-devices array."; |
+ return -1; |
+ } |
+ |
+ static const AudioObjectPropertyAddress kSetSubDevicesAddress = { |
+ kAudioAggregateDevicePropertyFullSubDeviceList, |
+ kAudioObjectPropertyScopeGlobal, |
+ kAudioObjectPropertyElementMaster |
+ }; |
+ |
+ size = sizeof(CFMutableArrayRef); |
+ result = AudioObjectSetPropertyData( |
+ *aggregate_device, |
+ &kSetSubDevicesAddress, |
+ 0, |
+ NULL, |
+ size, |
+ &sub_devices_array); |
+ if (result != noErr) { |
+ DLOG(ERROR) << "Error setting aggregate audio device sub-devices!"; |
+ return result; |
+ } |
+ |
+ // Use the input device as the master device. |
+ static const AudioObjectPropertyAddress kSetMasterDeviceAddress = { |
+ kAudioAggregateDevicePropertyMasterSubDevice, |
+ kAudioObjectPropertyScopeGlobal, |
+ kAudioObjectPropertyElementMaster |
+ }; |
+ |
+ size = sizeof(CFStringRef); |
+ result = AudioObjectSetPropertyData( |
+ *aggregate_device, |
+ &kSetMasterDeviceAddress, |
+ 0, |
+ NULL, |
+ size, |
+ &input_device_UID); |
+ if (result != noErr) { |
+ DLOG(ERROR) << "Error setting aggregate audio device master device!"; |
+ return result; |
+ } |
+ |
+ DVLOG(1) << "New aggregate device: " << *aggregate_device; |
+ return noErr; |
+} |
+ |
+void AggregateDeviceManager::DestroyAggregateDevice() { |
+ if (aggregate_device_ == kAudioObjectUnknown) |
+ return; |
+ |
+ static const AudioObjectPropertyAddress kDestroyAddress = { |
+ kAudioPlugInDestroyAggregateDevice, |
+ kAudioObjectPropertyScopeGlobal, |
+ kAudioObjectPropertyElementMaster |
+ }; |
+ |
+ UInt32 size = sizeof(aggregate_device_); |
+ OSStatus result = AudioObjectGetPropertyData( |
+ plugin_id_, |
+ &kDestroyAddress, |
+ 0, |
+ NULL, |
+ &size, |
+ &aggregate_device_); |
+ if (result != noErr) { |
+ DLOG(ERROR) << "Error destroying aggregate audio device!"; |
+ return; |
+ } |
+ |
+ aggregate_device_ = kAudioObjectUnknown; |
+} |
+ |
+} // namespace media |