Search code examples
iosiphoneswiftaudio-recording

iOS - Detect Blow into Mic and convert the results! (swift)


I need to develop an iOS App in swift which detects a blow in the microphone from a user. This has to be a Challenge-Game where two players have to blow into the iPhone mic one after the other. The decibel values should be measured and converted in meter or kilometer so I can determine a winner. The player which "blows further" (player1: 50km, player2: 70km) wins.

Is this a possible implementation?

I have this code in swift and I don't know how to proceed:

import Foundation
import UIKit
import AVFoundation
import CoreAudio

class ViewController: UIViewController {
// @IBOutlet weak var mainImage: UIImageView!

var recorder: AVAudioRecorder!
var levelTimer = NSTimer()
var lowPassResults: Double = 0.0
override func viewDidLoad() {
    super.viewDidLoad()
    let url = NSURL.fileURLWithPath("dev/null")
    //numbers are automatically wrapped into NSNumber objects, so I simplified that to [NSString : NSNumber]
    var settings : [NSString : NSNumber] = [AVSampleRateKey: 44100.0, AVFormatIDKey: kAudioFormatAppleLossless, AVNumberOfChannelsKey: 1, AVEncoderAudioQualityKey: AVAudioQuality.Max.rawValue]
    var error: NSError?
   // mainImage?.image = UIImage(named: "flyForReal.png");
    recorder = AVAudioRecorder(URL:url, settings:settings, error:&error)

    if((recorder) != nil){
        recorder.prepareToRecord()
        recorder.meteringEnabled = true
        recorder.record()
        levelTimer = NSTimer.scheduledTimerWithTimeInterval(0.05, target: self, selector: Selector("levelTimerCallback"), userInfo: nil, repeats: true)
    }
    else{
        NSLog("%@", "Error");
    }
}
func levelTimerCallback(timer:NSTimer) {
    recorder.updateMeters()

    let ALPHA: Double = 0.05
    var peakPowerForChannel = pow(Double(10), (0.05 * Double(recorder.peakPowerForChannel(0))))
    lowPassResults = ALPHA * peakPowerForChannel + (1.0 - ALPHA) * lowPassResults;
    if(lowPassResults > 0.95){
        NSLog("@Mic blow detected");
    }
    NSLog("@Average input: %f Peak input: %f Low pass results: %f", recorder.averagePowerForChannel(0), recorder.peakPowerForChannel(0), lowPassResults);
 }
}

Thanks ahead!


Solution

  • Close. You have a couple of issues. Your selector call crashes the app because you're not passing an argument and levelTimerCallback() expects one.

    averagePowerPerChannel seems to give me a more real-time metering so I used that instead of peakPowerPerChannel

    Also, you need to set up an audio session. I wasn't really sure what all that math was about, so I just got rid of it here. I've pasted the entire view controller below for basic mic detection.

    import Foundation
    import UIKit
    import AVFoundation
    import CoreAudio
    
    class ViewController: UIViewController {
    
    var recorder: AVAudioRecorder!
    var levelTimer = NSTimer()
    var lowPassResults: Double = 0.0
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        //make an AudioSession, set it to PlayAndRecord and make it active
        var audioSession:AVAudioSession = AVAudioSession.sharedInstance()
        audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord, error: nil)
        audioSession.setActive(true, error: nil)
    
        //set up the URL for the audio file
        var documents: AnyObject = NSSearchPathForDirectoriesInDomains( NSSearchPathDirectory.DocumentDirectory,  NSSearchPathDomainMask.UserDomainMask, true)[0]
        var str =  documents.stringByAppendingPathComponent("recordTest.caf")
        var url = NSURL.fileURLWithPath(str as String)
    
        // make a dictionary to hold the recording settings so we can instantiate our AVAudioRecorder
        var recordSettings: [NSObject : AnyObject] = [AVFormatIDKey:kAudioFormatAppleIMA4,
            AVSampleRateKey:44100.0,
            AVNumberOfChannelsKey:2,AVEncoderBitRateKey:12800,
            AVLinearPCMBitDepthKey:16,
            AVEncoderAudioQualityKey:AVAudioQuality.Max.rawValue
    
        ]
    
        //declare a variable to store the returned error if we have a problem instantiating our AVAudioRecorder
        var error: NSError?
    
        //Instantiate an AVAudioRecorder
        recorder = AVAudioRecorder(URL:url, settings: recordSettings, error: &error)
        //If there's an error, print that shit - otherwise, run prepareToRecord and meteringEnabled to turn on metering (must be run in that order)
        if let e = error {
            println(e.localizedDescription)
        } else {
            recorder.prepareToRecord()
            recorder.meteringEnabled = true
    
            //start recording
            recorder.record()
    
            //instantiate a timer to be called with whatever frequency we want to grab metering values
            self.levelTimer = NSTimer.scheduledTimerWithTimeInterval(0.02, target: self, selector: Selector("levelTimerCallback"), userInfo: nil, repeats: true)
    
        }
    
    }
    
    //This selector/function is called every time our timer (levelTime) fires
    func levelTimerCallback() {
        //we have to update meters before we can get the metering values
        recorder.updateMeters()
    
        //print to the console if we are beyond a threshold value. Here I've used -7
        if recorder.averagePowerForChannel(0) > -7 {
            print("Dis be da level I'm hearin' you in dat mic ")
            println(recorder.averagePowerForChannel(0))
            println("Do the thing I want, mofo")
        }
    }
    
    
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    
    }