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.
- 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)
When I restarted the application the setup code for the recorder (from viewLoad) would just overwrite my old file called:
sound1.caf
with a new one. Same name but no content.
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:
- Create a new audio session
- Check if the mic is connected
- Start recording
- Stop recording
- Save the recorded audio file
- 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
Firebase Cloud Messaging Doesn't Create Push Notifications But Gets Information
Record Audio and Save Permanently in iOS
Spritekit - Not Loading @3X Images from Sktextureatlas
App Icons Not Included in Build from Xcode
Error: Invalid Bitcode Version (Producer: '800.0.35.0_0' Reader: '703.0.31_0')
Ios: Custom Permission Alert View Text
Why Doesn't My Cordova/Phonegap iOS App Rotate When the Device Rotates
Error: Ld: Library Not Found for -Lpods with Cocoapods
Detect iOS Application About to Delete
Ld: Framework Not Found Parse Xcode 7 Beta
Using Secrandomcopybytes in Swift
Save Image Data to SQLite Database in Iphone
How to Mutate Structs in Swift Using Map
Swift Override Instance Variables
How to Render a Whole Uitableview as an Uiimage in iOS
Xcode 11 Backward Compatibility: "Uiwindowscene Is Only Available in iOS 13 or Newer"