如何录制音频并将其保存到沙盒?

Posted

技术标签:

【中文标题】如何录制音频并将其保存到沙盒?【英文标题】:How do I record audio and save it to Sandbox? 【发布时间】:2019-08-23 10:45:37 【问题描述】:

我正在开发记录用户音频的应用程序,并在表格视图的另一个屏幕上显示从该应用程序制作的所有早期录音。单击特定行时,必须播放录音。我该如何做到这一点,是否有任何资源可以帮助我解决这个问题?

我的代码当前保存录音并在同一屏幕上播放。但是,新的录音会覆盖以前的录音,并且只有一个录音会保存到文件管理器中。 我已将“隐私 - 需要使用麦克风”添加到 plist。 音频已成功录制和播放。

import UIKit
import AVFoundation
import MobileCoreServices



class ViewController: UIViewController, AVAudioRecorderDelegate, AVAudioPlayerDelegate

@IBOutlet var recordingTimeLabel: UILabel!
@IBOutlet var record_btn_ref: UIButton!
@IBOutlet var play_btn_ref: UIButton!

//Variables:
var audioRecorder: AVAudioRecorder!
var audioPlayer : AVAudioPlayer!
var meterTimer:Timer!
var isAudioRecordingGranted: Bool!
var isRecording = false
var isPlaying = false


override func viewDidLoad() 
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    //Check recording permission:

    check_record_permission()

    //Add right bar button:

    navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Documents", style: .plain, target: self, action: #selector(OpenDoc))


//Button action to start recording:
@IBAction func start_recording(_ sender: UIButton)

    //If already recording:
    if(isRecording)
    
        //Stop recording:
        finishAudioRecording(success: true)
        //Set the title back to "Record":
        record_btn_ref.setTitle("Record", for: .normal)
        //Enable the play button:
        play_btn_ref.isEnabled = true
        //Set the value of the variable "isRecording" to false
        isRecording = false
    
        //If audio was not being recorded:
    else
    
        //Setup the recorder:
        setup_recorder()
        //Start recording:
        audioRecorder.record()
        //Update label every 1 sec:
        meterTimer = Timer.scheduledTimer(timeInterval: 0.1, target:self, selector:#selector(self.updateAudioMeter(timer:)), userInfo:nil, repeats:true)
        //Set the title of the label to "Stop":
        record_btn_ref.setTitle("Stop", for: .normal)
        //Enable the play button:
        play_btn_ref.isEnabled = false
        //Set "isRecording" to true:
        isRecording = true
    



//Button action for play/pause:
@IBAction func play_recording(_ sender: Any)

    //If audio is already being played (i.e. it should pause on being clicked again):
    if(isPlaying)
    
        //Stop audio player
        audioPlayer.stop()
        //Enable record button:
        record_btn_ref.isEnabled = true
        //Set the title to "Play"
        play_btn_ref.setTitle("Play", for: .normal)
        //Set value of "isPlaying" to false:
        isPlaying = false
    
        //It is not playing (i.e. it should play when button is clicked)
    else
    
        //If file path exists:
        if FileManager.default.fileExists(atPath: getFileUrl().path)
        
            //Disable the record button:
            record_btn_ref.isEnabled = false
            //Set the title of the button to "Pause":
            play_btn_ref.setTitle("Pause", for: .normal)
            //Prepare to play:
            prepare_play()
            //Implement play method of audioPlayer:
            audioPlayer.play()
            //Set variable "isPlaying" to true:
            isPlaying = true
        
            //If file path doesn't exist:
        else
        
            display_alert(msg_title: "Error", msg_desc: "Audio file is missing.", action_title: "OK")
        
    



//Function that checks permission to record:

func check_record_permission()

    //Switch record permission instances:

    switch AVAudiosession.sharedInstance().recordPermission 
    //Case granted:
    case AVAudioSessionRecordPermission.granted:
        isAudioRecordingGranted = true
        break
    //Case denied:
    case AVAudioSessionRecordPermission.denied:
        isAudioRecordingGranted = false
        break
    //Case not determined, in which case ask for permission:
    case AVAudioSessionRecordPermission.undetermined:
        AVAudioSession.sharedInstance().requestRecordPermission( (allowed) in
            if allowed 
                self.isAudioRecordingGranted = true
             else 
                self.isAudioRecordingGranted = false
            
        )
        break
    //Default case:
    default:
        break
    



//Function that gets the directory path:
func getDocumentsDirectory() -> URL

    let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
    let documentsDirectory = paths[0]
    return documentsDirectory


//Function that gets the URL file path:
func getFileUrl() -> URL

    let filename = "myRecording.m4a"
    let filePath = getDocumentsDirectory().appendingPathComponent(filename)

    return filePath


//Function that sets up the recorder:
func setup_recorder()

    if isAudioRecordingGranted
    
        let session = AVAudioSession.sharedInstance()
        do
        
            try session.setCategory(AVAudioSession.Category.playAndRecord, options: .defaultToSpeaker)
            try session.setActive(true)
            let settings = [
                AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
                AVSampleRateKey: 44100,
                AVNumberOfChannelsKey: 2,
                AVEncoderAudioQualityKey:AVAudioQuality.high.rawValue
            ]
            audioRecorder = try AVAudioRecorder(url: getFileUrl(), settings: settings)
            audioRecorder.delegate = self
            audioRecorder.isMeteringEnabled = true
            audioRecorder.prepareToRecord()
        
        catch let error 
            display_alert(msg_title: "Error", msg_desc: error.localizedDescription, action_title: "OK")
        
    
    else
    
        display_alert(msg_title: "Error", msg_desc: "Don't have access to use your microphone.", action_title: "OK")
    




//Objective C function to update text of the timer label:
@objc func updateAudioMeter(timer: Timer)

    if audioRecorder.isRecording
    
        let hr = Int((audioRecorder.currentTime / 60) / 60)
        let min = Int(audioRecorder.currentTime / 60)
        let sec = Int(audioRecorder.currentTime.truncatingRemainder(dividingBy: 60))
        let totalTimeString = String(format: "%02d:%02d:%02d", hr, min, sec)
        recordingTimeLabel.text = totalTimeString
        audioRecorder.updateMeters()
    


//Function for finish audio recording:
func finishAudioRecording(success: Bool)

    //If recording was successful:
    if success
    
        audioRecorder.stop()
        audioRecorder = nil
        meterTimer.invalidate()
        print("recorded successfully.")


    
        //If recording was not successful:
    else
    
        display_alert(msg_title: "Error", msg_desc: "Recording failed.", action_title: "OK")
    



//Prepare to play:
func prepare_play()

    do
    
        audioPlayer = try AVAudioPlayer(contentsOf: getFileUrl())
        audioPlayer.delegate = self
        audioPlayer.prepareToPlay()
    
    catch
        print("Error")
    





//Function for audio record did finish recording:

func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool)

    if !flag
    
        finishAudioRecording(success: false)
    
    play_btn_ref.isEnabled = true





