Search code examples
iosswiftavaudiorecorder

Find silence in AVAudioRecorder Session


How can I stop the audio recording once the user stops taking? Like Siri. Once you say, Hi Siri it will respond to your voice. Means Siri app listening to the audio until you stop the taking.

I'm trying to do the same thing. If I say, Get weather details once I stop my voice. I want to trigger one method or call the API with recorded audio till is stop.

My requirement is app should continuously listen to the user find the voice end event send data to the server or just trigger a method.

Code:

import UIKit
import CoreAudio
import CoreAudioKit
import AVFoundation
import Foundation
import AVKit



class ViewController: UIViewController, AVAudioRecorderDelegate {

    private var recorder    : AVAudioRecorder? = nil
    private var isRecording : Bool = false
    private var timer       : Timer? = nil

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        permissionWasGranted { (isValied) in
            print("isValied")
            self.isRecording = false;
            self.intiateTimer()

        }
    }

    @objc func intiateTimer() {
        self.timer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(self.updateTimer), userInfo: nil, repeats: true)

    }
    @objc func updateTimer() {

        if !isRecording {
            //recorder = nil
            self.initRecorder()
            print("Recording intiated")
        }
        else {
            print("Recording Started")
        }
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    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()
        isRecording = false
        self.updateTimer()
    }


    func permissionWasGranted(result: @escaping (_: Bool)->()) {
        switch AVAudioSession.sharedInstance().recordPermission() {
        case AVAudioSessionRecordPermission.granted:
            //if IS_DEBUG { print("Permission granted") }
            print("Permission granted")

            result(true)
            return
        case AVAudioSessionRecordPermission.denied:
            //if IS_DEBUG { print("Pemission denied") }
                print("Pemission denied")
        case AVAudioSessionRecordPermission.undetermined:
            //if IS_DEBUG { print("Request permission here") }
            print("Request permission here")

            AVAudioSession.sharedInstance().requestRecordPermission({ (granted) in
                if granted {
                    result(true)
                    return
                }
            })

        }
        result(false)
    }

    func initRecorder() {
        let settings = [
            AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
            AVSampleRateKey: 12000,
            AVNumberOfChannelsKey: 1,
            AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
        ]
        do {
            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")
            }


            let decibels = self.getDispersyPercent()
            if decibels > -120  && decibels < -20 {
                self.timer?.invalidate()
                isRecording = true;
                self.start()
            }


        } catch {
            print("Error: AVAudioRecorder creation failed")
        }
    }

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

    func update() {
        if let recorder = recorder {
            recorder.updateMeters()
        }
    }

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

}

Solution

  • here I Have created my function that will actually detect silence for 5 seconds and if condition is satisfied you can stop recording that time

    -- I had used Recording Manager NSObject class so you can get the code from below function and manage to use it in yours

    Code

    //StartNewRecordingIfSilenceFor5Second
        func newSessionIfSilence(){
    
            //get Audio file name to store
            let AudioFileName = getDocumentsDirectory().appendingPathComponent("\(getUniqueName()).wav")
            //Declare a value that will be updated when silence is detected
            var statusForDetection = Float()
            //Recorder Settings used
            let settings: [String: Any] = [
                AVFormatIDKey: Int(kAudioFormatLinearPCM),
                AVSampleRateKey: 16000,
                AVNumberOfChannelsKey: 1,
                AVLinearPCMBitDepthKey: 16,
                AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue,
                AVLinearPCMIsBigEndianKey: false,
                AVLinearPCMIsFloatKey: false,
                ]
            //Try block
            do {
                //Start Recording With Audio File name
                Manager.recorder = try AVAudioRecorder(url: AudioFileName, settings: settings)
                Manager.recorder?.delegate = self
                Manager.recorder?.isMeteringEnabled = true
                Manager.recorder?.prepareToRecord()
                Manager.recorder?.record()
    
                //Tracking Metering values here only
                Manager.meterTimer = Timer.scheduledTimer(withTimeInterval: 0.10, repeats: true, block: { (timer: Timer) in
    
                    //Update Recording Meter Values so we can track voice loudness
                    //Getting Recorder from another class
                    //i managed my recorder from Manager class
                    if let recorder = Manager.recorder
                    {
                        //Start Metering Updates
                        recorder.updateMeters()
    
                        //Get peak values
                        Manager.recorderApc0 = recorder.averagePower(forChannel: 0)
                        Manager.recorderPeak0 = recorder.peakPower(forChannel: 0)
    
                        //it’s converted to a 0-1 scale, where zero is complete quiet and one is full volume.
                        let ALPHA: Double = 0.05
                        let peakPowerForChannel = pow(Double(10), (0.05 * Double(Manager.recorderPeak0)))
    
    //                    static var lowPassResults: Double = 0.0
                        RecordingManager.lowPassResults = ALPHA * peakPowerForChannel + (1.0 - ALPHA) * RecordingManager.lowPassResults
    
                        if(RecordingManager.lowPassResults > 0){
                            print("Mic blow detected")
                            //Do what you wanted to do here
    
                            //if blow is detected update silence value as zero
                            statusForDetection = 0.0
                        }
                        else
                        {
    
                            //Update Value for Status is blow being detected or not
                            //As timer is called at interval of 0.10 i.e 0.1 So add value each time in silence Value with 0.1
                            statusForDetection += 0.1
    
    
                            //if blow is not Detected for 5 seconds
                            if statusForDetection > 5.0 {
                                //Update value to zero
                                //When value of silence is greater than 5 Seconds
                                //Time to Stop recording
                                statusForDetection = 0.0
    
                                //Stop Audio recording
                                recorder.stop()
    
                            }
                        }
                    }
                })
    
            } catch {
                //Finish Recording with a Error
                print("Error Handling: \(error.localizedDescription)")
                self.finishRecording(success: false)
            }
    
        }