안녕하세요 회사를 다니며 진행했던 프로젝트 중 음원 관련 앱을 만들어본 경험이 있는데 이와 관련하여 개발하면서 많은 어려움을 겪었던 AVAudioEngine에 대해서 작성해보려 합니다.
특히 오디오 엔진 설정부터 재생 시작까지의 과정을 자세히 살펴보겠습니다
iOS 개발자라면 언젠가는 앱에 오디오 기능을 추가해야 할 때가 오는데요
AVAudioEngine으로 음악 재생, 음성 녹음, 실시간 오디오 처리 등 다양한 오디오 관련 작업을 수행해야 할 수 있습니다
이럴 때 우리의 강력한 동반자가 되어줄 프레임워크가 바로 AVAudioEngine입니다
AVAudioEngine은 Apple의 AVFoundation 프레임워크에 포함된 강력한 오디오 처리 시스템으로 복잡한 오디오 처리 작업을 간단하고 효율적으로 수행할 수 있게 해주는 API를 제공합니다
var engine_Song: AVAudioEngine? : 오디오 파일을 읽거나 쓰는 데 사용
var audioFile_Song: AVAudioFile? : 오디오 파일을 읽거나 쓰는 데 사용
var mixerNode_Song: AVAudioMixerNode! : 여러 오디오 소스를 하나로 혼합하는 노드
var mainMixer_Song : AVAudioMixerNode! : 주 믹서 노드로, 최종 오디오 출력을 제어
var inputNode_Song : AVAudioInputNode! : 오디오 입력(예: 마이크)을 처리하는 노드
var outputNode_Song : AVAudioOutputNode! : 최종 오디오 출력을 담당하는 노드
var audioFormat_Song: AVAudioFormat! : 오디오 데이터의 형식(샘플 레이트, 채널 수 등)을 정의
var nodePlayer_Song :AVAudioPlayerNode? : 오디오 파일을 재생하는 특수 노드
try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default, options: [.defaultToSpeaker, .allowBluetoothA2DP])
저가 개발하던 음원 앱에서는 블루투스와 기본 스피커에서 모두 재생이 가능하게 해야했기에 세션 옵션을 defaultToSpeaker,allowBluetoothA2DP 를 설정 했습니다
playAndRecord
options: [.defaultToSpeaker, .allowBluetoothA2DP]
audioFile_Song = try AVAudioFile(forReading: dest) -> dest: 파일 주소
self.nodePlayer_Song = AVAudioPlayerNode()
self.engine_Song = AVAudioEngine()
self.inputNode_Song = self.engine_Song?.inputNode
self.outputNode_Song = self.engine_Song?.outputNode
self.mainMixer_Song = self.engine_Song!.mainMixerNode
self.mixerNode_Song = AVAudioMixerNode()
오디오 각 구성별로 셋팅을 해주고
self.audioFormat_Song = AVAudioFormat(commonFormat: .pcmFormatFloat32,
sampleRate: sampleRate,
interleaved: false)
오디오의 샘플 레이트, 채널 수 등 어떤 셋팅으로 출력할지를 정합니다.
일반적인 sampleRate 값은 48000 Hz로 해주는데 이는 전문 오디오 및 비디오 제작에서 자주 사용해요 참고로 에어팟과 같은 일부 Bluetooth 장치는 24000 Hz (24 kHz)를 사용할 수 있어요!
self.engine_Song?.attach(mixerNode_Song)
self.engine_Song?.attach(nodePlayer_Song!)
이 두 줄의 코드는 AVAudioEngine에 오디오 처리 노드들을 연결하는 과정인데요 쉽게 말해, 오디오 시스템의 부품들을 메인 엔진에 장착하는 것과 같아요
self.engine_Song?.connect(inputNode_Song!, to: mixerNode_Song, format: audioFormat_Song)
입력 노드(마이크)의 오디오를 믹서 노드로 연결시켜줘 실시간 오디오 입력을 처리할 수 있게 해줍니다
self.engine_Song?.connect(mixerNode_Song, to: engine_Song!.mainMixerNode, format: audioFormat_Song)
커스텀 믹서 노드를 메인 믹서 노드에 연결(여러 오디오 소스를 혼합한 후 최종 출력)
self.engine_Song?.connect(nodePlayer_Song!, to: engine_Song!.mainMixerNode, format: audioFormat_Song)
오디오 플레이어 노드를 메인 믹서 노드에 연결(오디오 파일 재생에 사용)
do {
try self.engine_Song?.start()
print("오디오 엔진이 성공적으로 시작되었습니다.")
} catch {
print("오디오 엔진 시작 실패: \(error.localizedDescription)")
}
이후 연결된 AVAudioEngine을 실행 시켜주면 되는데요
AVAudioEngine을 실행시키면
위와 같은 일들이 진행 됩니다
하지만 아직 nodePlayer_Song를 실행 시키지 않았기때문에 음원 재생이 되지 않은 상태입니다
nodePlayer_Song?.volume = 1.0
음원 플레이 볼륨을 설정하고
if self.engine_Song?.isRunning == false {
try? self.engine_Song?.start()
}
engine_Song이 시작 상태인지 확인 후
self.nodePlayer_Song!.scheduleFile(self.audioFile_Song!, at: nil, completionCallbackType: .dataRendered) { (callback) in
print("노래 재생 완료 시 클로저 실행 즉 예약한 음원의 모든 데이터를 읽었을 때 실행 됨")
}
오디오 파일을 재생하도록 예약합니다
self.nodePlayer_Song?.play()
이 후 모든 셋팅을 맞췄다면 이제 음원을 재생하면 재생이 완료 됩니다
다음 포스팅에서는 노래가 종료된 후에 해야 하는 설정들에 대해서 다뤄보도록 하겠습니다!
감사합니다