//If recorded audio was played:

func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool)

    record_btn_ref.isEnabled = true
    play_btn_ref.setTitle("Play", for: .normal)


//Function to display alerts:

func display_alert(msg_title : String , msg_desc : String ,action_title : String)

    let ac = UIAlertController(title: msg_title, message: msg_desc, preferredStyle: .alert)
    ac.addAction(UIAlertAction(title: action_title, style: .default)
    
        (result : UIAlertAction) -> Void in
        _ = self.navigationController?.popViewController(animated: true)
    )
    present(ac, animated: true)





@objc func OpenDoc()

    let documentPicker = UIDocumentPickerViewController(documentTypes: [kUTTypeMPEG4Audio as String], in: .import)
    documentPicker.delegate = self as? UIDocumentPickerDelegate
    documentPicker.allowsMultipleSelection = false
    present(documentPicker, animated: true, completion: nil)



【问题讨论】:

【参考方案1】:

我的代码当前保存录音并在同一屏幕上播放。但是,新的录音会覆盖之前的录音,并且只有一个录音会保存到文件管理器中。

据我了解,您在此函数func getFileUrl() 中为录音提供了相同的名称。 让每条记录具有不同名称的最简单方法是为其添加时间戳,例如使用Date()timeIntervalSince1970

func getFileUrl() -> URL

    let currentTime = Date().timeIntervalSince1970
    let filename = "myRecording-\(currentTime).m4a"
    let filePath = getDocumentsDirectory().appendingPathComponent(filename)

    return filePath

