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

How do I record audio and save it to Sandbox?

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

我的代码目前保存录音并在同一屏幕上播放。但是,新的录音会覆盖之前的录音,并且只有一个录音会保存到文件管理器中。 我已将 "Privacy - Microphone usage required" 添加到 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)
}

}

My code currently saves the recording and plays it on the same screen. However, new recordings overwrite earlier ones and only one recording is saved into the file manager.

据我了解,您在此函数中为此处的录音指定了相同的名称 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 记录作为参数。

看了几篇文章和这里的答案:,我找到了答案,如下:

故事板包含两个按钮(一个用于 record/stop,另一个用于 play/pause)和一个用于显示录制声音长度的标签。

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()
    }
}


}