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.
4
5 #include "media/audio/mac/aggregate_device_manager.h"
6
7 #include <CoreAudio/AudioHardware.h>
8 #include <string>
9
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"
14
15 using base::ScopedCFTypeRef;
16
17 namespace media {
18
AggregateDeviceManager()19 AggregateDeviceManager::AggregateDeviceManager()
20 : plugin_id_(kAudioObjectUnknown),
21 input_device_(kAudioDeviceUnknown),
22 output_device_(kAudioDeviceUnknown),
23 aggregate_device_(kAudioObjectUnknown) {
24 }
25
~AggregateDeviceManager()26 AggregateDeviceManager::~AggregateDeviceManager() {
27 DestroyAggregateDevice();
28 }
29
GetDefaultAggregateDevice()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);
35
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;
42 }
43
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_;
53
54 // For now, once lazily created don't attempt to create another
55 // aggregate device.
56 return kAudioDeviceUnknown;
57 }
58
59 input_device_ = current_input_device;
60 output_device_ = current_output_device;
61
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;
67
68 if (input_clockdomain == 0 || input_clockdomain != output_clockdomain)
69 return kAudioDeviceUnknown;
70
71 OSStatus result = CreateAggregateDevice(
72 input_device_,
73 output_device_,
74 &aggregate_device_);
75 if (result != noErr)
76 DestroyAggregateDevice();
77
78 return aggregate_device_;
79 }
80
GetDeviceUID(AudioDeviceID id)81 CFStringRef AggregateDeviceManager::GetDeviceUID(AudioDeviceID id) {
82 static const AudioObjectPropertyAddress kDeviceUIDAddress = {
83 kAudioDevicePropertyDeviceUID,
84 kAudioObjectPropertyScopeGlobal,
85 kAudioObjectPropertyElementMaster
86 };
87
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(
93 id,
94 &kDeviceUIDAddress,
95 0,
96 0,
97 &size,
98 &device_UID);
99
100 return (result == noErr) ? device_UID : NULL;
101 }
102
GetDeviceName(AudioDeviceID id,char * name,UInt32 size)103 void AggregateDeviceManager::GetDeviceName(
104 AudioDeviceID id, char* name, UInt32 size) {
105 static const AudioObjectPropertyAddress kDeviceNameAddress = {
106 kAudioDevicePropertyDeviceName,
107 kAudioObjectPropertyScopeGlobal,
108 kAudioObjectPropertyElementMaster
109 };
110
111 OSStatus result = AudioObjectGetPropertyData(
112 id,
113 &kDeviceNameAddress,
114 0,
115 0,
116 &size,
117 name);
118
119 if (result != noErr && size > 0)
120 name[0] = 0;
121 }
122
GetClockDomain(AudioDeviceID device_id)123 UInt32 AggregateDeviceManager::GetClockDomain(AudioDeviceID device_id) {
124 static const AudioObjectPropertyAddress kClockDomainAddress = {
125 kAudioDevicePropertyClockDomain,
126 kAudioObjectPropertyScopeGlobal,
127 kAudioObjectPropertyElementMaster
128 };
129
130 UInt32 clockdomain = 0;
131 UInt32 size = sizeof(UInt32);
132 OSStatus result = AudioObjectGetPropertyData(
133 device_id,
134 &kClockDomainAddress,
135 0,
136 0,
137 &size,
138 &clockdomain);
139
140 return (result == noErr) ? clockdomain : 0;
141 }
142
GetPluginID(AudioObjectID * id)143 OSStatus AggregateDeviceManager::GetPluginID(AudioObjectID* id) {
144 DCHECK(id);
145
146 // Get the audio hardware plugin.
147 CFStringRef bundle_name = CFSTR("com.apple.audio.CoreAudio");
148
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);
154
155 static const AudioObjectPropertyAddress kPlugInAddress = {
156 kAudioHardwarePropertyPlugInForBundleID,
157 kAudioObjectPropertyScopeGlobal,
158 kAudioObjectPropertyElementMaster
159 };
160
161 UInt32 size = sizeof(plugin_translation);
162 OSStatus result = AudioObjectGetPropertyData(
163 kAudioObjectSystemObject,
164 &kPlugInAddress,
165 0,
166 0,
167 &size,
168 &plugin_translation);
169
170 DVLOG(1) << "CoreAudio plugin ID: " << *id;
171
172 return result;
173 }
174
175 CFMutableDictionaryRef
CreateAggregateDeviceDictionary(AudioDeviceID input_id,AudioDeviceID output_id)176 AggregateDeviceManager::CreateAggregateDeviceDictionary(
177 AudioDeviceID input_id,
178 AudioDeviceID output_id) {
179 CFMutableDictionaryRef aggregate_device_dict = CFDictionaryCreateMutable(
180 NULL,
181 0,
182 &kCFTypeDictionaryKeyCallBacks,
183 &kCFTypeDictionaryValueCallBacks);
184 if (!aggregate_device_dict)
185 return NULL;
186
187 const CFStringRef kAggregateDeviceName =
188 CFSTR("ChromeAggregateAudioDevice");
189 const CFStringRef kAggregateDeviceUID =
190 CFSTR("com.google.chrome.AggregateAudioDevice");
191
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);
201
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.
206 int value = 1;
207 ScopedCFTypeRef<CFNumberRef> aggregate_device_number(CFNumberCreate(
208 NULL,
209 kCFNumberIntType,
210 &value));
211 CFDictionaryAddValue(
212 aggregate_device_dict,
213 CFSTR(kAudioAggregateDeviceIsPrivateKey),
214 aggregate_device_number);
215
216 return aggregate_device_dict;
217 }
218
219 CFMutableArrayRef
CreateSubDeviceArray(CFStringRef input_device_UID,CFStringRef output_device_UID)220 AggregateDeviceManager::CreateSubDeviceArray(
221 CFStringRef input_device_UID, CFStringRef output_device_UID) {
222 CFMutableArrayRef sub_devices_array = CFArrayCreateMutable(
223 NULL,
224 0,
225 &kCFTypeArrayCallBacks);
226
227 CFArrayAppendValue(sub_devices_array, input_device_UID);
228 CFArrayAppendValue(sub_devices_array, output_device_UID);
229
230 return sub_devices_array;
231 }
232
CreateAggregateDevice(AudioDeviceID input_id,AudioDeviceID output_id,AudioDeviceID * aggregate_device)233 OSStatus AggregateDeviceManager::CreateAggregateDevice(
234 AudioDeviceID input_id,
235 AudioDeviceID output_id,
236 AudioDeviceID* aggregate_device) {
237 DCHECK(aggregate_device);
238
239 const size_t kMaxDeviceNameLength = 256;
240
241 scoped_ptr<char[]> input_device_name(new char[kMaxDeviceNameLength]);
242 GetDeviceName(
243 input_id,
244 input_device_name.get(),
245 sizeof(input_device_name));
246 DVLOG(1) << "Input device: \n" << input_device_name;
247
248 scoped_ptr<char[]> output_device_name(new char[kMaxDeviceNameLength]);
249 GetDeviceName(
250 output_id,
251 output_device_name.get(),
252 sizeof(output_device_name));
253 DVLOG(1) << "Output device: \n" << output_device_name;
254
255 OSStatus result = GetPluginID(&plugin_id_);
256 if (result != noErr)
257 return result;
258
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)
263 return -1;
264
265 // Create the aggregate device.
266 static const AudioObjectPropertyAddress kCreateAggregateDeviceAddress = {
267 kAudioPlugInCreateAggregateDevice,
268 kAudioObjectPropertyScopeGlobal,
269 kAudioObjectPropertyElementMaster
270 };
271
272 UInt32 size = sizeof(*aggregate_device);
273 result = AudioObjectGetPropertyData(
274 plugin_id_,
275 &kCreateAggregateDeviceAddress,
276 sizeof(aggregate_device_dict),
277 &aggregate_device_dict,
278 &size,
279 aggregate_device);
280 if (result != noErr) {
281 DLOG(ERROR) << "Error creating aggregate audio device!";
282 return result;
283 }
284
285 // Set the sub-devices for the aggregate device.
286 // In this case we use two: the input and output devices.
287
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.";
292 return -1;
293 }
294
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.";
299 return -1;
300 }
301
302 static const AudioObjectPropertyAddress kSetSubDevicesAddress = {
303 kAudioAggregateDevicePropertyFullSubDeviceList,
304 kAudioObjectPropertyScopeGlobal,
305 kAudioObjectPropertyElementMaster
306 };
307
308 size = sizeof(CFMutableArrayRef);
309 result = AudioObjectSetPropertyData(
310 *aggregate_device,
311 &kSetSubDevicesAddress,
312 0,
313 NULL,
314 size,
315 &sub_devices_array);
316 if (result != noErr) {
317 DLOG(ERROR) << "Error setting aggregate audio device sub-devices!";
318 return result;
319 }
320
321 // Use the input device as the master device.
322 static const AudioObjectPropertyAddress kSetMasterDeviceAddress = {
323 kAudioAggregateDevicePropertyMasterSubDevice,
324 kAudioObjectPropertyScopeGlobal,
325 kAudioObjectPropertyElementMaster
326 };
327
328 size = sizeof(CFStringRef);
329 result = AudioObjectSetPropertyData(
330 *aggregate_device,
331 &kSetMasterDeviceAddress,
332 0,
333 NULL,
334 size,
335 &input_device_UID);
336 if (result != noErr) {
337 DLOG(ERROR) << "Error setting aggregate audio device master device!";
338 return result;
339 }
340
341 DVLOG(1) << "New aggregate device: " << *aggregate_device;
342 return noErr;
343 }
344
DestroyAggregateDevice()345 void AggregateDeviceManager::DestroyAggregateDevice() {
346 if (aggregate_device_ == kAudioObjectUnknown)
347 return;
348
349 static const AudioObjectPropertyAddress kDestroyAddress = {
350 kAudioPlugInDestroyAggregateDevice,
351 kAudioObjectPropertyScopeGlobal,
352 kAudioObjectPropertyElementMaster
353 };
354
355 UInt32 size = sizeof(aggregate_device_);
356 OSStatus result = AudioObjectGetPropertyData(
357 plugin_id_,
358 &kDestroyAddress,
359 0,
360 NULL,
361 &size,
362 &aggregate_device_);
363 if (result != noErr) {
364 DLOG(ERROR) << "Error destroying aggregate audio device!";
365 return;
366 }
367
368 aggregate_device_ = kAudioObjectUnknown;
369 }
370
371 } // namespace media
372