接下来,您的 play_recording 函数应该从另一个屏幕上的 TableView 中获取录音的 URL 作为参数。

【讨论】:

谢谢,该代码很有帮助,但我的代码仍有一些错误,我已更正。再次感谢您的帮助【参考方案2】:

经过几篇文章和这里的答案:https://***.com/a/57629598/11830020,我想出了答案,如下:

故事板包含两个按钮,(一个用于录制/停止,另一个用于播放/暂停)和一个用于显示录制声音长度的标签。

import UIKit
import AVFoundation
import MobileCoreServices

class ViewController: UIViewController,AVAudioRecorderDelegate,AVAudioPlayerDelegate 

//Variables:
var audioRecorder: AVAudioRecorder!
var player: AVAudioPlayer!
var meterTimer:Timer!
var isAudioRecordingGranted: Bool!
var isRecording = false
var isPlaying = false
var totalTimeString = ""


//IBOutlets:
@IBOutlet var recordingTimeLabel: UILabel!
@IBOutlet var record_btn_ref: UIButton!
@IBOutlet weak var playBtn: UIButton!


//View Did Load:

override func viewDidLoad() 
    super.viewDidLoad()

    //Check for recording permission:

    check_record_permission()



//Button action to start recording:

@IBAction func start_recording(_ sender: UIButton)

    //If already recording:
    if(isRecording)
    
        //Stop recording:
        finishAudioRecording(success: true)
        //Set the title back to "Record":
        record_btn_ref.setTitle("Record", for: .normal)
        //Enable the play button:
        playBtn.isEnabled = true
        //Set the value of the variable "isRecording" to false
        isRecording = false

    
        //If audio was not being recorded:
    else
    
        //Setup the recorder:
        setup_recorder()
        //Start recording:
        audioRecorder.record()
        //Update label every 1 sec:
        meterTimer = Timer.scheduledTimer(timeInterval: 0.1, target:self, selector:#selector(self.updateAudioMeter(timer:)), userInfo:nil, repeats:true)
        //Set the title of the label to "Stop":
        record_btn_ref.setTitle("Stop", for: .normal)
        //Disable play:
        playBtn.isEnabled = false
        //Set "isRecording" to true:
        isRecording = true
    


//Play/pause button action

@IBAction func playBtnAction(_ sender: Any) 
    //playSound()

    //If audio is already being played (i.e. it should pause on being clicked again):
    if(isPlaying)
    
        //Stop audio player
        player.stop()
        //Enable record button:
        record_btn_ref.isEnabled = true
        //Set the title to "Play"
        playBtn.setTitle("Play", for: .normal)
        //Set value of "isPlaying" to false:
        isPlaying = false
    
        //It is not playing (i.e. it should play when button is clicked)
    else
    
        let filename = "myRecording\(totalTimeString).m4a"
        let url = getDocumentsDirectory().appendingPathComponent(filename)

        //If file path exists:
        if FileManager.default.fileExists(atPath: url.path)
        
            //Disable the record button:
            record_btn_ref.isEnabled = false
            //Set the title of the button to "Pause":
            playBtn.setTitle("Pause", for: .normal)
            //Prepare to play:
            prepare_play()
            //Implement play method of audioPlayer:
            player.play()
            //Set variable "isPlaying" to true:
            isPlaying = true
        
            //If file path doesn't exist:
        else
        
            display_alert(msg_title: "Error", msg_desc: "Audio file is missing.", action_title: "OK")
        
    




//Recording permissions:


//Function that checks for permission to record:

func check_record_permission()

    //Switch record permission instances:

    switch AVAudioSession.sharedInstance().recordPermission 
    //Case granted:
    case AVAudioSessionRecordPermission.granted:
        isAudioRecordingGranted = true
        break
    //Case denied:
    case AVAudioSessionRecordPermission.denied:
        isAudioRecordingGranted = false
        break
    //Case not determined, in which case ask for permission:
    case AVAudioSessionRecordPermission.undetermined:
        AVAudioSession.sharedInstance().requestRecordPermission( (allowed) in
            if allowed 
                self.isAudioRecordingGranted = true
             else 
                self.isAudioRecordingGranted = false
            
        )
        break
    //Default case:
    default:
        break
    



//Function that gets the directory path:
func getDocumentsDirectory() -> URL

    let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
    let documentsDirectory = paths[0]
    return documentsDirectory


//Function that gets the URL file path:
func getFileUrl() -> URL

    let date = Date()
    let calendar = Calendar.current
    let hr = calendar.component(.hour, from: date)
    let min = calendar.component(.minute, from: date)
    let sec = calendar.component(.second, from: date)
    totalTimeString = String(format: "%02d.%02d.%02d", hr, min, sec)
    let filename = "myRecording\(totalTimeString).m4a"
    let filePath = getDocumentsDirectory().appendingPathComponent(filename)

    return filePath





//Audio recorder functions:


//Function that sets up the recorder:

func setup_recorder()

    //If access to record:
    if isAudioRecordingGranted
    
        let session = AVAudioSession.sharedInstance()
        do
        
            try session.setCategory(AVAudioSession.Category.playAndRecord, options: .defaultToSpeaker)
            try session.setActive(true)
            let settings = [
                AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
                AVSampleRateKey: 44100,
                AVNumberOfChannelsKey: 2,
                AVEncoderAudioQualityKey:AVAudioQuality.high.rawValue
            ]
            audioRecorder = try AVAudioRecorder(url: getFileUrl(), settings: settings)
            audioRecorder.delegate = self
            audioRecorder.isMeteringEnabled = true
            audioRecorder.prepareToRecord()
        
        catch let error 
            display_alert(msg_title: "Error", msg_desc: error.localizedDescription, action_title: "OK")
        
    
        //If permission not granted:
    else
    
        display_alert(msg_title: "Error", msg_desc: "Don't have access to use your microphone.", action_title: "OK")
    


//Function that defines what to do when audio recording is finished successfully/unsuccessfully:

func finishAudioRecording(success: Bool)

    //If recording was successful:
    if success
    
        //Stop recording
        audioRecorder.stop()
        //Reset recorder
        audioRecorder = nil
        //Invalidate meter timer:
        meterTimer.invalidate()
    
        //If recording was not successful:
    else
    
        //Call function to display alert:
        display_alert(msg_title: "Error", msg_desc: "Recording failed.", action_title: "OK")
    


//Function for audio record did finish recording:

func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool)

    if !flag
    
        //Audio recording was not successful:
        finishAudioRecording(success: false)
    

    //Enable play button
    playBtn.isEnabled = true






