1b2f65efcda04159cad2f6f1639633d2ae3bd2b1
[WebKit-https.git] / Source / WebCore / platform / audio / ios / AudioSessionIOS.mm
1 /*
2  * Copyright (C) 2013-2019 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 #import "config.h"
27 #import "AudioSession.h"
28
29 #if USE(AUDIO_SESSION) && PLATFORM(IOS_FAMILY)
30
31 #import "Logging.h"
32 #import <AVFoundation/AVAudioSession.h>
33 #import <objc/runtime.h>
34 #import <pal/spi/mac/AVFoundationSPI.h>
35 #import <wtf/OSObjectPtr.h>
36 #import <wtf/RetainPtr.h>
37
38 #import <pal/cocoa/AVFoundationSoftLink.h>
39
40 namespace WebCore {
41
42 #if !LOG_DISABLED
43 static const char* categoryName(AudioSession::CategoryType category)
44 {
45 #define CASE(category) case AudioSession::category: return #category
46     switch (category) {
47         CASE(None);
48         CASE(AmbientSound);
49         CASE(SoloAmbientSound);
50         CASE(MediaPlayback);
51         CASE(RecordAudio);
52         CASE(PlayAndRecord);
53         CASE(AudioProcessing);
54     }
55     
56     ASSERT_NOT_REACHED();
57     return "";
58 }
59 #endif
60
61 class AudioSessionPrivate {
62 public:
63     AudioSessionPrivate(AudioSession*);
64     AudioSession::CategoryType m_categoryOverride;
65     OSObjectPtr<dispatch_queue_t> m_dispatchQueue;
66 };
67
68 AudioSessionPrivate::AudioSessionPrivate(AudioSession*)
69     : m_categoryOverride(AudioSession::None)
70 {
71 }
72
73 AudioSession::AudioSession()
74     : m_private(std::make_unique<AudioSessionPrivate>(this))
75 {
76 }
77
78 AudioSession::~AudioSession()
79 {
80 }
81
82 void AudioSession::setCategory(CategoryType newCategory, RouteSharingPolicy policy)
83 {
84 #if !HAVE(ROUTE_SHARING_POLICY_LONG_FORM_VIDEO)
85     if (policy == RouteSharingPolicy::LongFormVideo)
86         policy = RouteSharingPolicy::LongFormAudio;
87 #endif
88
89     LOG(Media, "AudioSession::setCategory() - category = %s", categoryName(newCategory));
90
91     if (categoryOverride() && categoryOverride() != newCategory) {
92         LOG(Media, "AudioSession::setCategory() - override set, NOT changing");
93         return;
94     }
95
96     NSString *categoryString;
97     NSString *categoryMode = AVAudioSessionModeDefault;
98     AVAudioSessionCategoryOptions options = 0;
99
100     switch (newCategory) {
101     case AmbientSound:
102         categoryString = AVAudioSessionCategoryAmbient;
103         break;
104     case SoloAmbientSound:
105         categoryString = AVAudioSessionCategorySoloAmbient;
106         break;
107     case MediaPlayback:
108         categoryString = AVAudioSessionCategoryPlayback;
109         break;
110     case RecordAudio:
111         categoryString = AVAudioSessionCategoryRecord;
112         break;
113     case PlayAndRecord:
114         categoryString = AVAudioSessionCategoryPlayAndRecord;
115         categoryMode = AVAudioSessionModeVideoChat;
116         options |= AVAudioSessionCategoryOptionAllowBluetooth | AVAudioSessionCategoryOptionAllowBluetoothA2DP | AVAudioSessionCategoryOptionDefaultToSpeaker | AVAudioSessionCategoryOptionAllowAirPlay;
117         break;
118     case AudioProcessing:
119         categoryString = AVAudioSessionCategoryAudioProcessing;
120         break;
121     case None:
122         categoryString = AVAudioSessionCategoryAmbient;
123         break;
124     }
125
126     NSError *error = nil;
127     [[PAL::getAVAudioSessionClass() sharedInstance] setCategory:categoryString mode:categoryMode routeSharingPolicy:static_cast<AVAudioSessionRouteSharingPolicy>(policy) options:options error:&error];
128 #if !PLATFORM(IOS_FAMILY_SIMULATOR) && !PLATFORM(IOSMAC)
129     ASSERT(!error);
130 #endif
131 }
132
133 AudioSession::CategoryType AudioSession::category() const
134 {
135     NSString *categoryString = [[PAL::getAVAudioSessionClass() sharedInstance] category];
136     if ([categoryString isEqual:AVAudioSessionCategoryAmbient])
137         return AmbientSound;
138     if ([categoryString isEqual:AVAudioSessionCategorySoloAmbient])
139         return SoloAmbientSound;
140     if ([categoryString isEqual:AVAudioSessionCategoryPlayback])
141         return MediaPlayback;
142     if ([categoryString isEqual:AVAudioSessionCategoryRecord])
143         return RecordAudio;
144     if ([categoryString isEqual:AVAudioSessionCategoryPlayAndRecord])
145         return PlayAndRecord;
146     if ([categoryString isEqual:AVAudioSessionCategoryAudioProcessing])
147         return AudioProcessing;
148     return None;
149 }
150
151 RouteSharingPolicy AudioSession::routeSharingPolicy() const
152 {
153     static_assert(static_cast<size_t>(RouteSharingPolicy::Default) == static_cast<size_t>(AVAudioSessionRouteSharingPolicyDefault), "RouteSharingPolicy::Default is not AVAudioSessionRouteSharingPolicyDefault as expected");
154 #if HAVE(ROUTE_SHARING_POLICY_LONG_FORM_VIDEO)
155     static_assert(static_cast<size_t>(RouteSharingPolicy::LongFormAudio) == static_cast<size_t>(AVAudioSessionRouteSharingPolicyLongFormAudio), "RouteSharingPolicy::LongFormAudio is not AVAudioSessionRouteSharingPolicyLongFormAudio as expected");
156     static_assert(static_cast<size_t>(RouteSharingPolicy::LongFormVideo) == static_cast<size_t>(AVAudioSessionRouteSharingPolicyLongFormVideo), "RouteSharingPolicy::LongFormVideo is not AVAudioSessionRouteSharingPolicyLongFormVideo as expected");
157 #else
158 ALLOW_DEPRECATED_DECLARATIONS_BEGIN
159     static_assert(static_cast<size_t>(RouteSharingPolicy::LongFormAudio) == static_cast<size_t>(AVAudioSessionRouteSharingPolicyLongForm), "RouteSharingPolicy::LongFormAudio is not AVAudioSessionRouteSharingPolicyLongForm as expected");
160 ALLOW_DEPRECATED_DECLARATIONS_END
161 #endif
162     static_assert(static_cast<size_t>(RouteSharingPolicy::Independent) == static_cast<size_t>(AVAudioSessionRouteSharingPolicyIndependent), "RouteSharingPolicy::Independent is not AVAudioSessionRouteSharingPolicyIndependent as expected");
163
164     AVAudioSessionRouteSharingPolicy policy = [[PAL::getAVAudioSessionClass() sharedInstance] routeSharingPolicy];
165     ASSERT(static_cast<RouteSharingPolicy>(policy) <= RouteSharingPolicy::LongFormVideo);
166     return static_cast<RouteSharingPolicy>(policy);
167 }
168
169 String AudioSession::routingContextUID() const
170 {
171 #if !PLATFORM(IOS_FAMILY_SIMULATOR) && !PLATFORM(IOSMAC) && !PLATFORM(WATCHOS)
172     return [[PAL::getAVAudioSessionClass() sharedInstance] routingContextUID];
173 #else
174     return emptyString();
175 #endif
176 }
177
178 void AudioSession::setCategoryOverride(CategoryType category)
179 {
180     if (m_private->m_categoryOverride == category)
181         return;
182
183     m_private->m_categoryOverride = category;
184     setCategory(category, RouteSharingPolicy::Default);
185 }
186
187 AudioSession::CategoryType AudioSession::categoryOverride() const
188 {
189     return m_private->m_categoryOverride;
190 }
191
192 float AudioSession::sampleRate() const
193 {
194     return [[PAL::getAVAudioSessionClass() sharedInstance] sampleRate];
195 }
196
197 size_t AudioSession::bufferSize() const
198 {
199     return [[PAL::getAVAudioSessionClass() sharedInstance] IOBufferDuration] * sampleRate();
200 }
201
202 size_t AudioSession::numberOfOutputChannels() const
203 {
204     return [[PAL::getAVAudioSessionClass() sharedInstance] outputNumberOfChannels];
205 }
206
207 bool AudioSession::tryToSetActiveInternal(bool active)
208 {
209     __block NSError* error = nil;
210
211     if (!m_private->m_dispatchQueue)
212         m_private->m_dispatchQueue = adoptOSObject(dispatch_queue_create("AudioSession Activation Queue", DISPATCH_QUEUE_SERIAL));
213
214     // We need to deactivate the session on another queue because the AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation option
215     // means that AVAudioSession may synchronously unduck previously ducked clients. Activation needs to complete before this method
216     // returns, so do it synchronously on the same serial queue.
217     if (active) {
218         dispatch_sync(m_private->m_dispatchQueue.get(), ^{
219             [[PAL::getAVAudioSessionClass() sharedInstance] setActive:YES withOptions:0 error:&error];
220         });
221
222         return !error;
223     }
224
225     dispatch_async(m_private->m_dispatchQueue.get(), ^{
226         [[PAL::getAVAudioSessionClass() sharedInstance] setActive:NO withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:&error];
227     });
228
229     return true;
230 }
231
232 size_t AudioSession::preferredBufferSize() const
233 {
234     return [[PAL::getAVAudioSessionClass() sharedInstance] preferredIOBufferDuration] * sampleRate();
235 }
236
237 void AudioSession::setPreferredBufferSize(size_t bufferSize)
238 {
239     NSError *error = nil;
240     float duration = bufferSize / sampleRate();
241     [[PAL::getAVAudioSessionClass() sharedInstance] setPreferredIOBufferDuration:duration error:&error];
242     ASSERT(!error);
243 }
244
245 }
246
247 #endif // USE(AUDIO_SESSION) && PLATFORM(IOS_FAMILY)