AVAudioSession설정으로 인한 트러블 슈팅 과정

요즘 프로젝트를 진행하면서 너무 정신없이 바쁘단 핑계로 아주 오랜만에 블로그를 남긴다.. 영상 챗봇 관련 내용을 진행하면서 AVAudioSession을 다루는데 영상을 킨상태로 음성을 다루자니 자동으로 ducking되는 현상때매 꽤나 애먹었던 기억이 있어서 AVAudioSession은 어떤 것이고 무엇을 다루고 설정할 수 있는지 정리 해놓으려고 한다.

 

먼저 iOS에서 AVAudioSession은 앱과 iOS 오디오 시스템인 Core Audio 사이에서 중개자 역할을 담당한다. 여기서 어떤 방법을 통해 연결하는지 알아보도록 하자

AVFoundation계층에서 AVAudioSession은 AVFoundation 프레임워크 안에 구현된 클래스이다. 앱은 AVFoundation을 통해 오디오 세션을 구성하고 활성화 하면 이 요청이 내부적으로 CoreAudio의 Audio Session Service로 전달한다.

 

Audio Session Service는 Core Audio의 하위 모듈로, 오디오 I/O 설정(샘플 레이트, 버퍼 크기, 입출력 포맷)과 라우팅을 담당한다.

AVAudioSession의 setCategory(: mode: options:), setAction( :) 호출이 결국 Audio Session Service의 API( AudioSessionSetProperty, AudioSessionInitialize 등)를 호출하는 형태로 동작한다.

 

Hardware Abstraction Layer(HAL)은 Audio Session Service에서 인입된 값을 하드웨어 추상화 계층인 HAL에 전달하고 실제 디바이스 드라이버를 통해 물리적 장치와 통신하게 된다!

 

AVAudioSession이 활성화되면, 지정한 카테고리, 모드, 옵션에 맞춰 물리적 , 가상 장치에 연결할 수 있게 된다.

내장 스피커 iPhone/iPad 후면·전면 스피커. .playback, .playAndRecord 등에서 기본 출력으로 선택됨.
내장 마이크 장치 전·후면에 위치한 MEMS 마이크. 녹음용 기본 입력 장치.
수화부 스피커 통화용 스피커. .voiceChat 모드처럼 통화 최적화 모드에서 자동 선택될 수 있음.
헤드폰/이어폰 Lightning–to–3.5 mm 어댑터 혹은 무선 블루투스 장치. 연결 시 자동 라우팅됨.
Bluetooth HFP/A2DP 핸즈프리 프로파일(HFP)과 고음질 오디오(A2DP) 지원 헤드셋. 옵션으로 허용 여부 설정.
AirPlay 디바이스 같은 네트워크 내 AirPlay 지원 스피커·TV. .allowAirPlay 옵션으로 라우팅 가능.
HDMI/USB 오디오 Lightning–to–HDMI 어댑터, USB-C 허브 등에 연결된 외부 오디오 인터페이스.

상기 표와 같은 장치에 연결이 가능해진다는 점!!

 

그렇다면 라우팅되는 흐름은 어떻게 될까?

AVAudioSession 활성화 → Audio Session Service 가 현재 연결된 장치 스캔 → 앱 설정에 맞는 경로 선택 →

HAL 을 통해 디바이스 드라이버에 요청 → 오디오 입출력

이 흐름으로 진행되게 된다.

 

그럼 이제 AVAudioSession을 앱단에서 활성화 하는 방법을 알아보자!

let session = AVAudioSession.sharedInstance()

싱글 톤 AVAudioSession 인스턴스를 가져온 다음

try session.setCategory(.playAndRecord,
						mode: .voiceChat,
						options: [.allowBluetooth, .defaultToSpeaker]
)

앱 목적에 맞게 카테고리, 모드, 옵션을 설정하는 Session을 만들어준다.

 

그 다음 해당 세션을 활성화 해주면 된다.

try session.setAction(true)

이렇게 설정하면, 라우팅이 실행되고 HAL을 통해 실제 디바이스 드라이버가 오디오 스트림을 시작하게 된다

 

트러블 슈팅 - 영상 활성화 시 음성 Ducking 문제

