• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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(&current_input_device);
34   AudioManagerMac::GetDefaultOutputDevice(&current_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