본문 바로가기

ISSUE

[ISSUE] AVFoundation ( 3 ) - AVAudioEngine

 

 

AVAudioEngine

 

이번에는 audio의 rate와 pitch를 control 하기 위해 

AVAudioEngine을 사용하여 

Player를 구현하는 과정에서 생긴 issue에 대해서 포스팅하겠다.

 

AVFoundation의 강력한 클래스 중 하나인 AVAudioEngine

한 개체의 출력이 다른 개체의 입력이 되도록

오디오 처리 개체를 체인으로 연결하는 것이다. 

 

시작 부분에 오디오를 공급하고

중간에 처리를 적용한 다음

오디오를 출력으로 재생하여

많은 노력없이 실시간으로

오디오 조작을 수행 할 수 있다.

 

하지만 'local에 위치한 audio파일'  이라는 제한이 있었다.

어쩔수 없이 해당 URL에 있는 audio 파일을

stream이 아닌

디바이스에 다운로드 받는 프로세스로 변경을 하였다.

( 앱에서 제공하는 노래의 개수는 상당히 많았고

재생하려하는 각각의 노래를 재생할때마다

디바이스에 다운로드하는 프로세스는

디바이스에 심각한 저장공간 낭비로 이어질것을 예상하여

다운로드시 파일이름을 항상같은 이름으로 저장하여

파일이 덮어씌어지게끔 처리했다.)

// 항상 ThisIsDisposable이라는 이름을 가진 파일로 저장
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let fileURL = documentsURL.appendingPathComponent("ThisIsDisposable.mp3")

 

 

먼저 AVAudioEngine객체와

file을 가지게 될 AVAudioFile 하나를 준비한다.

let engine = AVAudioEngine()

var file: AVAudioFile?

그리고 AVAudioPlayerNode와 오디오의 rate, pitch를 변환하는 프로세서가 필요하다. 

let audioPlayer = AVAudioPlayerNode()
let speedControl = AVAudioUnitVarispeed()    
let pitchControl = AVAudioUnitTimePitch()

 

그다음 하단과 같이 file setting, engine 연결, engine start, play 작업을 해주면

해당 음원이 재생되는걸 볼 수 있을것이다.

func play(_ url: URL) throws {
    // 1: load the file
    // local에 저장된 file의 url을 사용하여 AVAudioFile을 생성한 후 할당
    let file = try AVAudioFile(forReading: url)

    // 2: connect the components to our playback engine
    // 컴포넌트를 재생 엔진에 연결
    engine.attach(audioPlayer)
    engine.attach(pitchControl)
    engine.attach(speedControl)

    // 3: arrange the parts so that output from one is input to another
    // 한 쪽의 출력이 다른쪽에 입력되도록 연결하여 정렬
    engine.connect(audioPlayer, to: speedControl, format: nil)
    engine.connect(speedControl, to: pitchControl, format: nil)
    engine.connect(pitchControl, to: engine.mainMixerNode, format: nil)

    // 4: prepare the player to play its file from the beginning
    // 플레이어가 파일을 재생하도록 준비
    audioPlayer.scheduleFile(file, at: nil)

    // 5: start the engine and player
    try engine.start()
    audioPlayer.play()
}

 

rate와 pitch를 control하는 방법은 

rate, pitch를 변환하는 목적으로 만들어놓았던 프로세서인

speedControl, pitchControl의 속성에 

값을 할당하면 손쉽게 변활할 수 있다.

pitchControl.pitch += 50
.
.
.
speedControl.rate += 0.1

 

이제 문제될께 없겠지 생각하던 찰나에

 

가끔 download를 진행할때

속도가 현저히 떨어지는 때가 있었는데 ( Alamofire를 사용한 download )

이부분에 있어서 다운로드 시간이 상당히 길어지는 issue가 발생했다.

 

그리하여 download가 아닌 stream을 사용하여 

음원을 재생해야 할거같다는 피드백이 돌아왔었고

 

어쩔수 없이 다시 player를 찾던중 나온

SAPlayer라는 library의 힘을 빌리기로 했다.

 

SAPlayer는 stream을 지원하는 동시에

pitch와 rate를 조정할 수 있었으며

URL을 연결했을때의 buffer 진행률,

Seek 메소드,

주기적으로 currentTime을 받아볼수 있는 메소드 등등

개발자가 보다 손쉽게 player를 구현할 수 있게 

만들어 놓은 library이다.

 

최종적으로 프로젝트에 적용한 Player는 SAPlayer

 

tanhakabir/SwiftAudioPlayer

Streaming and realtime audio manipulation with AVAudioEngine - tanhakabir/SwiftAudioPlayer

github.com

 

의도한바는 아니지만 

AVPlayer, AVAudioPlayer, AVAudioEngine, 외부 library를 사용하여 제각각 구현을 해봤다는 것에 대해

뭔가 경험이 쌓였다는 느낌을 받아 나름 만족했던 프로젝트였고

Apple에서는 CoreAudio 같은 더 low한 level의 Framework들도 있다는것을 알게 되었다.

시간적 여유가 된다면 objectc에 대해 공부한 다음 

CoreAudio에 대해 알아보는 시간을 가져야겠다.