지난 번에는 오디오 세션을 설정하는 법에 대해 다뤄보았다.
이제 AVAudioRecorder에 대해 살펴보고, 녹음 처리를 위해 설정할 값들에 대해 알아보자.
AVAudioRecorer 객체를 사용하는 class
또한 녹음 동작은 AVAudioSession이 record 혹은 playAndRecord 카테고리일 때만 작동한다.
이 클래스는 두 가지 설정값을 가져야 한다.
하나는 바로 녹음이 저장될 경로인 URL값, 다른 하나는 AVAudioFormat의 형식을 지정받아야 한다
AVAudioFormat을 통해 sampleRate와 channelLayout 등을 지정할 수 있다.
이를 정의하는 방법은 여러개이겠지만 특정한 키-값 쌍의 배열을 통해 지정하는 settings를 통해 지정이 가능하다.
audio settings의 키값과 그에 맞는 value들을 살펴보겠다.
AppleLosslessALAC로 주로 설정을 하는 듯하다.
얼마나 선명한 음질을 가질 것인지 결정하면 된다.
기본 세팅값외에도 Linear PCM Settings 혹은 Encoder settings 등의 옵션을 별도로 설정할 수 있다.
그 중 Encoder settings의 키와 값을 하나 살펴보겠다.
let settings: [String:Any] = [
AVFormatIDKey: kAudioFormatAppleLossless,
AVSampleRateKey: 44100.0,
AVNumberOfChannelsKey: 1,
AVEncoderAudioQualityKey: AVAudioQuality.max.rawValue
]
//이런 식으로 설정을 정의하여
audioRecorder = try AVAudioRecorder(url: //url값 넣기, settings: settings)
//이런 식으로 레코더 객체를 설정할 수 있다.다음의 변수들은 AVAudioRecorder class 내에 정의되어 있어 audioRecorder를 통해 호출가능하다.
녹음을 진행 중에 Meter, 즉 음향 신호의 크기를 측정할 수 있다.
이 Meter값은 dBFS 단위로 반환된다.
dBFS : 디지털 오디오 시스템에서 신호의 크기를 나타내는 단위
Full Scale은 디지털 오디오 시스템에서 표현할 수 있는 최대의 값, dBFS는 이 최대값을 기준으로 얼마나 신호가 큰지 로그 스케일로 나타낸다.
isMeteringEnabled: Bool
updateMeters()
averagePower(forChannel: Int) → Float
peakPower(forChannel: Int) → Float
녹음 재생 중에 입력값의 세기에 따라서 움직이는 이펙트를 주고 싶다면 이 meter 측정을 사용하면 좋다.
audioRecorder.isMeteringEnabled = true
timer = Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { [weak self] _ in
guard let this = self else { return }
this.audioRecorder.updateMeters()
this.soundSample = AudioSampleModel(sample: this.audioRecorder.averagePower(forChannel: 0))
}
위 코드에서는 Metering을 true로 설정하고 타이머를 줘 0.05초마다 새로운 미터로 업데이트하고 그 간격의 평균 dBFS를 AudioSample에 저장했다.
오디오 레코더 클래스를 delegate하도록 설정하여
녹음 이벤트에 관련된 에러나 녹음이 완료되었을 때 처리를 따로 설정할 수 있다.
그리고 AVAudioRecorderDelegate는 NSObjectProtocol을 따르기 때문에 delegate를 선언하는 class는
NSObjectProtocol을 준수하는 NSObject class를 상속한다고 선언하는 게 좋다.
그게 아니면 NSObjectProtocol의 func들을 정의하는 등 conformance를 위해 stubs를 추가해야 한다.
아니면 아래와 같은 에러 메시지를 마주할 것이다.
Cannot declare conformance to 'NSObjectProtocol' in Swift;
'className' should inherit 'NSObject' instead
실제 사용 예시를 살펴보자
//녹음 전 AVAudioRecorder 객체에 delegate를 self로 설정한다.
audioRecorder = try AVAudioRecorder(url: audioFileName, settings: settings)
audioRecorder.delegate = self
// 또한 관련 녹음 class나 담당하는 구조가 AVAudioRecorderDelegate 프로토콜을 준수하도록 설정한다.
//그러면 다음 두 가지 func을 상속받는다.
func audioRecorderEncodeErrorDidOccur(_ recorder: AVAudioRecorder, error: (any Error)?) {
audioRecorder.stop()
print(error?.localizedDescription ?? "설명이 없는 에러입니다")
}
func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) {
if !flag {
audioRecorder.stop()
print("녹음이 제대로 종료되지 않아 재종료합니다")
}
}
audioRecorderEncodeErrorDidOccur에는 오디오 엔코딩을 할 때 에러가 발생할 경우 처리할 기능을 추가할 수 있다.
이 때 나는 녹음이 정상 종료되도록 audioRecorder.stop()을 선언했다.
func audioRecorderDidFinishRecording은 flag 값에 따라 레코딩이 성공적으로 끝나거나, 아닐 경우 처리를 추가할 수 있다.
flag가 true이면 녹음이 성공적으로 종료된 것이고 false이면 아닌 것이다.
역시 이곳에도 제대로 종료되도록 stop()을 추가했다
AVAudioRecorder에 대해 살펴보았다.
어떻게 녹음 전 설정하는지, 녹음 할 때 사용할 기본 func과
녹음할 때 입력 세기를 수치로 받는 meter,
녹음이 안전하게 종료될 때나 에러를 잡을 수 있는 delegate에 대해서도 살펴보았다.
AVaudioPlayer는 기본적으로 AVAudioRecorder와 유사하기 때문에 AVAudioRecorder를 사용했다면 바로 이해할 수도 있을 듯하다.
AVAudioPlayer를 간단하게 살펴보는 글은 나중에 올려보겠다.