이제 지금까지 개념에 대해서 알아봤다면 이제 내가 프로젝트를 진행하면서 어떤 문제를 겪었는지 보자.. 이 문제 때문에 며칠을 좀 고민했었는데 결국 답은 공식 문서에 있었다.

 

먼저 화면에는 얼굴 표정을 측정하기 위해 영상이 틀어져있는 상태였고 영상이 활성화 되어있는 상황이었기 때문에 이 상태에서 음성을 TTS를 통해 발산하게 된다면 음성이 원래 크기로 나오는게 아닌 작은 음성으로 나오는 문제가 있었다.

 

아까 위에서 음성을 활성화 시키기 위해서는 카테고리 설정을 해야한다고 했는데 해당 카테고리 설정이 여려개로 나누어져 있다 대표적으로는 아래와 같이 구성되어있다.

  • .playAndRecord → 출력 + 입력 동시에 활성화
  • .playback → 출력 전용, 백그라운드 재생 허용
  • .record → 입력 전용, 스피커는 음소거

그리고 카테고리와 모드를 설정해야하는데 주요 모드는 다음과 같다.

  • .spokenAudio → 팟캐스트, 오디오북, TTS 용 최적화, 기본적으로 다른 음성 프롬프트를 중단 시키고, 연속 재생에 적합하다.
  • .mixWithOthers → 다른 앱 오디오를 중단하지 않고 함께 재생한다.
  • .duckOthers → TTS 음성을 우선시하기 위해 다른 앱 오디오 볼륨을 일시적으로 줄인다.
  • .defaultToSpeaker → playAndRecord 사용 시 수화 부 스피커 대신 외부 스피커로 출력하게 한다.

다른 음성 혹은 영상이 활성화 되어있을 때 해당 하는 카테고리 및 모드를 잘 선택해야 내가 겪었던 Ducking 문제를 해결 할 수 있는데 어떤 카테고리와 모드가 존재하지 명확하지 않아 해당 문제를 쉽게 발견하기 어려웠다.

 

그래서 위 내용을 바탕으로 로직을 다음과 같이 수정했다.

let audioSession = AVAudioSession.sharedInstance()
do {
    // 1) TTS용 입출력 동시 활성화 + 스피커 기본 전환 + 다른 앱 오디오 볼륨 줄이기
    try audioSession.setCategory(
        .playAndRecord,
        mode: .spokenAudio,
        options: [.duckOthers, .defaultToSpeaker]
    )
    try audioSession.setActive(true)

    // 2) 다른 앱 오디오와도 자연스럽게 섞기 (필요 시)
    try audioSession.setCategory(
        .playAndRecord,
        mode: .spokenAudio,
        options: [.mixWithOthers, .defaultToSpeaker]
    )
    // → 이제 동영상 오디오와 TTS가 **함께** 출력되며, TTS가 재생되는 동안 동영상 소리는 **자동으로 작게** 들립니다.
} catch {
    print("오디오 세션 설정 오류: \\(error)")
}
  • 첫 호출
    • .duckOthers 옵션으로 시스템에 TTS가 나오면 다른 소리를 작게 하라고 요청한 뒤 setAction(true)로 세션을 활성화 시킨다.
    • mixWithOthers 옵션을 통해 다른 앱 오디오와도 섞어서 출력할 수 있도록 설정

이렇게 수정했더니 기존에 나를 괴롭혔던 Ducking 문제를 해결할 수 있었다. 왜 이런 문제가 일어났냐면 카테고리는 .playAndRecord를 잘 설정하긴 했지만 모드에서 정확히 음성이 어떤 방식으로 출력되도록 설정하는 부분에 대한 누락이 있었기 떄문이었다.

 

상기 코드와 같이 다른 오디오 세션과도 섞어서 출력할 수 있도록 설정하니 해당 문제가 해결 될 수 있었다..

 

음성을 제대로 다뤄본적이 이번이 처음이라 조금 어려운 부분이 있었지만 정리하면서 조금 나아진 것 같다. 추가적으로 음성 설정에 대해서 더 공부해서 추후 변경 될 모델에 잘 반영할 수 있도록 준비해놔야지..

오늘은 여기까지!