1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "media/audio/mac/aggregate_device_manager.h"
7 #include <CoreAudio/AudioHardware.h>
10 #include "base/mac/mac_logging.h"
11 #include "base/mac/scoped_cftyperef.h"
12 #include "media/audio/audio_parameters.h"
13 #include "media/audio/mac/audio_manager_mac.h"
15 using base::ScopedCFTypeRef
;
19 AggregateDeviceManager::AggregateDeviceManager()
20 : plugin_id_(kAudioObjectUnknown
),
21 input_device_(kAudioDeviceUnknown
),
22 output_device_(kAudioDeviceUnknown
),
23 aggregate_device_(kAudioObjectUnknown
) {
26 AggregateDeviceManager::~AggregateDeviceManager() {
27 DestroyAggregateDevice();
30 AudioDeviceID
AggregateDeviceManager::GetDefaultAggregateDevice() {
31 AudioDeviceID current_input_device
;
32 AudioDeviceID current_output_device
;
33 AudioManagerMac::GetDefaultInputDevice(¤t_input_device
);
34 AudioManagerMac::GetDefaultOutputDevice(¤t_output_device
);
36 if (AudioManagerMac::HardwareSampleRateForDevice(current_input_device
) !=
37 AudioManagerMac::HardwareSampleRateForDevice(current_output_device
)) {
38 // TODO(crogers): with some extra work we can make aggregate devices work
39 // if the clock domain is the same but the sample-rate differ.
40 // For now we fallback to the synchronized path.
41 return kAudioDeviceUnknown
;
44 // Use a lazily created aggregate device if it's already available
45 // and still appropriate.
46 if (aggregate_device_
!= kAudioObjectUnknown
) {
47 // TODO(crogers): handle default device changes for synchronized I/O.
48 // For now, we check to make sure the default devices haven't changed
49 // since we lazily created the aggregate device.
50 if (current_input_device
== input_device_
&&
51 current_output_device
== output_device_
)
52 return aggregate_device_
;
54 // For now, once lazily created don't attempt to create another
56 return kAudioDeviceUnknown
;
59 input_device_
= current_input_device
;
60 output_device_
= current_output_device
;
62 // Only create an aggregrate device if the clock domains match.
63 UInt32 input_clockdomain
= GetClockDomain(input_device_
);
64 UInt32 output_clockdomain
= GetClockDomain(output_device_
);
65 DVLOG(1) << "input_clockdomain: " << input_clockdomain
;
66 DVLOG(1) << "output_clockdomain: " << output_clockdomain
;
68 if (input_clockdomain
== 0 || input_clockdomain
!= output_clockdomain
)
69 return kAudioDeviceUnknown
;
71 OSStatus result
= CreateAggregateDevice(
76 DestroyAggregateDevice();
78 return aggregate_device_
;
81 CFStringRef
AggregateDeviceManager::GetDeviceUID(AudioDeviceID id
) {
82 static const AudioObjectPropertyAddress kDeviceUIDAddress
= {
83 kAudioDevicePropertyDeviceUID
,
84 kAudioObjectPropertyScopeGlobal
,
85 kAudioObjectPropertyElementMaster
88 // As stated in the CoreAudio header (AudioHardwareBase.h),
89 // the caller is responsible for releasing the device_UID.
90 CFStringRef device_UID
;
91 UInt32 size
= sizeof(device_UID
);
92 OSStatus result
= AudioObjectGetPropertyData(
100 return (result
== noErr
) ? device_UID
: NULL
;
103 void AggregateDeviceManager::GetDeviceName(
104 AudioDeviceID id
, char* name
, UInt32 size
) {
105 static const AudioObjectPropertyAddress kDeviceNameAddress
= {
106 kAudioDevicePropertyDeviceName
,
107 kAudioObjectPropertyScopeGlobal
,
108 kAudioObjectPropertyElementMaster
111 OSStatus result
= AudioObjectGetPropertyData(
119 if (result
!= noErr
&& size
> 0)
123 UInt32
AggregateDeviceManager::GetClockDomain(AudioDeviceID device_id
) {
124 static const AudioObjectPropertyAddress kClockDomainAddress
= {
125 kAudioDevicePropertyClockDomain
,
126 kAudioObjectPropertyScopeGlobal
,
127 kAudioObjectPropertyElementMaster
130 UInt32 clockdomain
= 0;
131 UInt32 size
= sizeof(UInt32
);
132 OSStatus result
= AudioObjectGetPropertyData(
134 &kClockDomainAddress
,
140 return (result
== noErr
) ? clockdomain
: 0;
143 OSStatus
AggregateDeviceManager::GetPluginID(AudioObjectID
* id
) {
146 // Get the audio hardware plugin.
147 CFStringRef bundle_name
= CFSTR("com.apple.audio.CoreAudio");
149 AudioValueTranslation plugin_translation
;
150 plugin_translation
.mInputData
= &bundle_name
;
151 plugin_translation
.mInputDataSize
= sizeof(bundle_name
);
152 plugin_translation
.mOutputData
= id
;
153 plugin_translation
.mOutputDataSize
= sizeof(*id
);
155 static const AudioObjectPropertyAddress kPlugInAddress
= {
156 kAudioHardwarePropertyPlugInForBundleID
,
157 kAudioObjectPropertyScopeGlobal
,
158 kAudioObjectPropertyElementMaster
161 UInt32 size
= sizeof(plugin_translation
);
162 OSStatus result
= AudioObjectGetPropertyData(
163 kAudioObjectSystemObject
,
168 &plugin_translation
);
170 DVLOG(1) << "CoreAudio plugin ID: " << *id
;
175 CFMutableDictionaryRef
176 AggregateDeviceManager::CreateAggregateDeviceDictionary(
177 AudioDeviceID input_id
,
178 AudioDeviceID output_id
) {
179 CFMutableDictionaryRef aggregate_device_dict
= CFDictionaryCreateMutable(
182 &kCFTypeDictionaryKeyCallBacks
,
183 &kCFTypeDictionaryValueCallBacks
);
184 if (!aggregate_device_dict
)
187 const CFStringRef kAggregateDeviceName
=
188 CFSTR("ChromeAggregateAudioDevice");
189 const CFStringRef kAggregateDeviceUID
=
190 CFSTR("com.google.chrome.AggregateAudioDevice");
192 // Add name and UID of the device to the dictionary.
193 CFDictionaryAddValue(
194 aggregate_device_dict
,
195 CFSTR(kAudioAggregateDeviceNameKey
),
196 kAggregateDeviceName
);
197 CFDictionaryAddValue(
198 aggregate_device_dict
,
199 CFSTR(kAudioAggregateDeviceUIDKey
),
200 kAggregateDeviceUID
);
202 // Add a "private aggregate key" to the dictionary.
203 // The 1 value means that the created aggregate device will
204 // only be accessible from the process that created it, and
205 // won't be visible to outside processes.
207 ScopedCFTypeRef
<CFNumberRef
> aggregate_device_number(CFNumberCreate(
211 CFDictionaryAddValue(
212 aggregate_device_dict
,
213 CFSTR(kAudioAggregateDeviceIsPrivateKey
),
214 aggregate_device_number
);
216 return aggregate_device_dict
;
220 AggregateDeviceManager::CreateSubDeviceArray(
221 CFStringRef input_device_UID
, CFStringRef output_device_UID
) {
222 CFMutableArrayRef sub_devices_array
= CFArrayCreateMutable(
225 &kCFTypeArrayCallBacks
);
227 CFArrayAppendValue(sub_devices_array
, input_device_UID
);
228 CFArrayAppendValue(sub_devices_array
, output_device_UID
);
230 return sub_devices_array
;
233 OSStatus
AggregateDeviceManager::CreateAggregateDevice(
234 AudioDeviceID input_id
,
235 AudioDeviceID output_id
,
236 AudioDeviceID
* aggregate_device
) {
237 DCHECK(aggregate_device
);
239 const size_t kMaxDeviceNameLength
= 256;
241 scoped_ptr
<char[]> input_device_name(new char[kMaxDeviceNameLength
]);
244 input_device_name
.get(),
245 sizeof(input_device_name
));
246 DVLOG(1) << "Input device: \n" << input_device_name
;
248 scoped_ptr
<char[]> output_device_name(new char[kMaxDeviceNameLength
]);
251 output_device_name
.get(),
252 sizeof(output_device_name
));
253 DVLOG(1) << "Output device: \n" << output_device_name
;
255 OSStatus result
= GetPluginID(&plugin_id_
);
259 // Create a dictionary for the aggregate device.
260 ScopedCFTypeRef
<CFMutableDictionaryRef
> aggregate_device_dict(
261 CreateAggregateDeviceDictionary(input_id
, output_id
));
262 if (!aggregate_device_dict
)
265 // Create the aggregate device.
266 static const AudioObjectPropertyAddress kCreateAggregateDeviceAddress
= {
267 kAudioPlugInCreateAggregateDevice
,
268 kAudioObjectPropertyScopeGlobal
,
269 kAudioObjectPropertyElementMaster
272 UInt32 size
= sizeof(*aggregate_device
);
273 result
= AudioObjectGetPropertyData(
275 &kCreateAggregateDeviceAddress
,
276 sizeof(aggregate_device_dict
),
277 &aggregate_device_dict
,
280 if (result
!= noErr
) {
281 DLOG(ERROR
) << "Error creating aggregate audio device!";
285 // Set the sub-devices for the aggregate device.
286 // In this case we use two: the input and output devices.
288 ScopedCFTypeRef
<CFStringRef
> input_device_UID(GetDeviceUID(input_id
));
289 ScopedCFTypeRef
<CFStringRef
> output_device_UID(GetDeviceUID(output_id
));
290 if (!input_device_UID
|| !output_device_UID
) {
291 DLOG(ERROR
) << "Error getting audio device UID strings.";
295 ScopedCFTypeRef
<CFMutableArrayRef
> sub_devices_array(
296 CreateSubDeviceArray(input_device_UID
, output_device_UID
));
297 if (sub_devices_array
== NULL
) {
298 DLOG(ERROR
) << "Error creating sub-devices array.";
302 static const AudioObjectPropertyAddress kSetSubDevicesAddress
= {
303 kAudioAggregateDevicePropertyFullSubDeviceList
,
304 kAudioObjectPropertyScopeGlobal
,
305 kAudioObjectPropertyElementMaster
308 size
= sizeof(CFMutableArrayRef
);
309 result
= AudioObjectSetPropertyData(
311 &kSetSubDevicesAddress
,
316 if (result
!= noErr
) {
317 DLOG(ERROR
) << "Error setting aggregate audio device sub-devices!";
321 // Use the input device as the master device.
322 static const AudioObjectPropertyAddress kSetMasterDeviceAddress
= {
323 kAudioAggregateDevicePropertyMasterSubDevice
,
324 kAudioObjectPropertyScopeGlobal
,
325 kAudioObjectPropertyElementMaster
328 size
= sizeof(CFStringRef
);
329 result
= AudioObjectSetPropertyData(
331 &kSetMasterDeviceAddress
,
336 if (result
!= noErr
) {
337 DLOG(ERROR
) << "Error setting aggregate audio device master device!";
341 DVLOG(1) << "New aggregate device: " << *aggregate_device
;
345 void AggregateDeviceManager::DestroyAggregateDevice() {
346 if (aggregate_device_
== kAudioObjectUnknown
)
349 static const AudioObjectPropertyAddress kDestroyAddress
= {
350 kAudioPlugInDestroyAggregateDevice
,
351 kAudioObjectPropertyScopeGlobal
,
352 kAudioObjectPropertyElementMaster
355 UInt32 size
= sizeof(aggregate_device_
);
356 OSStatus result
= AudioObjectGetPropertyData(
363 if (result
!= noErr
) {
364 DLOG(ERROR
) << "Error destroying aggregate audio device!";
368 aggregate_device_
= kAudioObjectUnknown
;