관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] 5. AVFoundation 개념 - AVAudioSession, 녹음기능 구현 방법 (오디오 활성화, 입출력 변경, 잠금화면 재생, 재생 중 전화가 온 경우 처리) 본문

iOS 기본 (swift)

[iOS - swift] 5. AVFoundation 개념 - AVAudioSession, 녹음기능 구현 방법 (오디오 활성화, 입출력 변경, 잠금화면 재생, 재생 중 전화가 온 경우 처리)

jake-kim 2022. 4. 11. 23:29

1. AVFoundation 개념 - 구조, AVAsset, AVKit

2. AVFoundation 개념 - AVPlayer, AVPlayerItem

3. AVFoundation 개념 - AVPlayer 오디오 재생, 일시정지, 재생구간 이동 구현 (play, pause, seek)

4. AVFoundation 개념 - AVPlayer, AVPlayerLayer로 동영상 재생 방법

5. AVFoundation 개념 - AVAudioSession 개념 (오디오 활성화, 입출력 변경, 잠금화면 재생, 재생 중 전화가 온 경우 처리)

*  번외) 오디오 처리 - AVPlayer, AVAudioPlayer 개념 (실시간 스트리밍, 로컬 파일 재생)

AVAudioSession

  • AVAudioSession라는 싱글톤이 있고, 이 싱글톤을 통해 OS에서 마이크, 오디오 설정등이 가능
    • background에서 스피커 활성화
    • 잠금화면에서도 스피커 활성화
    • 앱간의 오디오 우선순위 설정, 확성화 설정
    • 에어팟을 갑자기 뺀 경우, 입출력장치가 변경되는 경우의 처리

https://developer.apple.com/library/archive/documentation/Audio/Conceptual/AudioSessionProgrammingGuide/Introduction/Introduction.html

  • AVAudioSession의 라이프 사이클은 앱의 라이프사이클과 일치
  • AVAudioSession을 가지고 사용할 수 있는 기능
    • 지금 앱에서 오디오를 활성화 / 비활성화 (active, deactive)
    • 오디오 모드 설정
    • 다른 앱들과 지금 앱의 오디오를 어떻게 호환할 것인지
AVAudioSession.sharedInstance().setActive(true)
AVAudioSession.sharedInstance().setMode(.gameChat)
  • AVAudioSession은 앱에게 이벤트도 전달
    • Audio Interrupt 발생
    • Audio Route Change 발생

오디오 활성화 동작

  • 내비게이션앱에서 "좌회전입니다" 음성이 나올 때 음악소리가 줄어드는 것처럼, "좌회전입니다"를 사용할 때 enabled 된 것
    • "좌회전입니다" 소리가 줄어들고 다시 외부 앱의 소리가 커지는 시점이 음성이 disabled된 것
  • enabled를 아래처럼 실행해도 더 높은 오디오 세션이 active되어 있다면, active 요청은 실패
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    let audioSession = AVAudioSession.sharedInstance()
    try? audioSession.setCategory(.soloAmbient)
    try? audioSession.setActive(true, options: .notifyOthersOnDeactivation)
    return true
}

Audio Route

  • 오디오 입력 소스로부터 오디오 출력 소스까지의 path를 의미 (n/w에서의 라우팅과 동일한 개념)
  • 연결중인 에어팟이 제거가 된 경우, 오디오 입력 소스에서는 reroute하여 오디오 입출력의 path를 재정의
    • OS에서 Audio Route가 변경되었다는 신호를 줄 때, 앱에서는 Notification을 통해 확인이 가능

ex) reroute 되는 그림

https://developer.apple.com/library/archive/documentation/Audio/Conceptual/AudioSessionProgrammingGuide/Introduction/Introduction.html

  • AVAudioSession.routeChangeNotification을 observe하여 이벤트처리
NotificationCenter.default.addObserver(
  self,
  selector: #selector(handleRouteChanged),
  name: AVAudioSession.routeChangeNotification,
  object: AVAudioSession.sharedInstance
)

@objc private func handleRouteChanged(notification: Notification) {
  guard
    let info = notification.userInfo,
    let reason = info[AVAudioSessionRouteChangeReasonKey] as? AVAudioSession.RouteChangeReason
  else { return }
  
  if reason == .oldDeviceUnavailable {
    let lastRoute = info[AVAudioSessionRouteChangePreviousRouteKey] as! AVAudioSessionRouteDescription
    let lastOutput = lastRoute.outputs[0]
    let port = lastOutput.portType
    
    if port == .headphones {
      // 재생 중지
    }
  }
}

AVAudioSession 다양한 기능

  • 아이폰의 좌측 음소거 스위츠를 켜도 재생되도록 설정 방법
    • AVAudioSession의 카테고리를 playBack으로 설정
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    try? AVAudioSession.sharedInstance().setCategory(.playBack)
    try? AVAudioSession.sharedInstance().setActive(true, options: .notifyOthersOnDeactivation)
    return true
}
  • 잠금화면에서도 재생되도록 설정 방법
    • info.plist에서 Required background modes 키값의 item 0에 App plays audio or streams audio/video using AirPlay으로 설정

  • Interrupt 발생 (재생 중 전화가 온 경우)
    • 음악이 서서히 볼륨이 정지되면서 정지되게끔 처리
    • AVAudioSession.interruptionNotification 이벤트 사용
NotificationCenter.default.addObserver(
  self,
  selector: #selector(handleInterruptOccured),
  name: AVAudioSession.interruptionNotification,
  object: AVAudioSession.sharedInstance()
)

@objc private func handleInterruptOccured(notification: Notification) {
  guard let interrupt = notification.userInfo?[AVAudioSessionInterruptionTypeKey] as? AVAudioSession.InterruptionType else { return }
  if interrupt == .began {
    print("인터럽트 시작")
  } else if
    let options = notification.userInfo?[AVAudioSessionInterruptionTypeKey] as? AVAudioSession.InterruptionOptions,
    options == .shouldResume
  {
    print("재개")
  }
}

* 참고

https://developer.apple.com/documentation/avfaudio/avaudiosession/1616627-setactive

https://developer.apple.com/library/archive/documentation/Audio/Conceptual/AudioSessionProgrammingGuide/Introduction/Introduction.html

Comments