이 예제는 아이폰의 마이크를 통해 소리가 들어오면 dBFS(위키백과 링크)를 측정하여 조용한 상태인지, 소음이 있는지를 검사합니다.

먼저 마이크 권한을 물어보는 메시지에 대한 설정이 필요합니다. 다음 마이크 녹음을 진행하면서 dBFS를 측정하고, 이 값에 따라 소음이 어떤지 메시지를 나타냅니다. dBFS가 -50을 벗어나는 경우 조용한 상태, -10 ~ 0 인 경우 시끄러운 상태이며 0을 초과하는 경우는 측정값이 더 이상 의미가 없을 정도로 시끄러운 상태를 의미합니다.

 

1) Info.plist 설정 추가

Info.plist 설정을 열고 (+) 버튼을 누르면 새로운 항목 추가 란이 나타납니다. 여기서 Privacy - Microphone Usage Description 을 검색한 다음 마이크 권한을 허용할지 묻는 메시지를 추가합니다.

 

2) 메인 스토리보드에 레이블 배치 후 아웃렛 연결

lblDecibel은 몇 dBFS 인지 나타내는 부분이며, 아래 lblLoudState는 현재 소음의 상태를 나타내는 부분입니다.

 

3) ViewController 코드 작성

import Foundation
import UIKit
import AVFoundation
import CoreAudio

class ViewController: UIViewController {
    
    var recorder: AVAudioRecorder!
    var levelTimer = Timer()
    
    @IBOutlet weak var lblDecibel: UILabel!
    @IBOutlet weak var lblLoudState: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        initRecord()
    }
    
    func initRecord() {
        
        switch AVAudioSession.sharedInstance().recordPermission {
        case AVAudioSessionRecordPermission.granted:
            record()
        case AVAudioSessionRecordPermission.denied:
            recordNotAllowed()
        case AVAudioSessionRecordPermission.undetermined:
            AVAudioSession.sharedInstance().requestRecordPermission({ (granted) in
                print(granted)
                if granted {
                    // timer는 main thread 에서 실행됨
                    // 그러나 requestRecordPermission 의 클로저 함수는 별도의 스레드에서 실행되므로
                    // 강제로 main 에서 실행되도록 한다.
                    // 
                    DispatchQueue.main.sync {
                        self.record()
                    }
                } else {
                    self.recordNotAllowed()
                }
            })
        default:
            break
        }
    }
    
    
    func recordNotAllowed() {
        print("permission denied")
    }
    
    func record() {
        
        let audioSession = AVAudioSession.sharedInstance()
        
        // userDomainMask에 녹음 파일 생성
        let documents = URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)[0])
        let url = documents.appendingPathComponent("record.caf")
        print("이 URL을 복사한 뒤 Finder - '이동' 메뉴 - '폴더로 가기'를 사용해 이동하세요.",
              documents.absoluteString.replacingOccurrences(of: "file://", with: "")
        )
        
        // 녹음 세팅
        let recordSettings: [String: Any] = [
            AVFormatIDKey:              kAudioFormatAppleIMA4,
            AVSampleRateKey:            16000, // 44100.0(표준), 32kHz, 24, 16, 12
            AVNumberOfChannelsKey:      1, // 1: 모노 2: 스테레오(표준)
            AVEncoderBitRateKey:        9600, // 32k, 96, 128(표준), 160, 192, 256, 320
            AVLinearPCMBitDepthKey:     8, // 4, 8, 11, 12, 16(표준), 18,
            AVEncoderAudioQualityKey:   AVAudioQuality.max.rawValue
        ]
        
        do {
            try audioSession.setCategory(AVAudioSession.Category.playAndRecord)
            try audioSession.setActive(true)
            try recorder = AVAudioRecorder(url:url, settings: recordSettings)
        } catch {
            return
        }
        
        recorder.prepareToRecord()
        recorder.isMeteringEnabled = true
        recorder.record()
        
        // 타이머는 main thread 에서 실행됨
        levelTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(levelTimerCallback), userInfo: nil, repeats: true)
    }
    
    @objc func levelTimerCallback() {
        recorder.updateMeters()
        
        let level = recorder.averagePower(forChannel: 0)
        
        lblDecibel.text = String(format: "%.0f dBFS",  level)
        
        // do whatever you want with isLoud
        if level < -48 {
            lblLoudState.text = "조용함"
        } else if level < -30 {
            lblLoudState.text = "약간의 소음 있음"
        } else if level < -10 {
            lblLoudState.text = "통상적인 소음 있음"
        } else {
            lblLoudState.text = "시끄러움"
        }
        
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    
}
  • Foundation, AVFoundation, CoreAudio 를 추가로 import 합니다.
  • initRecord 함수는 현재 마이크 권한 상태를 가져와, 마이크가 허용(granted)이 되어있으면 record 함수를 실행하고, 거부(denied)되었다면 recordNotAllowed 함수를 실행합니다.
  • undetermined 는 앱을 최초로 실행한 상태라 마이크 허용 여부가 결정되지 않은 상태로, 이 때 허용이 되었다면 별도의 부분에 작성을 해야 합니다.
  • undetermined 부분에서 주의할 점은 record 함수의 timer 부분인데 이 부분은 메인 스레드에서만 동작됩니다. 그러나 requestRecordPermission 의 클로저 함수는 별도의 스레드에서 실행되므로 스레드를 정해주지 않는 경우 timer가 실행이 되지 않습니다.
  • 따라서 위의 requestRecordPermission에서 granted 된 경우에는 강제적으로 record 함수를 main 스레드에서 실행시켜줘야 할 필요가 있습니다. (DispatchQueue.main.sync) – 참고
  • levelTimerCallback 에서 일정 인터벌마다 dBFS를 측정합니다. (참고: Swift(스위프트): 타이머(Timer) 만들기)

 

문의 | 코멘트 또는 yoonbumtae@gmail.com


카테고리: Swift


0개의 댓글

답글 남기기

Avatar placeholder

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다