AVAudioRecorder not saving recording - ios

I am making an iOS game. One of the things I need to do is to allow the user to make a quick little audio recording. This all works, but the recording is only temporarily saved. So when the user closes the app and reopens it, the recording should be able to play again, but it doesn't, it gets deleted when I close the app. I don't understand what I am doing wrong. Below is my code:
I setup the AVAudioRecorder in the ViewDidLoad method like this:
// Setup audio recorder to save file.
NSArray *pathComponents = [NSArray arrayWithObjects:[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject], #"MyAudioMemo.m4a", nil];
NSURL *outputFileURL = [NSURL fileURLWithPathComponents:pathComponents];
// Setup audio session.
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
NSMutableDictionary *recordSetting = [[NSMutableDictionary alloc] init];
[recordSetting setValue:[NSNumber numberWithInt:kAudioFormatMPEG4AAC] forKey:AVFormatIDKey];
[recordSetting setValue:[NSNumber numberWithFloat:44100.0] forKey:AVSampleRateKey];
[recordSetting setValue:[NSNumber numberWithInt:2] forKey:AVNumberOfChannelsKey];
audio_recorder = [[AVAudioRecorder alloc] initWithURL:outputFileURL settings:recordSetting error:nil];
audio_recorder.delegate = self;
audio_recorder.meteringEnabled = YES;
[audio_recorder prepareToRecord];
I have got the AVAudio delegate methods, too:
-(void)audio_playerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
NSLog(#"Did finish playing: %d", flag);
}
-(void)audio_playerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error {
NSLog(#"Decode Error occurred");
}
-(void)audio_recorderDidFinishRecording:(AVAudioPlayer *)recorder successfully:(BOOL)flag {
NSLog(#"Did finish recording: %d", flag);
}
-(void)audio_recorderEncodeErrorDidOccur:(AVAudioPlayer *)recorder error:(NSError *)error {
NSLog(#"Encode Error occurred");
}
When I want to play, record or stop the audio, I have made the following IBActions which are linked to UIButtons:
-(IBAction)play_audio {
NSLog(#"Play");
if (!audio_recorder.recording){
audio_player = [[AVAudioPlayer alloc] initWithContentsOfURL:audio_recorder.url error:nil];
[audio_player setDelegate:self];
[audio_player play];
}
}
-(IBAction)record_voice {
NSLog(#"Record");
if (!audio_recorder.recording) {
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setActive:YES error:nil];
// Start recording.
[audio_recorder record];
}
else {
// Pause recording.
[audio_recorder pause];
}
}
-(IBAction)stop_audio {
NSLog(#"Stop");
[audio_recorder stop];
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
[audioSession setActive:NO error:nil];
}
If you try my code you will see that it works, but it only seems to save the audio file temporarily.
What am I doing wrong? I thought I had used all the correct AVAudioRecorder methods?

To make a working recorder and save the recorded files, you need to:
Create a new audio session
Make sure microphone is connected/working
Start recording
Stop recording
Save the recorded audio file
Play the saved voice file
You're missing step 5 in your code, so the file that's just recorded is still available for you to play, but once you close the app, as it's not saved into an actual file somewhere in the app's directories, you lose it. You should add a method to save the recorded audio into a file so that you can access it any time later:
-(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;
audio_recorder = [[AVAudioRecorder alloc] initWithURL:destinationURL settings:settings error:&error];
audio_recorder.delegate = self;
}
Unrelated to this problem, but a general thing to mention is that you must follow Apple's (Objective-C's) naming conventions when defining variables, etc. audio_recording in no way follows these guidelines. You could use something like audioRecording instead.

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.

Related

save recorded audio in documents directory and create a database that contains all audio files

I am new to ios development and facing problem in creating an app.
My app can record audio and save and play it back again.
I have used AVAudiorecorder and AVAudioplayer.
As soon as the recording is stopped, it asks the user to give the name (in textfield) to the file to save and then, the list of all saved files should appear and can be played on the next screen. (same as the italk recorder app)
I want to save the recorded file in documents directory and want a database that will contain metadata of all audio files. Database should have id, date, title, path.
This is what i have done :
if (player.playing) {
[player stop];
}
if (!recorder.recording) {
[self updatefilecounter];
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setActive:YES error:nil];
// Set the audio file
//filename = #"nnnn";
filename = #"Music/nnnn";
filename = [filename stringByAppendingString:#(filecounter).stringValue];
filename = [filename stringByAppendingString:#".m4a"];
NSArray *pathComponents = [NSArray arrayWithObjects:
[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject],filename,nil];
NSURL *outputFileURL = [NSURL fileURLWithPathComponents:pathComponents];
// Setup audio session
[session setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
// Define the recorder setting
NSMutableDictionary *recordSetting = [[NSMutableDictionary alloc] init];
[recordSetting setValue:[NSNumber numberWithInt:kAudioFormatMPEG4AAC] forKey:AVFormatIDKey];
[recordSetting setValue:[NSNumber numberWithFloat:44100.0] forKey:AVSampleRateKey];
[recordSetting setValue:[NSNumber numberWithInt: 2] forKey:AVNumberOfChannelsKey];
// Initiate and prepare the recorder
recorder = [[AVAudioRecorder alloc] initWithURL:outputFileURL settings:recordSetting error:NULL];
recorder.delegate = self;
recorder.meteringEnabled = YES;
NSLog(#"%#", outputFileURL);
NSLog(#"%#", filename);
[recorder record];
} else {
// Stop recording
[recorder stop];
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
[audioSession setActive:NO error:nil];
SaveAudioVC *vc = [self.storyboardinstantiateViewControllerWithIdentifier:#"SaveAudioVC"];
[self.navigationController pushViewController:vc animated:YES];
}
}
File gets saved as nnnn.1, nnnn.2 and so on, but when i stop the app and run again it overwrites the last files and i want the the user to give the name of his/her choice to save the file and display in last screen with the given name to the recorded file.
I have referred this link : Record audio and save permanently in iOS
But my other requirements are not matching so plz guide me to save the recorded file in documents directory, give the name to the file as per user wish in the text field and display all the saved files and create a database as mentioned above.
i think you should give path string like [#"Music" stringByAppendingByPathComponents:#"nnnn"] instead of filename = #"Music/nnnn";i hope it will give you some idea.when i starts audio recording i give it temporary name like temp.m4a than when i stops recording i use bellow method to replace temporary name with new name.
/*
give name and replace it with defaultName'temp.m4a'
*/
- (void)convertNewName:(NSString*)newname{
NSString* folder=[documentDirectory stringByAppendingPathComponent:#"VoiceDB"];
NSString* defaultfile=[folder stringByAppendingPathComponent:#"temp.m4a"];
newname=[NSString stringWithFormat:#"%#.m4a",newname];
NSString *newPath = [[defaultfile stringByDeletingLastPathComponent] stringByAppendingPathComponent:newname];
[[NSFileManager defaultManager] moveItemAtPath:defaultfile toPath:newPath error:nil];
}

How to prevent AVAudioRecorder taking 3% CPU when "sleeping"

I have an AudioPlayer implemented in an app. As soon as I call [AVAudioRecorder prepareToRecord]; the CPU usage goes up to 5%. Fine, because something useful is happening. But after [AVAudioRecorder stop]; is called, CPU drops to 3% and stays there.
This is the code where I implement the recorder:
- (void)prepareRecorder
{
outputFileString = [NSString stringWithFormat:#"%#/%#", [self applicationDocumentsDirectory], #"audio.m4a"];
NSArray *pathComponents = [NSArray arrayWithObjects:[self applicationDocumentsDirectory], #"audio.m4a", nil];
outputFileURL = [NSURL fileURLWithPathComponents:pathComponents];
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setActive:YES error:nil];
[session setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
NSMutableDictionary *recordSetting = [[NSMutableDictionary alloc] init];
[recordSetting setValue:[NSNumber numberWithInt:kAudioFormatMPEG4AAC] forKey:AVFormatIDKey];
[recordSetting setValue:[NSNumber numberWithFloat:44100.0] forKey:AVSampleRateKey];
[recordSetting setValue:[NSNumber numberWithInt:2] forKey:AVNumberOfChannelsKey];
recorder = [[AVAudioRecorder alloc] initWithURL:outputFileURL settings:recordSetting error:nil];
recorder.delegate = self;
recorder.meteringEnabled = YES;
[recorder prepareToRecord];
}
I don't know if it's possible to release/dealloc an object in ARC, but I tried
recorder = Nil;
How can I solve this? Or shouldn't I bother about the 3%?
If You say it stays 3% no matter how much You start and stop AudioRecording, then there is no leak, but possibly SoundRecording framework has cached some private sound recording instances (something that is located and happens behind the Apple frameworks hood) and one of the reasons would be - next time to start recording faster than on first time. These cached instances would be deallocated in case sound player is not used anymore AND there would be a need of extra memory.
I think similarly is with AVPlayer and MFMessageComposeViewController (and probably many more frameworks/api's, which are heavy-to-run) - because - for both of these mentioned frameworks - on the first time I try to show SMS form, or play a video - there is a noticable delay. After that - there is no delay. So on first time it probably loads in cache something important, which can be auto-purged in case there is little memory available or application is closed.
So I think it is perfectly fine not to bother about this.

AVAudioRecorder throwing exception on prepareToRecord

I'm running sample code on audio recording (source code downloadable at the end of the article). The code is like this
NSArray *pathComponents = [NSArray arrayWithObjects:
[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject],
#"MyAudioMemo.m4a",
nil];
NSURL *outputFileURL = [NSURL fileURLWithPathComponents:pathComponents];
// Setup audio session
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
// Define the recorder setting
NSMutableDictionary *recordSetting = [[NSMutableDictionary alloc] init];
[recordSetting setValue:[NSNumber numberWithInt:kAudioFormatMPEG4AAC] forKey:AVFormatIDKey];
[recordSetting setValue:[NSNumber numberWithFloat:44100.0] forKey:AVSampleRateKey];
[recordSetting setValue:[NSNumber numberWithInt: 2] forKey:AVNumberOfChannelsKey];
// Initiate and prepare the recorder
recorder = [[AVAudioRecorder alloc] initWithURL:outputFileURL settings:recordSetting error:nil];
recorder.delegate = self;
recorder.meteringEnabled = YES;
[recorder prepareToRecord];
However it throws an exception at prepareToRecord while running on the simulator:
It does record and it'll run fine if I just turn off the break point at exception. But this is annoying. What's wrong?
I'm getting this exception, too. Searching for solution. Important to note that you can only see these exceptions if you enable exception breakpoints. The app perform properly, but this is concerning. I'll keep investigating.
EDIT:
well, the answer is here: AVAudioPlayer throws breakpoint in debug mode
i figured that much myself, but still feel uncomfortable about having these exceptions. using different audio format while recording makes the exceptions to go away. however, i want to use the lossless format, which does cause these exceptions. hope this helps.

NSURL not working after retrieved from NSMutableArray

Having trouble storing an audio recording file address in an array and using it later.
This is how I get the URL address, convert it to a string and add it to the array. This seems to work.
testViewController.h
#interface record_audio_testViewController : UIViewController <AVAudioRecorderDelegate> {
NSURL *audioFile;
AVAudioRecorder *recorder;
...
}
#property (nonatomic, retain) NSURL *audioFile;
testViewController.m
#import "record_audio_testViewController.h"
#implementation record_audio_testViewController
#synthesize audioFile
- (void)viewDidLoad {
NSMutableArray *urlArray = [[NSMutableArray alloc] init];
audioFile = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent: [NSString stringWithFormat: #"%.0f.%#", [NSDate timeIntervalSinceReferenceDate] * 1000.0, #"caf"]]];
// convert NSURL to NSString
NSString *fileLoc = [audioFile absoluteString];
[urlArray addObject: fileLoc ];
[[NSUserDefaults standardUserDefaults] setObject:urlArray forKey:#"urlArray"];
[[NSUserDefaults standardUserDefaults] synchronize];
[urlArray release];
// Create an AVAudioSession object.
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
[audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error: &error];
//Activate the session
[audioSession setActive:YES error: &error];
}
- (void)buttonClicked:(UIButton*)button {
NSMutableArray *urlArray;
urlArray = [[NSUserDefaults standardUserDefaults] objectForKey:#"urlArray"];
audioFile = [NSURL URLWithString: [urlArray objectAtIndex:btnN] ];
// at this point, the URL looks exactly as created
}
- (IBAction) rec_button_pressed {
NSMutableDictionary* recordSetting = [[NSMutableDictionary alloc] init];
[recordSetting setValue :[NSNumber numberWithInt:kAudioFormatAppleIMA4] forKey:AVFormatIDKey];
[recordSetting setValue:[NSNumber numberWithFloat:44100.0] forKey:AVSampleRateKey];
[recordSetting setValue:[NSNumber numberWithInt: 2] forKey:AVNumberOfChannelsKey];
recorder = [[ AVAudioRecorder alloc] initWithURL: audioFile settings: recordSetting error:&error ]; // this is where it CRASHES
[recorder setDelegate:self];
[recorder prepareToRecord];
[recorder record];
}
When I use NSLog to look at and compare these addresses, they all look the same. But the program throws an exception:
Thread 1: EXC_BAD_ACCESS (code=2 address=0x9)
Anyone know how to get through this situation?
Also, are there easier and/or safer ways to do this?
The code you're showing could be completely irrelevant, since it isn't here that the exception is being thrown. The exception is probably due to a memory management error, and might have nothing to do with NSURL. You are just guessing about that - and you shouldn't be.
There is no memory management in your code, and since you are not using ARC, that suggests that you're unaware of your memory management responsibilities. My book explains them to you, and also explains why you should use ARC if you can:
http://www.apeth.com/iOSBook/ch12.html
Also, use the static analyzer; it will spot some of the danger signs of memory mismanagement.

iOS avaudiosession not starting on certain iOS hardware

I have a strange situation with an AVAudioRecorder used to record audio in my iOS app. I have found some rare iPad2 devices where the audio session will not start recording. The iOS app code does record fine for all other types of iPod Touches, iPhones, and iPads as well as in the simulator. It only seems to not be able to record in a very few cases. Does anyone know why this might be happening?
Here is the coding information.
In my .h file I have the AVAudioRecorder set up...
RVC.h
#interface RVC : UIViewController <AVAudioRecorderDelegate, AVAudioPlayerDelegate>{
AVAudioRecorder *audioRecorder;
}
#property (nonatomic, retain) AVAudioRecorder *audioRecorder;
RVC.m
#implementation RVC
#synthesize audioRecorder;
// Set up the file path and name, where the path is to the temporary directory
soundFile = [NSURL fileURLWithPath:[tempDir stringByAppendingFormat:#"theSoundFile.m4a"]];
// Set up the settings for the recording
soundSetting = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithFloat: 44100.0],AVSampleRateKey,
[NSNumber numberWithInt: kAudioFormatMPEG4AAC], AVFormatIDKey,
[NSNumber numberWithInt: 2], AVNumberOfChannelsKey,
[NSNumber numberWithInt: AVAudioQualityHigh], AVEncoderAudioQualityKey, nil];
// Make the AVAudioRecorder with the filename and the sound settings.
self.audioRecorder = [[AVAudioRecorder alloc] initWithURL:soundFile settings:soundSetting error:&error];
// Check to make sure the iOS device has audio hardware that can make a recording
BOOL audioHWAvailable = [[AVAudioSession sharedInstance] inputIsAvailable];
if (! audioHWAvailable) {
NSLog(#"No audio hardware is available.");
return;
}
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
// Make sure any output sound is played through the speakers.
UInt32 audioRouteOverride = kAudioSessionOverrideAudioRoute_Speaker;
AudioSessionSetProperty (kAudioSessionProperty_OverrideAudioRoute,sizeof (audioRouteOverride),&audioRouteOverride);
[[AVAudioSession sharedInstance] setActive:YES error:nil];
// If the audiorecorder is properly set up, then start recording.
if (audioRecorder) {
[audioRecorder prepareToRecord];
// Start the recording
isRecording = [audioRecorder record];
}
else {
isRecording = FALSE;
NSLog(#"The audio recorder was not properly set up.");
}
if (isRecording) {
// The AVAudioSession is recording.
}
else {
// The AVAudioSession did not start so make some kind of log file.
NSLog(#"The recording was not started properly.");
// This is where THE PROBLEM SHOWS UP where it is having trouble with some versions of the iPad2, which I can not replicate in the simulators.
return;
}
You are passing in an error in the init call:
self.audioRecorder = [[AVAudioRecorder alloc] initWithURL:soundFile settings:soundSetting error:&error];
but you are never reading it. I would bet that on the failed cases, you are getting an error here, causing the audioRecorder to be nil.

Resources