Search code examples
iosiphoneswiftxcodeavaudiorecorder

iOS : Create a simple audio waveform animation


I am trying to record audio with AVAudioRecorder. I need to create a simple waveform like this :

enter image description here

When the user speaks into the microphone a circle indicates the level of the user's voice. I tried to measure the voice with this code but the code did not detect any buffer and not work :

func levelTimerCallback(timer:Timer) {

    //we have to update meters before we can get the metering values
    audioRecorder.updateMeters()

    //print to the console if we are beyond a threshold value. Here I've used -7
    if audioRecorder.averagePower(forChannel: 1) > -7 {

        print(" level I'm hearin' you in dat mic ")
        print(audioRecorder.averagePower(forChannel: 0))
    }
}    

Recording audio :

func startRecording() {

        let audioSession = AVAudioSession.sharedInstance()
        do {

       // Audio Settings
            settings = [
                AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
                AVSampleRateKey: 12000,
                AVNumberOfChannelsKey: 1,
                AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
            ]


            audioRecorder = try AVAudioRecorder(url: self.directoryURL(), settings: settings)
            audioRecorder.delegate = self
            audioRecorder.prepareToRecord()
            audioRecorder.isMeteringEnabled = true


        } catch {

            finishRecording(success: false)
        }

        do {

            try audioSession.setActive(true)
            audioRecorder.record()

            TIMERRRRR = Timer.scheduledTimer(timeInterval: 0.02, target: self, selector: #selector(ViewController.levelTimerCallback), userInfo: nil, repeats: true)

        } catch {

        }

    }

Solution

  • Two problems that I can see.

    The first is that you are testing with the wrong channel number in your function levelTimerCallback. Your test should be for channel 0.

    The other is that the value of -7 is actually very loud. averagePower returns floats between -160 and 0. Try testing against -160 to start with and increasing that to reduce the sensitivity.

    This changes your test to:

    if audioRecorder.averagePower(forChannel: 1) > -160 {
        ...
    }
    

    There is only a single channel for the input and the count starts at 0. You have this right later on in your call to audioRecorder.averagePower.

    To get a linear scale, you'll need to interpolate. Change the value of scale to change the range. proportion will be between 0 and scale.

    let lowerLimit: Float = -100.0
    
    func levelTimerCallback(timer:Timer) {
    
        audioRecorder.updateMeters()
    
        let power = audioRecorder.averagePower(forChannel: 0)
        if power > lowerLimit {
            // proportion will have a value between 0 and scale
            let scale: Float = 10.0
            let proportion = -scale * (power - lowerLimit) / lowerLimit
        }
    }
    

    Edit: fixed the proportion calculation