1/*
2 * Copyright (c) 2003, 2012, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26//#define USE_TRACE
27//#define USE_ERROR
28
29#include "PLATFORM_API_MacOSX_Utils.h"
30
31int MACOSX_DAUDIO_Init() {
32    static int initialized = 0;
33    if (!initialized) {
34        CFRunLoopRef runLoop = NULL;
35
36        OSStatus err = SetAudioObjectProperty(kAudioObjectSystemObject, kAudioObjectPropertyScopeGlobal,
37            kAudioHardwarePropertyRunLoop, sizeof(CFRunLoopRef), &runLoop);
38
39        if (err) {
40            OS_ERROR0(err, "MACOSX_DAUDIO_Init(kAudioHardwarePropertyRunLoop)");
41        } else {
42            TRACE0("MACOSX_DAUDIO_Init(kAudioHardwarePropertyRunLoop): OK\n");
43            initialized = 1;
44        }
45    }
46    return initialized;
47}
48
49DeviceList::DeviceList(): count(0), devices(NULL) {
50    MACOSX_DAUDIO_Init();
51
52    AudioObjectPropertyAddress address = {kAudioHardwarePropertyDevices,
53        kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster};
54    OSStatus err = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &address, NotificationCallback, this);
55    if (err) {
56        OS_ERROR0(err, "AudioObjectAddPropertyListener(kAudioHardwarePropertyDevices)");
57    } else {
58        TRACE0("AudioObjectAddPropertyListener(kAudioHardwarePropertyDevices): OK\n");
59    }
60}
61
62DeviceList::~DeviceList() {
63    Free();
64
65    AudioObjectPropertyAddress address = {kAudioHardwarePropertyDevices,
66        kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster};
67    AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &address, NotificationCallback, this);
68}
69
70OSStatus DeviceList::Refresh() {
71    MutexLock::Locker locker(lock);
72    Free();
73
74    OSStatus err;
75    UInt32 size;
76    err = GetAudioObjectPropertySize(kAudioObjectSystemObject, kAudioObjectPropertyScopeGlobal, kAudioHardwarePropertyDevices, &size);
77    if (err == noErr) {
78        devices = (AudioDeviceID *)malloc(size);
79        err = GetAudioObjectProperty(kAudioObjectSystemObject, kAudioObjectPropertyScopeGlobal, kAudioHardwarePropertyDevices, &size, devices);
80        if (err == noErr) {
81            count = size/sizeof(AudioDeviceID);
82        }
83    }
84    if (err) {
85        OS_ERROR0(err, "DeviceList::Refresh");
86        Free();
87    }
88#ifdef USE_TRACE
89    TRACE1("<<DeviceList::Refresh, %d devices {", count);
90    for (int i=0; i<count; i++) {
91        if (i > 0)
92            TRACE0(", ");
93        TRACE1("0x%x", (int)devices[i]);
94    }
95    TRACE0("}\n");
96#endif
97
98    return err;
99}
100
101int DeviceList::GetCount() {
102    MutexLock::Locker locker(lock);
103    return count;
104}
105
106AudioDeviceID DeviceList::GetDeviceID(int index) {
107    MutexLock::Locker locker(lock);
108    return index < 0 ? 0 : index >= count ? 0 : devices[index];
109}
110
111bool DeviceList::GetDeviceInfo(int index, AudioDeviceID *pDeviceID, int stringLength, char *name, char *vendor, char *description, char *version) {
112    MutexLock::Locker locker(lock);
113    if (index < 0 || index >= count) {
114        return false;
115    }
116
117    AudioDeviceID deviceID = devices[index];
118    if (pDeviceID != NULL)
119        *pDeviceID = deviceID;
120
121    OSStatus err = noErr;
122
123    if (name != NULL || description != NULL) {
124        CFStringRef cfName = NULL;
125        err = GetAudioObjectProperty(deviceID, kAudioObjectPropertyScopeGlobal,
126            kAudioObjectPropertyName, sizeof(cfName), &cfName, 1);
127        if (err == noErr) {
128            if (name != NULL)
129                CFStringGetCString(cfName, name, stringLength, kCFStringEncodingUTF8);
130            if (description)
131                CFStringGetCString(cfName, description, stringLength, kCFStringEncodingUTF8);
132            CFRelease(cfName);
133        }
134    }
135
136    if (vendor != NULL) {
137        CFStringRef cfManufacturer = NULL;
138        err = GetAudioObjectProperty(deviceID, kAudioObjectPropertyScopeGlobal,
139            kAudioObjectPropertyManufacturer, sizeof(cfManufacturer), &cfManufacturer, 1);
140        if (err == noErr) {
141            CFStringGetCString(cfManufacturer, vendor, stringLength, kCFStringEncodingUTF8);
142            CFRelease(cfManufacturer);
143        }
144    }
145
146    return true;
147}
148
149void DeviceList::Free() {
150    if (devices != NULL) {
151        free(devices);
152        devices = NULL;
153        count = 0;
154    }
155}
156
157/*static*/
158OSStatus DeviceList::NotificationCallback(AudioObjectID inObjectID,
159    UInt32 inNumberAddresses, const AudioObjectPropertyAddress inAddresses[], void *inClientData)
160{
161    DeviceList *pThis = (DeviceList *)inClientData;
162
163    for (UInt32 i=0; i<inNumberAddresses; i++) {
164        switch (inAddresses[i].mSelector) {
165        case kAudioHardwarePropertyDevices:
166            TRACE0("NOTIFICATION: kAudioHardwarePropertyDevices\n");
167            break;
168        }
169    }
170
171    return noErr;
172}
173
174
175
176AudioDeviceID GetDefaultDevice(int isSource) {
177    AudioDeviceID deviceID;
178    OSStatus err = GetAudioObjectProperty(kAudioObjectSystemObject, kAudioObjectPropertyScopeGlobal,
179        isSource ? kAudioHardwarePropertyDefaultOutputDevice : kAudioHardwarePropertyDefaultInputDevice,
180        sizeof(deviceID), &deviceID, 1);
181    if (err) {
182        OS_ERROR1(err, "GetDefaultDevice(isSource=%d)", isSource);
183        return 0;
184    }
185    return deviceID;
186}
187
188int GetChannelCount(AudioDeviceID deviceID, int isSource) {
189    int result = 0;
190    OSStatus err;
191    UInt32 size, i;
192    AudioObjectPropertyScope scope = isSource ? kAudioDevicePropertyScopeOutput : kAudioDevicePropertyScopeInput;
193
194    err = GetAudioObjectPropertySize(deviceID, scope, kAudioDevicePropertyStreamConfiguration, &size);
195    if (err) {
196        OS_ERROR2(err, "GetChannelCount(getSize), deviceID=0x%x, isSource=%d", (int)deviceID, isSource);
197    } else {
198        AudioBufferList *pBufferList = (AudioBufferList *)malloc(size);
199        memset(pBufferList, 0, size);
200        err = GetAudioObjectProperty(deviceID, scope, kAudioDevicePropertyStreamConfiguration, &size, pBufferList);
201        if (err == noErr) {
202            for (i=0; i<pBufferList->mNumberBuffers; i++) {
203                result += pBufferList->mBuffers[i].mNumberChannels;
204            }
205        } else {
206            OS_ERROR2(err, "GetChannelCount(getData), deviceID=0x%x, isSource=%d", (int)deviceID, isSource);
207        }
208        free(pBufferList);
209    }
210    TRACE2("GetChannelCount (deviceID=0x%x): total %d channels\n", (int)deviceID, result);
211    return result;
212}
213
214float GetSampleRate(AudioDeviceID deviceID, int isSource) {
215    Float64 result;
216    AudioObjectPropertyScope scope = isSource ? kAudioDevicePropertyScopeOutput : kAudioDevicePropertyScopeInput;
217    OSStatus err = GetAudioObjectProperty(deviceID, scope, kAudioDevicePropertyActualSampleRate, sizeof(result), &result, 1);
218    if (err) {
219        OS_ERROR2(err, "GetSampleRate(ActualSampleRate), deviceID=0x%x, isSource=%d", (int)deviceID, isSource);
220        // try to get NominalSampleRate
221        err = GetAudioObjectProperty(deviceID, scope, kAudioDevicePropertyNominalSampleRate, sizeof(result), &result, 1);
222        if (err) {
223            OS_ERROR2(err, "GetSampleRate(NominalSampleRate), deviceID=0x%x, isSource=%d", (int)deviceID, isSource);
224            return 0;
225        }
226    }
227    return (float)result;
228}
229
230
231OSStatus GetAudioObjectPropertySize(AudioObjectID object, AudioObjectPropertyScope scope, AudioObjectPropertySelector prop, UInt32 *size)
232{
233    const AudioObjectPropertyAddress address = {prop, scope, kAudioObjectPropertyElementMaster};
234    OSStatus err;
235
236    err = AudioObjectGetPropertyDataSize(object, &address, 0, NULL, size);
237
238    return err;
239}
240
241OSStatus GetAudioObjectProperty(AudioObjectID object, AudioObjectPropertyScope scope, AudioObjectPropertySelector prop, UInt32 *size, void *data)
242{
243    const AudioObjectPropertyAddress address = {prop, scope, kAudioObjectPropertyElementMaster};
244    OSStatus err;
245
246    err = AudioObjectGetPropertyData(object, &address, 0, NULL, size, data);
247
248    return err;
249}
250
251OSStatus GetAudioObjectProperty(AudioObjectID object, AudioObjectPropertyScope scope, AudioObjectPropertySelector prop, UInt32 size, void *data, int checkSize)
252{
253    const AudioObjectPropertyAddress address = {prop, scope, kAudioObjectPropertyElementMaster};
254    UInt32 oldSize = size;
255    OSStatus err;
256
257    err = AudioObjectGetPropertyData(object, &address, 0, NULL, &size, data);
258
259    if (!err && checkSize && size != oldSize)
260        return kAudioHardwareBadPropertySizeError;
261    return err;
262}
263
264// wrapper for AudioObjectSetPropertyData (kAudioObjectPropertyElementMaster)
265OSStatus SetAudioObjectProperty(AudioObjectID object, AudioObjectPropertyScope scope, AudioObjectPropertySelector prop, UInt32 size, void *data)
266{
267    AudioObjectPropertyAddress address = {prop, scope, kAudioObjectPropertyElementMaster};
268
269    OSStatus err = AudioObjectSetPropertyData(object, &address, 0, NULL, size, data);
270
271    return err;
272}
273