Swift로 녹음 기능 구현하기 - AVAudioRecorder

나우리·2024년 10월 31일

Swift

목록 보기
11/13

지난 번에는 오디오 세션을 설정하는 법에 대해 다뤄보았다.
이제 AVAudioRecorder에 대해 살펴보고, 녹음 처리를 위해 설정할 값들에 대해 알아보자.

AVAudioRecorder

AVAudioRecorer 객체를 사용하는 class

또한 녹음 동작은 AVAudioSession이 record 혹은 playAndRecord 카테고리일 때만 작동한다.
이 클래스는 두 가지 설정값을 가져야 한다.
하나는 바로 녹음이 저장될 경로인 URL값, 다른 하나는 AVAudioFormat의 형식을 지정받아야 한다

AVAudioFormat을 통해 sampleRate와 channelLayout 등을 지정할 수 있다.
이를 정의하는 방법은 여러개이겠지만 특정한 키-값 쌍의 배열을 통해 지정하는 settings를 통해 지정이 가능하다.

audio settings

audio settings의 키값과 그에 맞는 value들을 살펴보겠다.

  • AVFormatIDKey: 녹음파일의 압축 형식을 설정가능하다. 용량관리와 원하는 녹음의 선명도에 따라 설정을 변경해야 한다.
    • kAudioFormatLinearPCM : 무압축 오디오 데이터 형식
    • MPEG4AAC : 높은 압축률과 품질을 제공
    • AppleLosslessALAC, 손실없는 압축 오디오 코덱
    • iLBC : 낮은 비트레이트의 오디오 코덱 VoIP에 주로 이용
    • ULaw : uLaw(Mu-low) 압축 형식, 주로 통신 시스템에서 사용

AppleLosslessALAC로 주로 설정을 하는 듯하다.

  • AVSampleRateKey: 8 kHz ~ 192 kHz까지 샘플rate를 설정가능하다.
    • 녹음이 저장될 때 일직선으로 저장되는 게 아니라 소리를 특정 floating point로 쪼개어 소리를 재현한다.
      이때 초당 샘플되는 횟수를 이 키값으로 설정할 수 있다. 즉 1Hz 동안 오디오 데이터를 몇 번 샘플링하는지 설정이 가능하다.
    • 당연히 샘플레이트키값이 크면 클수록 녹음 소리와 유사해진다
    • 8000 Hz : 전화 통화 품질, 기본 음성녹음에 적합
    • 44100 Hz 이상 : CD, 고품질 녹음에 적합

얼마나 선명한 음질을 가질 것인지 결정하면 된다.

  • AVNumberOfChannelKey: 1 ~ 64까지 설정 가능한 채널 수이다.
    • 이 때 채널이란 입력하는 기기의 갯수라고 생각하면 된다.
    • 노트북에서 음성 파일 정보를 보면 오디오 채널 부분에서 모노 또는 스테레오인지 확인할 수 있다.
    • 모노는 한 개의 마이크로 녹음하거나 스피커로 재생하는 것이고 스테레오는 두 개로 하는 것이다.
    • 실제 기기 녹음 채널이 1개 밖에 없다면 2로 설정하는 것은 의미가 없다.
      오히려 1개 채널 녹음한 값을 복사하여 이용하기에 크기적인 부분에선 손해이기 때문에 정확히 설정하는 게 좋다.

