1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "coreaudiosessionmanager.h"
41#import <AVFoundation/AVAudioSession.h>
42#import <Foundation/Foundation.h>
43
44QT_BEGIN_NAMESPACE
45
46@interface CoreAudioSessionObserver : NSObject
47{
48    CoreAudioSessionManager *m_sessionManager;
49    AVAudioSession *m_audioSession;
50}
51
52@property (readonly, getter=sessionManager) CoreAudioSessionManager *m_sessionManager;
53@property (readonly, getter=audioSession) AVAudioSession *m_audioSession;
54
55-(CoreAudioSessionObserver *)initWithAudioSessionManager:(CoreAudioSessionManager *)sessionManager;
56
57-(BOOL)activateAudio;
58-(BOOL)deactivateAudio;
59
60//Notification handlers
61-(void)audioSessionInterruption:(NSNotification *)notification;
62-(void)audioSessionRouteChange:(NSNotification *)notification;
63-(void)audioSessionMediaServicesWereReset:(NSNotification *)notification;
64
65@end //interface CoreAudioSessionObserver
66
67@implementation CoreAudioSessionObserver
68
69@synthesize m_sessionManager, m_audioSession;
70
71-(CoreAudioSessionObserver *)initWithAudioSessionManager:(CoreAudioSessionManager *)sessionManager
72{
73        if (!(self = [super init]))
74            return nil;
75
76        self->m_sessionManager = sessionManager;
77        self->m_audioSession = [AVAudioSession sharedInstance];
78
79        //Set up observers
80        [[NSNotificationCenter defaultCenter] addObserver:self
81                                                 selector:@selector(audioSessionInterruption:)
82                                                     name:AVAudioSessionInterruptionNotification
83                                                   object:self->m_audioSession];
84        [[NSNotificationCenter defaultCenter] addObserver:self
85                                                 selector:@selector(audioSessionMediaServicesWereReset:)
86                                                     name:AVAudioSessionMediaServicesWereResetNotification
87                                                   object:self->m_audioSession];
88        [[NSNotificationCenter defaultCenter] addObserver:self
89                                                 selector:@selector(audioSessionRouteChange:)
90                                                     name:AVAudioSessionRouteChangeNotification
91                                                   object:self->m_audioSession];
92
93        return self;
94}
95
96-(void)dealloc
97{
98#ifdef QT_DEBUG_COREAUDIO
99    qDebug() << Q_FUNC_INFO;
100#endif
101
102    [[NSNotificationCenter defaultCenter] removeObserver:self
103                                                    name:AVAudioSessionInterruptionNotification
104                                                  object:self->m_audioSession];
105    [[NSNotificationCenter defaultCenter] removeObserver:self
106                                                    name:AVAudioSessionMediaServicesWereResetNotification
107                                                  object:self->m_audioSession];
108    [[NSNotificationCenter defaultCenter] removeObserver:self
109                                                    name:AVAudioSessionRouteChangeNotification
110                                                  object:self->m_audioSession];
111
112    [super dealloc];
113}
114
115-(BOOL)activateAudio
116{
117    NSError *error = nil;
118    BOOL success = [self->m_audioSession setActive:YES error:&error];
119    if (![self->m_audioSession setActive:YES error:&error]) {
120#ifdef QT_DEBUG_COREAUDIO
121        qDebug("audio session activation failed: %s", [[error localizedDescription] UTF8String]);
122    } else {
123        qDebug("audio session activated");
124#endif
125    }
126
127    return success;
128}
129
130-(BOOL)deactivateAudio
131{
132    NSError *error = nil;
133    BOOL success = [m_audioSession setActive:NO error:&error];
134#ifdef QT_DEBUG_COREAUDIO
135    if (!success) {
136        qDebug("%s", [[error localizedDescription] UTF8String]);
137    }
138#endif
139    return success;
140}
141
142-(void)audioSessionInterruption:(NSNotification *)notification
143{
144    NSNumber *type = [[notification userInfo] valueForKey:AVAudioSessionInterruptionTypeKey];
145    if ([type intValue] == AVAudioSessionInterruptionTypeBegan) {
146#ifdef QT_DEBUG_COREAUDIO
147        qDebug("audioSession Interuption begain");
148#endif
149    } else if ([type intValue] == AVAudioSessionInterruptionTypeEnded) {
150#ifdef QT_DEBUG_COREAUDIO
151        qDebug("audioSession Interuption ended");
152#endif
153        NSNumber *option = [[notification userInfo] valueForKey:AVAudioSessionInterruptionOptionKey];
154        if ([option intValue] == AVAudioSessionInterruptionOptionShouldResume) {
155#ifdef QT_DEBUG_COREAUDIO
156            qDebug("audioSession is active and immediately ready to be used.");
157#endif
158        } else {
159            [self activateAudio];
160        }
161    }
162}
163
164-(void)audioSessionMediaServicesWereReset:(NSNotification *)notification
165{
166    Q_UNUSED(notification)
167#ifdef QT_DEBUG_COREAUDIO
168    qDebug("audioSession Media Services were reset");
169#endif
170    //Reactivate audio when this occurs
171    [self activateAudio];
172}
173
174-(void)audioSessionRouteChange:(NSNotification *)notification
175{
176    NSNumber *reason = [[notification userInfo] valueForKey:AVAudioSessionRouteChangeReasonKey];
177    NSUInteger reasonEnum = [reason intValue];
178
179    if (reasonEnum == AVAudioSessionRouteChangeReasonUnknown) {
180#ifdef QT_DEBUG_COREAUDIO
181        qDebug("audioSession route changed. reason: unknown");
182#endif
183    } else if (reasonEnum == AVAudioSessionRouteChangeReasonNewDeviceAvailable) {
184#ifdef QT_DEBUG_COREAUDIO
185        qDebug("audioSession route changed. reason: new device available");
186#endif
187    } else if (reasonEnum == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) {
188#ifdef QT_DEBUG_COREAUDIO
189        qDebug("audioSession route changed. reason: old device unavailable");
190#endif
191    } else if (reasonEnum == AVAudioSessionRouteChangeReasonCategoryChange) {
192#ifdef QT_DEBUG_COREAUDIO
193        qDebug("audioSession route changed. reason: category changed");
194#endif
195    } else if (reasonEnum == AVAudioSessionRouteChangeReasonOverride) {
196#ifdef QT_DEBUG_COREAUDIO
197        qDebug("audioSession route changed. reason: override");
198#endif
199    } else if (reasonEnum == AVAudioSessionRouteChangeReasonWakeFromSleep) {
200#ifdef QT_DEBUG_COREAUDIO
201        qDebug("audioSession route changed. reason: woken from sleep");
202#endif
203    } else if (reasonEnum == AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory) {
204#ifdef QT_DEBUG_COREAUDIO
205        qDebug("audioSession route changed. reason: no suitable route for category");
206#endif
207    }
208
209}
210
211@end //implementation CoreAudioSessionObserver
212
213CoreAudioSessionManager::CoreAudioSessionManager() :
214    QObject(0)
215{
216    m_sessionObserver = [[CoreAudioSessionObserver alloc] initWithAudioSessionManager:this];
217}
218
219CoreAudioSessionManager::~CoreAudioSessionManager()
220{
221#ifdef QT_DEBUG_COREAUDIO
222    qDebug() << Q_FUNC_INFO;
223#endif
224    [m_sessionObserver release];
225}
226
227
228CoreAudioSessionManager &CoreAudioSessionManager::instance()
229{
230    static CoreAudioSessionManager instance;
231    return instance;
232}
233
234bool CoreAudioSessionManager::setActive(bool active)
235{
236    if (active) {
237        return [m_sessionObserver activateAudio];
238    } else {
239        return [m_sessionObserver deactivateAudio];
240    }
241}
242
243bool CoreAudioSessionManager::setCategory(CoreAudioSessionManager::AudioSessionCategorys category, CoreAudioSessionManager::AudioSessionCategoryOptions options)
244{
245    NSString *targetCategory = nil;
246
247    switch (category) {
248    case CoreAudioSessionManager::Ambient:
249        targetCategory = AVAudioSessionCategoryAmbient;
250        break;
251    case CoreAudioSessionManager::SoloAmbient:
252        targetCategory = AVAudioSessionCategorySoloAmbient;
253        break;
254    case CoreAudioSessionManager::Playback:
255        targetCategory = AVAudioSessionCategoryPlayback;
256        break;
257    case CoreAudioSessionManager::Record:
258        targetCategory = AVAudioSessionCategoryRecord;
259        break;
260    case CoreAudioSessionManager::PlayAndRecord:
261        targetCategory = AVAudioSessionCategoryPlayAndRecord;
262        break;
263    case CoreAudioSessionManager::AudioProcessing:
264#ifndef Q_OS_TVOS
265        targetCategory = AVAudioSessionCategoryAudioProcessing;
266#endif
267        break;
268    case CoreAudioSessionManager::MultiRoute:
269        targetCategory = AVAudioSessionCategoryMultiRoute;
270        break;
271    }
272
273    if (targetCategory == nil)
274        return false;
275
276    return [[m_sessionObserver audioSession] setCategory:targetCategory
277                                             withOptions:(AVAudioSessionCategoryOptions)options
278                                                   error:nil];
279}
280
281bool CoreAudioSessionManager::setMode(CoreAudioSessionManager::AudioSessionModes mode)
282{
283    NSString *targetMode = nil;
284    switch (mode) {
285    case CoreAudioSessionManager::Default:
286        targetMode = AVAudioSessionModeDefault;
287        break;
288    case CoreAudioSessionManager::VoiceChat:
289        targetMode = AVAudioSessionModeVoiceChat;
290        break;
291    case CoreAudioSessionManager::GameChat:
292        targetMode = AVAudioSessionModeGameChat;
293        break;
294    case CoreAudioSessionManager::VideoRecording:
295        targetMode = AVAudioSessionModeVideoRecording;
296        break;
297    case CoreAudioSessionManager::Measurement:
298        targetMode = AVAudioSessionModeMeasurement;
299        break;
300    case CoreAudioSessionManager::MoviePlayback:
301        targetMode = AVAudioSessionModeMoviePlayback;
302        break;
303    }
304
305    if (targetMode == nil)
306        return false;
307
308    return [[m_sessionObserver audioSession] setMode:targetMode error:nil];
309
310}
311
312CoreAudioSessionManager::AudioSessionCategorys CoreAudioSessionManager::category()
313{
314    NSString *category = [[m_sessionObserver audioSession] category];
315    AudioSessionCategorys localCategory = Ambient;
316
317    if (category == AVAudioSessionCategoryAmbient) {
318        localCategory = Ambient;
319    } else if (category == AVAudioSessionCategorySoloAmbient) {
320        localCategory = SoloAmbient;
321    } else if (category == AVAudioSessionCategoryPlayback) {
322        localCategory = Playback;
323    } else if (category == AVAudioSessionCategoryRecord) {
324        localCategory = Record;
325    } else if (category == AVAudioSessionCategoryPlayAndRecord) {
326        localCategory = PlayAndRecord;
327#ifndef Q_OS_TVOS
328    } else if (category == AVAudioSessionCategoryAudioProcessing) {
329        localCategory = AudioProcessing;
330#endif
331    } else if (category == AVAudioSessionCategoryMultiRoute) {
332        localCategory = MultiRoute;
333    }
334
335    return localCategory;
336}
337
338CoreAudioSessionManager::AudioSessionModes CoreAudioSessionManager::mode()
339{
340    NSString *mode = [[m_sessionObserver audioSession] mode];
341    AudioSessionModes localMode = Default;
342
343    if (mode == AVAudioSessionModeDefault) {
344        localMode = Default;
345    } else if (mode == AVAudioSessionModeVoiceChat) {
346        localMode = VoiceChat;
347    } else if (mode == AVAudioSessionModeGameChat) {
348        localMode = GameChat;
349    } else if (mode == AVAudioSessionModeVideoRecording) {
350        localMode = VideoRecording;
351    } else if (mode == AVAudioSessionModeMeasurement) {
352        localMode = Measurement;
353    } else if (mode == AVAudioSessionModeMoviePlayback) {
354        localMode = MoviePlayback;
355    }
356
357    return localMode;
358}
359
360QList<QByteArray> CoreAudioSessionManager::inputDevices()
361{
362    //TODO: Add support for USB input devices
363    //Right now the default behavior on iOS is to have only one input route
364    //at a time.
365    QList<QByteArray> inputDevices;
366    inputDevices << "default";
367    return inputDevices;
368}
369
370QList<QByteArray> CoreAudioSessionManager::outputDevices()
371{
372    //TODO: Add support for USB output devices
373    //Right now the default behavior on iOS is to have only one output route
374    //at a time.
375    QList<QByteArray> outputDevices;
376    outputDevices << "default";
377    return outputDevices;
378}
379
380float CoreAudioSessionManager::currentIOBufferDuration()
381{
382    return [[m_sessionObserver audioSession] IOBufferDuration];
383}
384
385float CoreAudioSessionManager::preferredSampleRate()
386{
387    return [[m_sessionObserver audioSession] preferredSampleRate];
388}
389
390#ifdef QT_DEBUG_COREAUDIO
391QDebug operator<<(QDebug dbg, CoreAudioSessionManager::AudioSessionCategorys category)
392{
393    QDebug output = dbg.nospace();
394    switch (category) {
395    case CoreAudioSessionManager::Ambient:
396        output << "AudioSessionCategoryAmbient";
397        break;
398    case CoreAudioSessionManager::SoloAmbient:
399        output << "AudioSessionCategorySoloAmbient";
400        break;
401    case CoreAudioSessionManager::Playback:
402        output << "AudioSessionCategoryPlayback";
403        break;
404    case CoreAudioSessionManager::Record:
405        output << "AudioSessionCategoryRecord";
406        break;
407    case CoreAudioSessionManager::PlayAndRecord:
408        output << "AudioSessionCategoryPlayAndRecord";
409        break;
410    case CoreAudioSessionManager::AudioProcessing:
411        output << "AudioSessionCategoryAudioProcessing";
412        break;
413    case CoreAudioSessionManager::MultiRoute:
414        output << "AudioSessionCategoryMultiRoute";
415        break;
416    }
417    return output;
418}
419
420QDebug operator<<(QDebug dbg, CoreAudioSessionManager::AudioSessionCategoryOptions option)
421{
422    QDebug output = dbg.nospace();
423    switch (option) {
424    case CoreAudioSessionManager::None:
425        output << "AudioSessionCategoryOptionNone";
426        break;
427    case CoreAudioSessionManager::MixWithOthers:
428        output << "AudioSessionCategoryOptionMixWithOthers";
429        break;
430    case CoreAudioSessionManager::DuckOthers:
431        output << "AudioSessionCategoryOptionDuckOthers";
432        break;
433    case CoreAudioSessionManager::AllowBluetooth:
434        output << "AudioSessionCategoryOptionAllowBluetooth";
435        break;
436    case CoreAudioSessionManager::DefaultToSpeaker:
437        output << "AudioSessionCategoryOptionDefaultToSpeaker";
438        break;
439    }
440    return output;
441}
442
443QDebug operator<<(QDebug dbg, CoreAudioSessionManager::AudioSessionModes mode)
444{
445    QDebug output = dbg.nospace();
446    switch (mode) {
447    case CoreAudioSessionManager::Default:
448        output << "AudioSessionModeDefault";
449        break;
450    case CoreAudioSessionManager::VoiceChat:
451        output << "AudioSessionModeVoiceChat";
452        break;
453    case CoreAudioSessionManager::GameChat:
454        output << "AudioSessionModeGameChat";
455        break;
456    case CoreAudioSessionManager::VideoRecording:
457        output << "AudioSessionModeVideoRecording";
458        break;
459    case CoreAudioSessionManager::Measurement:
460        output << "AudioSessionModeMeasurement";
461        break;
462    case CoreAudioSessionManager::MoviePlayback:
463        output << "AudioSessionModeMoviePlayback";
464        break;
465    }
466    return output;
467}
468#endif
469
470QT_END_NAMESPACE
471
472#include "moc_coreaudiosessionmanager.cpp"
473