개인 프로젝트 기록 - 2 (Recording)

2023. 4. 1. 19:00iOS/프로젝트

녹음 테스트

재생을 테스트 했으니, 음성 메시지를 녹음할 수 있도록 그 기능을 구현해 보는 것이 목표

 

목표 달성


AudioSession 카테고리 설정 (앞선 설정과 동일)

/// 재생 + 녹음 (playAndRecord) 모드
try audioSession.setCategory(.playAndRecord)
 

User Permission 받기

func getUserPermission() {
    audioSession.requestRecordPermission() { [unowned self] allowed in
        DispatchQueue.main.async {
            if allowed {
                self.isRecordingAllowed = true
            } else {
                // TODO: 비동의의 경우는? 어떻게 처리?
                print("Recording is not allowed by user")
            }
        }
    }
}
 
녹음 기능을 사용하기 위해 사용자의 허가가 필요하다.
 
  • Info.plist 에 Privacy - Microphone Usage Description 항목 추가 후 유저에게 보여질 사용 목적(Description) 을 추가한다.
  • 오디오 세션의 requestRecordPermission(_:) 라는 인스턴스 메소드를 사용해 사용자 허가를 요청한다. 

예제 코드를 가져왔는데, unowned self 를 그대로 사용할 지 고민이었다. 싱글톤은 사용할 때 처음 만들어지고 앱이 살아있는 동안에는 메모리에서 제거되지 않는다고 하니, 이 싱글톤에서의 self는 unowned로 선언되어도 관계 없다고 판단해서 그대로 사용하기로 했다.

 

이제 새로운 질문 - 사용자가 허용을 하지 않거나 허용했다가 뒤늦게 거절하면 어떻게 되는 거지? - 이거는 나중에 유저 플로우 발전시키면서 해결해 보도록 하겠다. (일단 목표는 녹음하는 것이니까!)

 

AVAudioRecorder 사용하기

/// AVAudioRecorder: 오디오 데이터를 파일에 녹음하는 객체
var audioRecorder: AVAudioRecorder!
 
우선 AVAudioPlayer 처럼 오디오 데이터를 파일에 녹음하기 위해 AVAudioRecorder 라는 객체가 필요하다.
 
// TODO: 적절한 세팅값 찾기
let settings = [
    AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
    AVSampleRateKey: 12000,
    AVNumberOfChannelsKey: 1,
    AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
]

do {
    /// url에 audio data를 쓰게 된다
    audioRecorder = try AVAudioRecorder(url: audioFilename, settings: settings)
    audioRecorder.record()
    self.isRecordingStarted = true
} catch {
    print(error)
}
 
일단 크게 두 단계가 있다.
  • url, settings 와 함께 AVAudioRecorder 생성
url : 오디오 파일이 쓰여질 경로
settings : Recorder와 관련된 설정 값들

사실 settings 내용도 자세히 알아볼까 했는데, 우선 예제 코드가 잘 동작하기도 하고 오디오에 관한 깊은 조사가 필요할 것 같아 이것도 나중의 나에게 토스..하겠다.

 

  • 녹음 시작

만들어진 audioRecorder의 record() 함수를 실행한다.

 

 

목표 달성을 위해 필요한 것들


오디오 파일 저장을 위한 파일 시스템 이용하기

오디오 파일을 저장하기 위해서는 사용자의 기기에 접근할 수 있는 인터페이스가 필요하다.

그 역할을 해 주는 것이 바로 FileManager 이다. 이를 이용해 파일 시스템에 접근할 수 있는 경로를 얻을 함수를 만든다.

 

func getDocumentsDirectory() -> URL {
    /// FileManager: 파일 시스템의 콘텐츠에 접근할 수 있게 해 주는 인터페이스이자 파일 시스템과 상호작용할 수 있는 주요 수단 (Class)
    /// 사용자에 의해 생성되는 모든 Contents는 document 디렉토리에 저장되도록 강하게 권장된다고 한다
    /// default: FileManager의 싱글톤 객체를 만들어 준다
    let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) // -> [NSURL]
    return paths[0]
}
 
  • FileManager.default는 파일 매니저의 싱글톤 객체를 만들어 준다.
  • 사용자에 의해 생성되는 모든 Contents는 document 디렉토리에 저장되도록 강하게 권장된다고 한다. 따라서 documentDirectory 의 path를 가져온다.
  • userDomainMask는 공식 문서에 따르면 The user’s home directory—the place to install user’s personal items (~). 라고 되어 있다.
 
