Apple의 Audio Session Documentation 을 참고하여 따로 정리해본 오디오 세션 정리글입니다. Audio Session 동작에 대한 개괄적인 설명과 play, record 등을 위한 Audio Session Setting 방법을 다룹니다. 더 상세히 공부하고 싶다면 여기를 참고하세요.
Audio Session
Audio Session은 다음 그림으로 이해할 수 있다.
- OS단에서 관리하는 오디오 모듈이 있음. 애플에서는 앱에서 이 오디오 모듈과 통신할 수 있도록 오디오 세션 API를 제공함
- 앱이 시작되면 자동으로 오디오 세션이 제공됨 (AVAudioSession. 싱글톤) 오디오 세션의 라이프사이클은 앱의 라이프사이클과 일치
- 앱의 라이프사이클동안, 앱 오디오 세션과 OS단과의 인터랙션은 이 AVAudioSession 객체만을 통해 이루어진다
- 앱에서는 오디오 세션을 통해 다음과 같은 것들을 OS에 알릴 수 있다
- 우리앱에서 오디오를 쓸거야 / 다 썼어 (active, deactive)
- 우리앱에서 오디오를 어떤 모드로 사용할지 (category, mode, option)
- 다른 앱들이랑 우리 오디오랑 어떻게 호환할지 (mix, interrupt option)
- <참고> 이런 것들은 상황에 따라 제약이 걸리기도 해서 완벽히 자유롭게 설정할 수는 없다
- 오디오 세션은 앱에게 다음과 같은 것을 알려줄 수 있고, 앱은 이에 적절히 대응해야 한다
- Audio Interrupt 발생
- Audio Route Change 발생
- <참고> 위 2가지는 아래에서 상세히 다룰 예정
Audio Session Active & Deactive
Active & Deactive 방법
- AVAudioSession.shared.setActive(_, options:)
- true or false 를 넘겨서 active / deactive
- 항상 성공이 보장되지 않음
Audio Session Active (활성화)
- 간단히 표현하면, OS에게 앞으로 이 앱이 오디오를 쓸 것이라고 알리는 것. 오디오 enabled
- 앱이 사용할 오디오 옵션들을 구성한 뒤, 이 옵션을 반영하기 위해서는 오디오 세션을 활성화 해야한다
- 예시 > Apple Music 이 Background에서 Play 중인 상황에서, 내비게이션 앱이 경로 안내 TTS Sound를 Play 해야할 경우, ‘이제부터 이 앱이 Sound를 Play 할 거니까 다른 앱에서는 소리를 조금 줄여달라’ 라고 알려줄 수 있어야 한다. 이럴 때 Audio Session Active가 필요한 것. 아이폰에서 background music 을 재생한 채로 대중적인 지도 앱에서 내비게이션 경로 안내를 받아보면 오디오가 위와 같이 동작하는 것을 확인해볼 수 있다.
- 앱에서 Active를 요청했더라도 만약 우선순위가 더 높은 오디오 세션이 Active 되어있다면 (ex: incoming call) 앱의 active 요청은 실패한다
Audio Session Deactive (비활성화)
- OS에게 이 앱이 오디오를 다 썼다고 알리는 것. 오디오 disabled
- 예시 > 위 Active 예제에서 뉴스 앱에서의 TTS가 끝난 시점에 볼륨이 작아졌던 외부 앱들의 소리를 다시 원복할 수 있어야 한다. 이럴때 뉴스 앱에서 Audio Session Deactive 를 통해 오디오세션을 비활성화하고 재생을 중단하겠다고 OS에게 알려줄 수 있다
Audio Route
Audio Route란?
Electronic pathway of Audio signal. 오디오 입력 소스(built-in speaker, 외부 마이크 등)에서부터 오디오 출력 소스(헤드폰, 블루투스 오디오 등)까지의 pathway라고 생각하면 된다.
Audio Route Change에 대응하기
- 오디오 input/output이 추가되거나 제거되었을때 Audio Route가 변한다
- 예를 들어, 헤드폰 단자를 꽂거나 블루투스 이어폰을 연결했다고 생각해보자. 이때 오디오 세션은 Audio signal을 reroute 한다(==오디오 입출력 pathway를 다시 잡는다). 헤드폰 연결 전의 출력 소스가 폰 스피커였다면, 헤드폰 연결 후에는 출력 소스가 헤드폰으로 변경된다.
- OS에서 Audio Route 변경되었다는 신호를 주기 때문에 (Notification) 앱에서는 이 시점을 감지할 수 있다.
- 위는 iOS 앱에서 전형적으로 발생하는 Audio Route Change 상황에 대한 플로우이다.
- 플로우의 마지막 Action은 애플이 권장하는 사항이며, 앱의 서비스 정책에 따라 다른 처리를 해도 된다
- <참고> 애플의 권장사항: 일반적으로 사용자들은 헤드폰을 연결할 때 앱에서 재생하고 있던 콘텐츠를 그대로 이어서 재생하기를 원할 것이다. 반대로 헤드폰의 연결을 해제할 때에는 앱에서 재생하고 있던 콘텐츠를 폰 스피커로 이어서 재생하는 것을 원치 않을 것이다(privacy적인 이유로). 앱에서는 이런 암묵적인 룰을 존중해주는 것이 좋다. 예를 들면, 에어팟을 사용하여 유튜브 앱에서 영상을 보다가 에어팟을 갑자기 빼서 연결을 해제했을 때 보고있던 영상이 자동으로 멈추는 것을 떠올려볼 수 있다.
- <참고> 애플에서 제공하는 AVPlayer 를 사용하면 위 동작이 자동으로 보장된다. 반대로 말하면, 위와 다른 동작을 구현하고 싶다면 해당 플로우에 대해 추가 개발이 필요하다.
개발 API
- AVAudioSession.routeChangeNotification
- Audio Route Change Reason: 위 노티에 포함된 정보
- newDeviceAvailable - 새로운 기기 연결 시. 현재의 새로운 Audio output 이 어디로 route 되었는지의 정보가 들어있음
- oldDeviceUnavailable - 기존의 기기 연결 해제 시. 해제된 route에 대한 정보가 들어있음
Audio Interrupt
Audio Interrupt란?
우리 앱에서 OS 오디오 세션을 Active하고 오디오를 재생하는 도중, 우리 앱의 오디오보다 우선순위가 더 높은 오디오 이벤트가 발생할 수 있다. 예를 들면, 폰에서의 incoming call이 있다. 이 경우 call 의 우선순위가 더 높기 때문에 앱에서 재생되던 오디오는 멈추고 전화벨 소리가 재생된다. 우리 앱의 입장에서 이것이 ‘Audio Interrupt가 발생한 것'이다.
반대로, 타 앱이 재생중인 상황에서 우리 앱이 그것을 중지시키고 우리 앱의 오디오를 재생하게 할 수도 있다. 예를 들면, 팟캐스트가 재생되고 있는 도중 Apple Music 앱에서 음악을 재생시키는 경우를 생각해보자. 이때 팟캐스트의 재생 항목은 일시정지 되고 Apple Music 에서의 미디어가 재생되게 된다. 이 상황이 Music 앱이 ‘Audio Interrupt를 발생시킨 것’이다.
참고로 위와 같은 상황에서 하나의 오디오를 정지시키지 않고 둘 다 소리를 출력할 수 있도록 옵션을 설정할 수도 있다(mix). 몇 가지 옵션을 통해 두 개를 동일한 볼륨으로 출력할지, 다른 앱의 소리를 상대적으로 낮출지 등도 선택할 수 있으며, 이에는 약간의 제약사항도 존재한다. 옵션에 대한 것은 밑에서 따로 다룬다.
Audio Interrupt에 대응하기
Audio Interrupt가 발생했을 때와 끝났을 때, 오디오 세션에서 notification 을 발생시킨다. 앱에서는 이 노티를 받아 필요한 대응을 할 수 있다.
Interrupt Begin
- 앱에서 잘 재생하고있던 오디오가 외부 요인에 의해 멈출 때
- 우리 앱에서 오디오 세션을 active 하고 오디오를 재생하던 도중, 우선순위가 더 높은 오디오 이벤트가 발생할 경우 Interrupt가 발생한다
- ex) Incoming call, 재난경보, mix를 허용하지 않는 다른 앱에서의 사운드세션 Active 등
- 이 경우 우리 앱의 오디오 세션이 경쟁에서 밀려 비활성화(deactive)된다
- 오디오 세션으로부터 Audio Interrupt Notification 을 받을 수 있다
- 재생중이던 오디오가 멈췄기 때문에, 앱 서비스 정책에 따라 적절한 처리가 필요 (ex) 뮤직플레이어의 재생버튼을 일시정지 버튼으로 변경해주기 등
Interrupt End
- 우리 앱에 Interrupt를 발생시킨 외부 요인의 세션이 끝났을 때를 가리킴
- ex) 걸려오던 전화를 유저가 무시했을 때, 재난경보를 닫았을 때, 다른 앱에서 오디오 재생이 끝났을 때 등
- 오디오 세션으로부터 Audio Interrupt Notification 을 받을 수 있다
- 앱에서 오디오 세션을 다시 활성화하고 일시정지됐던 오디오를 resume 하는 등의 처리를 하면 된다 (중단된 시점에서의 유저 context에 따라 적절한 처리가 필요)
개발 API
- AVAudioSession.interruptionNotification
- AVAudioSession.InterruptionType: 위 노티에 포함된 정보
- began - 앱 오디오 Interrupt 시작됨
- ended - 앱 오디오 Interrupt 끝남. (항상 호출된다는 보장은 없음)
- AVAudioSessionInterruptionOptionKey 의 .shouldResume 을 체크해보면 resume 권장여부를 알 수 있다
참고
OS로부터 인터럽트 신호는 받을 수 있으나, 그 원인이 어떤 것인지는 알 수가 없다. 따라서 실제 코드에서는 Call Begin/End를 감지할 수 있는 CallKit 을 함께 사용하여 구현하고 있다. 인터럽트 원인이 Call인지, 외부앱인지에 따라 다른 처리가 필요하기 때문.
또한 실제 프로젝트에서는, 외부앱에 의해 Interrupt 되어 오디오 세션이 Deactivate 된다고 해서 외부앱의 재생이 끝나는 것만 기다리고 있으면 안 되는 경우가 있다. Deactivate 된 것을 감지한 직후라도, Sound 재생이 반드시 필요한 경우에는 Audio Session 을 다시 Activate 해야 한다는 것을 주의하자.
Audio Session Setting
Category / Mode / CategoryOptions 세 가지 범주를 소개하겠다. OS에게 앱에서 오디오를 어떻게 쓸지 알려주기 위한 방법들이며, 새로이 설정할 때에는 명시적으로 오디오세션을 deactive 후 다시 active 해주어야 한다. 주요한 것들 위주로 간단히 설명하겠다.
Category
경험 상 많이 쓰이는 것들
- playback: 무음모드일때도 소리 남
- playAndRecord: 녹음과 재생 같이 쓸 때 사용. 소리 input 필요할때 쓴다. 무음모드일때도 소리 남
- 위 카테고리 2개는 default가 noxmixable이며 (다른 앱과 소리 동시에 들리지 않음) 옵션을 통해 mixable하게 만들 수 있다
그 외의 것들
- ambient: for play along. mixable. 무음모드일 때 소리 안 남
- soloAmbient: default. nonmixable. 무음모드일 때 소리 안 남
- record: 오디오 녹음만을 위함 (재생 불가)
- multiRoute: 여러 루트로 오디오 출력 시 사용
아래는 간단한 비교 테이블이다. 원하는 스펙을 찾아 사용하면 된다.
Category | 폰 무음모드 or Lock 상태에서 | 다른 앱 오디오와 상호작용 | input(recording)/output(playback) |
ambient | play X | mix | output only |
soloAmbient | play X | interrupt | output only |
playback | play O | interrupt (옵션 추가로 mix 가능) | output only |
record | record O | interrupt | input only |
playAndRecord | play O record O | interrupt (옵션 추가로 mix 가능) | input & output |
multiRoute | play O | interrupt | input & output |
Mode
경험 상 많이 쓰이는 것들
- default: 웬만하면 이걸로 하면 된다
- moviePlayback: 영상 재생 시
- videoRecording: 영상 녹화 시
- spokenAudio: for continuous spoken audio. (ex) 팟캐스트 앱은 이 모드를 사용
- voicePrompt: 카플레이 등을 위한 모드. for text-to-speech
- (추천 호환 옵션: duckOthers & interruptSpokenAudioAndMixWithOthers)
참고사항
- Category와 Mode가 호환이 안 되는 쌍이 존재함. 그럴 때는 setting 이 아예 실패하므로 주의
- (ex) videoRecording은 record, playAndRecord 카테고리가 아니면 사용할 수 없다
- (ex) playAndRecord + .voicePrompt 는 같이 사용할 수 없다
CategoryOptions
경험 상 많이 쓰이는 것들
- mixWithOthers: 다른 앱의 소리와 우리 앱 소리가 함께 들릴 수 있게 하는 옵션.
- duckOthers: mixWithOthers 를 기본으로 채택하면서 +우리 앱에서 소리가 날 때는 다른 앱 소리를 살짝 죽이는 옵션.
- interruptSpokenAudioAndMixWithOthers: mixWithOthers를 기본으로 채택하면서 + 다른 앱에서 spokenAudio 가 출력될 때에만 interrupt 걸고 우리 앱이 소리를 독차지 (*라디오, 팟캐스트 등을 생각하면 됨)
- <참고> duckOthers와 함께 사용하게 되면 다른 앱에서 Media 종류의 Audio 를 재생할때는 duckOthers 의 설정을 따르고, 다른 앱에서 spokenAudio가 출력되고 있을 때에만 우리 앱이 interrupt 를 걸어버린다
- allowBluetoothA2DP: 블루투스 디바이스 지원을 위한 옵션
- playback 카테고리에서는 기본제공이지만 playAndRecord 카테고리에서는 기본이 아니기 때문에 따로 셋팅해주어야 함
- defaultToSpeaker: 연결된 오디오 receiver가 따로 있을때(ex: 아이폰 built-in receiver, 이어폰, 카오디오 등), 그쪽이 아닌 폰 speaker로 오디오를 전달하게 해주는 옵션
- playAndRecord 카테고리에서만 유효한 옵션
- 이 옵션과 같은 역할을 하는 AVAudioSession.sharedInstance().overrideOutputAudioPort(.speaker) 라는 일회성 함수도 있다
매너를 지키자
- duck/interrupt 옵션을 사용했다면 오디오가 종료될 때 deactive(with notifyOthersOnDeactivation option)를 통해 외부 앱들이 다시 오디오 세션을 차지할 수 있도록 알려주어야 한다
<참고> Navigation 앱을 위한 애플의 Audio Guideline
알아볼 일이 있어서 좀 알아보았던 것을 공유
- Audio Session Category: .playback 과 .playAndRecord 사용하기
- Audio Session Mode: Carplay 시에는 .voicePrompt 사용하기
- 타 앱과의 오디오 정책 : [.duckOthers, interruptSpokenAudioAndMixWithOthers]
- SpokenAudio 는 interrupt (라디오/팟캐스트가 겹치면 그것들을 일시정지 하기)
- 그 외의 오디오(music 등)와는 mix & ducking (음악 소리 줄어들며 동시에 출력)
- Prompt 재생(내비 TTS)이 필요하기 전까지는 오디오 세션을 활성화하지 말라
- Prompt 재생(내비 TTS)이 끝나면 즉시 오디오 세션을 비활성화하라
- 한 번 Interrupt 당한 prompt를 즉시 다시 재생하려고 시도하지 말라
'iOS 일반 > iOS' 카테고리의 다른 글
Data Flow Through SwiftUI (2) | 2020.02.25 |
---|---|
Swift로 그래프 탐색 알고리즘을 실전 문제에 적용해보기 - BFS 편 (1) | 2020.01.10 |
Swift로 그래프 탐색 알고리즘을 실전 문제에 적용해보기 - DFS 편 (1) | 2020.01.07 |
Swift로 작성해보는 기본 자료구조 - Stack, Queue (0) | 2020.01.06 |