기본 세팅값외에도 Linear PCM Settings 혹은 Encoder settings 등의 옵션을 별도로 설정할 수 있다.
그 중 Encoder settings의 키와 값을 하나 살펴보겠다.

  • AVEncoderAudioQualityKey : 오디오의 퀄리티 정도를 지정
    • enum으로 정의된 다음과 같은 값들을 가진다.
      • min = 0
      • low = 32
      • medium = 64
      • high = 96
      • max = 127
    let settings: [String:Any] = [
           AVFormatIDKey: kAudioFormatAppleLossless,
           AVSampleRateKey: 44100.0,
           AVNumberOfChannelsKey: 1,
           AVEncoderAudioQualityKey: AVAudioQuality.max.rawValue
       ]
    //이런 식으로 설정을 정의하여
    audioRecorder = try AVAudioRecorder(url: //url값 넣기, settings: settings)
    //이런 식으로 레코더 객체를 설정할 수 있다.

기본적으로 정의된 녹음 관련 func

  • func prepareToRecord() → Bool
    • 오디오 파일을 만들고 녹음 준비를 함
    • 해당 위치에 파일이 있는 경우 덮어쓴다.
    • 보통 아래 기본 녹음 시작 func에서 자동으로 호출되기에 별도 선언이 필요없다.
  • record() → Bool
    • 오디오 녹음을 시작하거나 중도 정지된 상태에서 재시작한다.
    • 암묵적으로 prepareToRecord 호출한다.
  • record(atTime: TimeInterval) → Bool
    • 특정 시간에 시작하여 녹음한다
  • record(forDuration: TimeInterval) → Bool
    • 특정 시간동안 녹음함
  • record(atTime: TimeInterval, forDuration: TimeInterval) → Bool
    • 특정 시간에 시작하여 특정 기간까지 녹음함
  • pause()
    • 오디오 녹음 잠시 중단한다. 이 때 파일은 닫히지 않아 record()를 통해 멈춘 위치에서 녹음 재개가 가능하다.
  • stop()
    • 녹음을 종료하고 오디오 파일을 닫는다.
  • deleteRecording() → Bool
    • 녹음된 오디오 파일을 삭제한다.

녹음과 관련된 변수

다음의 변수들은 AVAudioRecorder class 내에 정의되어 있어 audioRecorder를 통해 호출가능하다.

  • isRecording:Bool
    • 오디오 레코더가 녹음중인지를 반환하는 bool
  • currentTime : TimeInterval
    • 녹음 시작 후 초기준으로 지나간 시간
    • 이 시간을 조절하여 특정 시간에 녹음을 시작하도록 할 수 있다.
  • deviceCurrentTime: TimeInterval
    • 오디오 기기의 시간, 초기준

오디오 Meter와 관련된 func, 변수

녹음을 진행 중에 Meter, 즉 음향 신호의 크기를 측정할 수 있다.
이 Meter값은 dBFS 단위로 반환된다.

dBFS : 디지털 오디오 시스템에서 신호의 크기를 나타내는 단위
Full Scale은 디지털 오디오 시스템에서 표현할 수 있는 최대의 값, dBFS는 이 최대값을 기준으로 얼마나 신호가 큰지 로그 스케일로 나타낸다.

  • isMeteringEnabled: Bool

    • 녹음기가 오디오 레벨 미터링 데이터를 만드는지 결정하는 Bool
  • updateMeters()

    • 평균과 peak power 값을 리프레쉬, 다시 받아 계산한다.
  • averagePower(forChannel: Int) → Float

    • 평균 power를 채널을 기준으로 dBFS로 반납한다.
    • 이 때 채널은 0부터 시작하며 0일 때 모노 채널이다.
    • dBFS는 음향 신호를 기기가 표현할 수 있는 최대와 기기가 감지할 수 있는 최소값으로 나타낸다. 이 범위는 -160~ 0으로 반환된다.
    • -160 dBFS (최소 파워) ~ 0 dBFS (최대 파워)
    • 0dBFS : 디지털 시스템에서 표현 가능한 최대 레벨, 더 큰 값은 클리핑이 발생하여 소리가 왜곡될 수 있다.
  • peakPower(forChannel: Int) → Float

    • 최대 파워를 dBFS 단위로 반납한다. 역시 -160 ~ 0으로 반납한다.

녹음 재생 중에 입력값의 세기에 따라서 움직이는 이펙트를 주고 싶다면 이 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하도록 설정하여
녹음 이벤트에 관련된 에러나 녹음이 완료되었을 때 처리를 따로 설정할 수 있다.

  • delegate: (any AVAudioRecorderDelegate)?
    • 오디오 레코더의 delegate 오브젝트
  • protocol AVaudioRecorderDelegate
    • 오디오 레코딩 이벤트와 에러에 반등하는 메소드를 정의한 프로토콜

그리고 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를 간단하게 살펴보는 글은 나중에 올려보겠다.

profile
왕초보 개발일지

0개의 댓글