Swift: how to get decibels

You might had a chance to check out the new trend – scream application. The most popular one I believe “Chicken Scream”. The new weird and funny interface for the games. Let’s take a look at how chicken scream works – the main part of functionality is to detect the background noice to set up the threshold, and to get “loudness” value – some how to measure the decibels.
In scope of my tests and investigations I’ve decided to create a flappy bird like game but where you need to scream, kind of flappy bird scream, you may want check out the result – “Scream to Fly” (appstore link, my site link)

All right, the first thing to start you need to request access to the mic, to have such ability we need to add the following to the Info.plist – click “plus” and start typing “Privacy – Microphone Usage Description” and add the value, something like – “Access to microphone is needed to detect noice level only.”. This message will appear when you request the access to it programmatically.

To check whether permissions were granted or to request ones you’ll need the following method:

func permissionWasGranted(result: @escaping (_: Bool)->()) {
    switch AVAudioSession.sharedInstance().recordPermission() {
    case AVAudioSessionRecordPermission.granted:
        if IS_DEBUG { print("Permission granted") }
        result(true)
        return
    case AVAudioSessionRecordPermission.denied:
        if IS_DEBUG { print("Pemission denied") }
    case AVAudioSessionRecordPermission.undetermined:
        if IS_DEBUG { print("Request permission here") }
        AVAudioSession.sharedInstance().requestRecordPermission({ (granted) in
            if granted {
                result(true)
                return
            }
        })
    default:
        break
    }
    result(false)
}

In case of AVAudioSessionRecordPermission.denied user won’t be shown this request dialog anymore, the good practice is to detect this situation and show something on UI, just for instance – “you have disabled access to mic” and the screenshot how to enable it back in settings.

By the way, here’s the list of all imports you might need

import CoreAudio
import CoreAudioKit
import AVFoundation
import Foundation
import AVKit

Ok, let say user granted the access to the mic, now we are ready to start detecting the loudness, something like

audioService.permissionWasGranted(result: { granted in
    if (granted) {
        if IS_DEBUG { print("we have mic access!") }
        self.micOverlay.removeFromParent()
        self.audioService.initRecorder()
        self.audioService.start()
        self.audioService.startCalibration()
        self.initGame()
    } else {
        if IS_DEBUG { print("we don't have mic access :(") }
        self.micOverlay.render(rect: self.calculateOverlayRect(), text: "no mic access...")
        self.addChild(self.micOverlay)
    }
})

Let’s take a look at what is audioService and what initRecorder and start methods are doing

First of all, here’s the declaration of AudioService class and it’s protocols and some boilerplate

import CoreAudio
import CoreAudioKit
import AVFoundation
import Foundation
import AVKit
 
class AudioService : UIViewController, AVAudioRecorderDelegate {
 
    //...
 
    private var recorder : AVAudioRecorder? = nil
 
    func getDocumentsDirectory() -> URL {
        let fileManager = FileManager.default
        let urls = fileManager.urls(for: .documentDirectory, in: .userDomainMask)
        let documentDirectory = urls.first!
        return documentDirectory.appendingPathComponent("recording.m4a")
    }
 
 
    // MARK: protocol
 
 
    func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) {
        recorder.stop()
        recorder.deleteRecording()
        recorder.prepareToRecord()
    }
}

Note, that you have to conform AVAudioRecorderDelegate protocol to be able to use AVAudioRecorder which will help us to get decibels.

Here’s how to initialize the AVAudioRecorder and AVAudioSession

func initRecorder() {
    let settings = [
        AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
        AVSampleRateKey: 12000,
        AVNumberOfChannelsKey: 1,
        AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
    ]
    do {
        if IS_DEBUG { print("init AVAudioRecorder...") }
        let session = AVAudioSession.sharedInstance()
        try session.setCategory(AVAudioSessionCategoryPlayAndRecord)
        try session.overrideOutputAudioPort(AVAudioSessionPortOverride.speaker)
        try session.setActive(true)
 
        try recorder = AVAudioRecorder(url: getDocumentsDirectory(), settings: settings)
        recorder!.delegate = self
        recorder!.isMeteringEnabled = true
        if !recorder!.prepareToRecord() {
            print("Error: AVAudioRecorder prepareToRecord failed")
        }
    } catch {
        print("Error: AVAudioRecorder creation failed")
    }
}

The most vital parts here are: set up AVAudioSession, init AVAudioRecorder with settings, reference recorder to our service class via delegate and run prepareToRecord. This initRecorder method should be called from “override func didMove(to view: SKView) {” in case is you use SKScene.
Now we are ready to call

func start() {
    recorder?.record()
    recorder?.updateMeters()
}

And this is it, the “tracker” is up and running. On every let say “override func update(_ currentTime: TimeInterval) {” we can get the decibel value with the following method

func update() {
    if let recorder = recorder {
        recorder.updateMeters()
    }
}
 
func getDispersyPercent() -> Float {
    if let recorder = recorder {
        let decibels = recorder.averagePower(forChannel: 0)
        //...
    }
    return 0
}

So, basically the answer is how to get decibels via AVAudioRecorder is to call recorder.averagePower(forChannel: 0) method. How to understand the value you will get?
this method will return Float value from -120 (the absolute quite) up to a 0 – hella loud.
In some cafe you will have something like -20 or even -16. So you can play with this values to experiment and find what you need. In my game I also have calibration – the first second I measure the background noice level – append all values into the array and try to calculate median and disperse to detect the “average” maximum of the background noice level to set up threshold correctly, so the game won’t react on a noice level.
Ping me in info (doggy) .com if any questions.
And feel free to enjoy
the result of these investigations .

This is it.

Social Share Toolbar