Record Audio and Save Permanently in iOS

Record audio and save permanently in iOS

Ok I finally solved it. The problem was that I was setting up the AVAudioRecorder and file the path in the viewLoad of my ViewController.m overwriting existing files with the same name.

  1. After recording and saving the audio to file and stopping the app, I could find the file in Finder. (/Users/xxxxx/Library/Application Support/iPhone Simulator/6.0/Applications/0F107E80-27E3-4F7C-AB07-9465B575EDAB/Documents/sound1.caf)
  2. When I restarted the application the setup code for the recorder (from viewLoad) would just overwrite my old file called:

    sound1.caf

  3. with a new one. Same name but no content.

  4. The play back would just play an empty new file. --> No Sound obviously.


So here is what I did:

I used NSUserdefaults to save the path of the recorded file name to be retrieved later in my playBack method.


cleaned viewLoad in ViewController.m :

- (void)viewDidLoad
{

AVAudioSession *audioSession = [AVAudioSession sharedInstance];

[audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];

[audioSession setActive:YES error:nil];

[recorder setDelegate:self];

[super viewDidLoad];
}

edited record in ViewController.m :

- (IBAction) record
{

NSError *error;

// Recording settings
NSMutableDictionary *settings = [NSMutableDictionary dictionary];

[settings setValue: [NSNumber numberWithInt:kAudioFormatLinearPCM] forKey:AVFormatIDKey];
[settings setValue: [NSNumber numberWithFloat:8000.0] forKey:AVSampleRateKey];
[settings setValue: [NSNumber numberWithInt: 1] forKey:AVNumberOfChannelsKey];
[settings setValue: [NSNumber numberWithInt:16] forKey:AVLinearPCMBitDepthKey];
[settings setValue: [NSNumber numberWithBool:NO] forKey:AVLinearPCMIsBigEndianKey];
[settings setValue: [NSNumber numberWithBool:NO] forKey:AVLinearPCMIsFloatKey];
[settings setValue: [NSNumber numberWithInt: AVAudioQualityMax] forKey:AVEncoderAudioQualityKey];

NSArray *searchPaths =NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentPath_ = [searchPaths objectAtIndex: 0];

NSString *pathToSave = [documentPath_ stringByAppendingPathComponent:[self dateString]];

// File URL
NSURL *url = [NSURL fileURLWithPath:pathToSave];//FILEPATH];


//Save recording path to preferences
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];


[prefs setURL:url forKey:@"Test1"];
[prefs synchronize];


// Create recorder
recorder = [[AVAudioRecorder alloc] initWithURL:url settings:settings error:&error];

[recorder prepareToRecord];

[recorder record];
}

edited playback in ViewController.m:

-(IBAction)playBack
{

AVAudioSession *audioSession = [AVAudioSession sharedInstance];

[audioSession setCategory:AVAudioSessionCategoryPlayback error:nil];

[audioSession setActive:YES error:nil];


//Load recording path from preferences
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];


temporaryRecFile = [prefs URLForKey:@"Test1"];



player = [[AVAudioPlayer alloc] initWithContentsOfURL:temporaryRecFile error:nil];



player.delegate = self;


[player setNumberOfLoops:0];
player.volume = 1;


[player prepareToPlay];

[player play];


}

and added a new dateString method to ViewController.m:

- (NSString *) dateString
{
// return a formatted string for a file name
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"ddMMMYY_hhmmssa";
return [[formatter stringFromDate:[NSDate date]] stringByAppendingString:@".aif"];
}

Now it can load the last recorded file via NSUserdefaults loading it with:

    //Load recording path from preferences
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];


temporaryRecFile = [prefs URLForKey:@"Test1"];



player = [[AVAudioPlayer alloc] initWithContentsOfURL:temporaryRecFile error:nil];

in (IBAction)playBack. temporaryRecFile is a NSURL variable in my ViewController class.

declared as following ViewController.h :

@interface SoundRecViewController : UIViewController <AVAudioSessionDelegate,AVAudioRecorderDelegate, AVAudioPlayerDelegate>
{
......
......
NSURL *temporaryRecFile;

AVAudioRecorder *recorder;
AVAudioPlayer *player;

}
......
......
@end

xcode: How to save audio file after recording audio using AVFoundation

You need to stop recording in order to save the data to the file permanently:

[recorder stop];

AVAudioRecorder not saving recording

Ok so thanks for @Neeku so much for answering my question, certainly something to take into account but its still not solving the problem. I was searching around and I found this example which works perfectly and more to the point shows me that I was approaching this entire functionality the wrong way. I think another one of my problems is that my app would delete the previously saved audio file in the ViewDidload method. And as well as that I don't think I have used the AVAudioSession instances correctly.

Anyway the example I found is very useful and solves my problem perfectly, you can find it here: Record audio and save permanently in iOS

Hope that helps anyone else who is having a similar problem to me.

Saving a recorded AVAudioRecorder sound file: Now what? ( iOS, Xcode 4 )

Voice Record Demo is a great example to take a look at. It uses Cocos2D for the UI but if you just take a look at the HelloWorldScene.m class, it contains all the code you need to:

  1. Create a new audio session
  2. Check if the mic is connected
  3. Start recording
  4. Stop recording
  5. Save the recorded audio file
  6. Play the saved voice file

After you init your audio session, you could use a method like the one below to save an a recording to a given filename:

-(void) saveAudioFileNamed:(NSString *)filename {

destinationString = [[self documentsPath] stringByAppendingPathComponent:filename];
NSLog(@"%@", destinationString);
NSURL *destinationURL = [NSURL fileURLWithPath: destinationString];

NSDictionary *settings = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithFloat: 44100.0], AVSampleRateKey,
[NSNumber numberWithInt: kAudioFormatAppleLossless], AVFormatIDKey,
[NSNumber numberWithInt: 1], AVNumberOfChannelsKey,
[NSNumber numberWithInt: AVAudioQualityMax], AVEncoderAudioQualityKey,
nil];

NSError *error;

recorder = [[AVAudioRecorder alloc] initWithURL:destinationURL settings:settings error:&error];
recorder.delegate = self;
}

How do I record audio and save it to Sandbox?

After going through a few articles and the answer here: https://stackoverflow.com/a/57629598/11830020, I have figured out the answer and it is as below:

The storyboard contains two buttons,(one for record/stop and the other for play/pause) and one label to display the length of the recorded sound.

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


}


Related Topics



Leave a reply



Submit