1/*
2 * Copyright (C) 2014 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "AudioHardwareListenerMac.h"
28
29#if PLATFORM(MAC)
30
31#include <algorithm>
32
33enum {
34    kAudioHardwarePropertyProcessIsRunning = 'prun'
35};
36
37namespace WebCore {
38
39static AudioHardwareActivityType isAudioHardwareProcessRunning()
40{
41    AudioObjectPropertyAddress propertyAddress = {
42        kAudioHardwarePropertyProcessIsRunning,
43        kAudioObjectPropertyScopeGlobal,
44        kAudioObjectPropertyElementMaster
45    };
46
47    if (!AudioObjectHasProperty(kAudioObjectSystemObject, &propertyAddress))
48        return AudioHardwareActivityType::Unknown;
49
50    UInt32 result = 0;
51    UInt32 resultSize = sizeof(UInt32);
52
53    if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, 0, &resultSize, &result))
54        return AudioHardwareActivityType::Unknown;
55
56    if (result)
57        return AudioHardwareActivityType::IsActive;
58    else
59        return AudioHardwareActivityType::IsInactive;
60}
61
62static bool currentDeviceSupportsLowPowerBufferSize()
63{
64    AudioDeviceID deviceID = kAudioDeviceUnknown;
65    UInt32 descriptorSize = sizeof(deviceID);
66    AudioObjectPropertyAddress defaultOutputDeviceDescriptor = {
67        kAudioHardwarePropertyDefaultOutputDevice,
68        kAudioObjectPropertyScopeGlobal,
69        kAudioObjectPropertyElementMaster };
70
71    if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &defaultOutputDeviceDescriptor, 0, 0, &descriptorSize, (void*)&deviceID))
72        return false;
73
74    UInt32 transportType = kAudioDeviceTransportTypeUnknown;
75    descriptorSize = sizeof(transportType);
76    AudioObjectPropertyAddress tranportTypeDescriptor = {
77        kAudioDevicePropertyTransportType,
78        kAudioObjectPropertyScopeGlobal,
79        kAudioObjectPropertyElementMaster,
80    };
81
82    if (AudioObjectGetPropertyData(deviceID, &tranportTypeDescriptor, 0, 0, &descriptorSize, &transportType))
83        return false;
84
85    // Only allow low-power buffer size when using built-in output device, many external devices perform
86    // poorly with a large output buffer.
87    return kAudioDeviceTransportTypeBuiltIn == transportType;
88}
89
90static const AudioObjectPropertyAddress& processIsRunningPropertyDescriptor()
91{
92    static AudioObjectPropertyAddress processIsRunningProperty = {
93        kAudioHardwarePropertyProcessIsRunning,
94        kAudioObjectPropertyScopeGlobal,
95        kAudioObjectPropertyElementMaster
96    };
97
98    return processIsRunningProperty;
99};
100
101static const AudioObjectPropertyAddress& outputDevicePropertyDescriptor()
102{
103    static AudioObjectPropertyAddress outputDeviceProperty = {
104        kAudioHardwarePropertyDefaultOutputDevice,
105        kAudioObjectPropertyScopeGlobal,
106        kAudioObjectPropertyElementMaster
107    };
108
109    return outputDeviceProperty;
110};
111
112PassRefPtr<AudioHardwareListener> AudioHardwareListener::create(Client& client)
113{
114    return AudioHardwareListenerMac::create(client);
115}
116
117PassRefPtr<AudioHardwareListenerMac> AudioHardwareListenerMac::create(Client& client)
118{
119    return adoptRef(new AudioHardwareListenerMac(client));
120}
121
122AudioHardwareListenerMac::AudioHardwareListenerMac(Client& client)
123    : AudioHardwareListener(client)
124    , m_weakFactory(this)
125{
126    setHardwareActivity(isAudioHardwareProcessRunning());
127    setOutputDeviceSupportsLowPowerMode(currentDeviceSupportsLowPowerBufferSize());
128
129    auto weakThis = m_weakFactory.createWeakPtr();
130    m_block = Block_copy(^(UInt32 count, const AudioObjectPropertyAddress properties[]) {
131        if (weakThis)
132            weakThis->propertyChanged(count, properties);
133    });
134
135    AudioObjectAddPropertyListenerBlock(kAudioObjectSystemObject, &processIsRunningPropertyDescriptor(), dispatch_get_main_queue(), m_block);
136    AudioObjectAddPropertyListenerBlock(kAudioObjectSystemObject, &outputDevicePropertyDescriptor(), dispatch_get_main_queue(), m_block);
137}
138
139AudioHardwareListenerMac::~AudioHardwareListenerMac()
140{
141    AudioObjectRemovePropertyListenerBlock(kAudioObjectSystemObject, &processIsRunningPropertyDescriptor(), dispatch_get_main_queue(), m_block);
142    AudioObjectRemovePropertyListenerBlock(kAudioObjectSystemObject, &outputDevicePropertyDescriptor(), dispatch_get_main_queue(), m_block);
143    Block_release(m_block);
144}
145
146void AudioHardwareListenerMac::propertyChanged(UInt32 propertyCount, const AudioObjectPropertyAddress properties[])
147{
148    const AudioObjectPropertyAddress& deviceRunning = processIsRunningPropertyDescriptor();
149    const AudioObjectPropertyAddress& outputDevice = outputDevicePropertyDescriptor();
150
151    for (UInt32 i = 0; i < propertyCount; ++i) {
152        const AudioObjectPropertyAddress& property = properties[i];
153
154        if (!memcmp(&property, &deviceRunning, sizeof(AudioObjectPropertyAddress)))
155            processIsRunningChanged();
156        else if (!memcmp(&property, &outputDevice, sizeof(AudioObjectPropertyAddress)))
157            outputDeviceChanged();
158    }
159}
160
161void AudioHardwareListenerMac::processIsRunningChanged()
162{
163    AudioHardwareActivityType activity = isAudioHardwareProcessRunning();
164    if (activity == hardwareActivity())
165        return;
166    setHardwareActivity(activity);
167
168    if (hardwareActivity() == AudioHardwareActivityType::IsActive)
169        m_client.audioHardwareDidBecomeActive();
170    else if (hardwareActivity() == AudioHardwareActivityType::IsInactive)
171        m_client.audioHardwareDidBecomeInactive();
172}
173
174void AudioHardwareListenerMac::outputDeviceChanged()
175{
176    setOutputDeviceSupportsLowPowerMode(currentDeviceSupportsLowPowerBufferSize());
177    m_client.audioOutputDeviceChanged();
178}
179
180}
181
182#endif
183