이 외의 다른 옵션은 아래와 같이 있는데 대충 봐도 userDomainMask를 쓰는 게 맞다는 느낌이 든다.

왜 paths가 여러 개이고, 그 중 맨 앞의 것을 가져와야 하는 지는 찾아봐도 잘 모르겠다. (과제 +1)

 

이제 위에서 얻은 파일 시스템 경로를 가지고 새로운 파일을 생성할 것이다.

일단 이름은 만들어진 날짜와 시간으로 설정하도록 했다.

 

// 파일 이름 생성
let formatter = DateFormatter()
formatter.dateFormat = "yy-MM-dd--HH-mm"
fileName = formatter.string(from: Date())

// 파일 이름과 확장자를 포함한 전체 경로 설정
let audioFilename = getDocumentsDirectory().appendingPathComponent("\(fileName).m4a")

 

appendingPathComponent 는 주어진 path 에 새로운 요소를 추가해 새로운 URL을 리턴해 준다.

 

/// url에 audio data를 쓰게 된다
audioRecorder = try AVAudioRecorder(url: audioFilename, settings: settings)
 
AVAudioRecorder 를 생성할 때 url을 넣어서 해당 위치에 오디오 파일이 쓰여질 수 있도록 한다.

 

 

Info.plist 설정

아래의 설정을 true로 해 주어야 디렉토리에 저장이 되는 것을 확인할 수 있다.

관련 설명은 아래와 같다.

 

남아있는 질문들


  • 유저가 녹음을 허용 했다가 유저가 허용 안함으로 바꾼 다음엔 에러가 나는지? 그걸 어디서 처리할지?
  • 매번 유저 허가를 요청하지 말고, 허용 여부를 기억해서 처리하는 법?
  • AudioRecorder의 적절한 settings 값은?
  • 공식 문서에서 오디오 관련 작업 중 인터럽트가 발생했을 경우 그를 처리하는 코드를 그대로 가져왔는데, 아직은 엄두가 안 난다. 시간 날 때 해석해 보자!
  • Paths 여러개인 이유와 왜 맨 앞을 가져오는지?
  • 오디오 파일들을 저장하고 가져오는 등 관리할 방법이 필요한데, 어떻게 하지?

 

대답한 질문들


  • 싱글톤의 인스턴스는 언제 생성될까?

→ 싱글톤의 인스턴스는 AudioManager.shared 하는 순간 생성된다. 따라서 나는 제일 첫 뷰의 onAppear에서 AudioManager.shared.getUserPermission() 을 처리해 주고 있는데 이렇게 shared 에 접근하는 순간 생성된다고 보면 된다.

 

  • AVAudioPlayer는 다른 오디오 파일 마다 매번 새로 할당해 주어야 하나?

→ 검색 결과 stackoverflow 선배님덜에 따르면 AVAudioPlayer를 만드는 비용이 크지 않아서 매번 생성해 주어도 된다고 한다

 

 

이전 이야기


https://mila00a.tistory.com/55

 

개인 프로젝트 기록 - 1 (Ear Speaker)

Ear Speaker 테스트 귀로 전화를 받아야 하는 것이 주 아이디어이기에, Ear Speaker를 사용하는 것을 가장 먼저 테스트 해 보려 한다. 목표 달성 처음에 검색한 결과 /// Ear speaker로 재생하기 위한 설정 ?

mila00a.tistory.com

 

다음 이야기


https://mila00a.tistory.com/57

 

개인 프로젝트 기록 - 4 (CoreData)

Core Data 사용하기 어쨌든 한번만 사용하고 끝낼 게 아니기 때문에 정보들을 저장해야 합니다. 그래서 1) CoreData를 SwiftUI에서 사용하는 법을 이해하고, 2) 실제 나의 프로젝트에 적용해 볼 것입니다

mila00a.tistory.com