//Play/pause recorded audio:


//Prepare to play:

func prepare_play()

    do
    
        let filename = "myRecording\(totalTimeString).m4a"
        let url = getDocumentsDirectory().appendingPathComponent(filename)

        player = try AVAudioPlayer(contentsOf: url)
        player.delegate = self
        player.prepareToPlay()
    
    catch
        print("Error")
    


//If recorded audio was played:

func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool)

    //Enable record button:
    record_btn_ref.isEnabled = true
    //Set title of play button to Play:
    playBtn.setTitle("Play", for: .normal)




//Alerts:

//Function to display alerts:

func display_alert(msg_title : String , msg_desc : String ,action_title : String)

    let ac = UIAlertController(title: msg_title, message: msg_desc, preferredStyle: .alert)
    ac.addAction(UIAlertAction(title: action_title, style: .default)
    
        (result : UIAlertAction) -> Void in
        _ = self.navigationController?.popViewController(animated: true)
    )
    present(ac, animated: true)





//Timer label:

//Objective C function to update text of the timer label:
@objc func updateAudioMeter(timer: Timer)

    if audioRecorder.isRecording
    
        let hr = Int((audioRecorder.currentTime / 60) / 60)
        let min = Int(audioRecorder.currentTime / 60)
        let sec = Int(audioRecorder.currentTime.truncatingRemainder(dividingBy: 60))
        let totalTimeString = String(format: "%02d:%02d:%02d", hr, min, sec)
        recordingTimeLabel.text = totalTimeString
        audioRecorder.updateMeters()
    




【讨论】:

以上是关于如何录制音频并将其保存到沙盒?的主要内容,如果未能解决你的问题,请参考以下文章

如何保存录制的音频iOS?

swift保存视频到沙盒

iOS writeToFile 保存文件到沙盒某目录失败的解决方案

如何在 WASAPICaptureSharedEventDriven 中保存原始格式的音频文件

通过 openAL 将录制的音频保存为 .wav

保存录制的音频 blob [JS]