recordForDuration hangs up, doesn't stop recording - objective-c

when i set up an audio recorder and just use a button to start/stop the "record" function call, everything seems to work fine. however, when i use a separate button to start the "recordForDuration" function call. it seems to just keep on recording or perhaps it thinks its recording. either way, it does not stop after the requested time duration. the while loop below the call just keeps spitting out "recording..." to the debug window forever. any ideas? this code was basically plucked from some working code i had in xcode5 that runs fine on iphone 5s with ios7.1. the code hangs up with anything running ios8.1 (also hangs up in 8.1 simulator). see my code below. thanks for any advice.
- (void)viewDidLoad {
[super viewDidLoad];
recordSwitchOn=NO;
//setup generic audio session
audioSession = [AVAudioSession sharedInstance];
[audioSession setCategory :AVAudioSessionCategoryPlayAndRecord error:nil];
[audioSession setActive:YES error:nil];
NSLog(#"setupRecorder:");
inFileName = [NSString stringWithFormat:#"recordedData.caf"];//5
inFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:inFileName];
inFileURL = [NSURL fileURLWithPath:inFilePath];
recordSetting = [[NSMutableDictionary alloc] init];
[recordSetting setValue :[NSNumber numberWithInt:kAudioFormatLinearPCM] forKey:AVFormatIDKey];
[recordSetting setValue:[NSNumber numberWithFloat:40000] forKey:AVSampleRateKey];
[recordSetting setValue:[NSNumber numberWithInt: 1] forKey:AVNumberOfChannelsKey];
[recordSetting setValue:[NSNumber numberWithInt:AVAudioQualityMax] forKey:AVEncoderAudioQualityKey];
// get audio file and put into a file ID
// AudioFileID fileID; //in header
AudioFileOpenURL((__bridge CFURLRef)inFileURL, kAudioFileReadWritePermission, kAudioFileCAFType,&inputFileID);
// initialize my audio recorders
inputRecorder = [[AVAudioRecorder alloc] initWithURL:inFileURL settings:recordSetting error:nil];
if (inputRecorder) {NSLog(#"inputRecorder is setup");
}else{NSLog(#"error setting up inputRecorder");}
}
- (IBAction)rec4durButt:(id)sender {
[statusLabel setText:#"Recording..."];
[inputRecorder setDelegate:self];
[inputRecorder recordForDuration:2.0];
while([inputRecorder isRecording]){
NSLog(#"recording...");
}
[statusLabel setText:#"Recording stopped"];
}
- (IBAction)recButt:(id)sender {
if(recordSwitchOn==NO){
[statusLabel setText:#"Recording..."];
recordSwitchOn=YES;
[inputRecorder setDelegate:self];
// [inputRecorder recordForDuration:(NSTimeInterval)0.35];
[inputRecorder record];
}else{
[statusLabel setText:#"Recording stopped"];
recordSwitchOn=NO;
[inputRecorder stop];
}
}

It seems that looping on the main thread blocks some aspect of the AVAudioRecorder from updating its recording status (or even getting started). By removing the loop, you free the recorder up to do its normal processing.
The delegate provides a method (audioRecorderDidFinishRecording:successfully:) that signals the completion of recording, so drop the while loop and implement that.

If you want continuous recording, recording exception may occur, so you should be one second after recording, I think, may be caused by audio cache

Related

freeing memory in a simple avaudioengine program?

I'm wondering how to free memory in this simple program that plays a file through a buffer and then stops it.
-(void)setupAudioOne
{
NSError *error;
BOOL success = NO;
_player = [[AVAudioPlayerNode alloc] init];
NSURL *hiphopOneURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"Hip Hop 1" ofType:#"caf"]];
AVAudioFile *hiphopOneFile = [[AVAudioFile alloc] initForReading:hiphopOneURL error:&error];
_playerLoopBuffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:[hiphopOneFile processingFormat] frameCapacity:(AVAudioFrameCount)[hiphopOneFile length]];
success = [hiphopOneFile readIntoBuffer:_playerLoopBuffer error:&error];
_engine = [[AVAudioEngine alloc] init];
[_engine attachNode:_player];
AVAudioMixerNode *mainMixer = [_engine mainMixerNode];
AVAudioFormat *stereoFormat = [[AVAudioFormat alloc] initStandardFormatWithSampleRate:44100 channels:2];
[_engine connect:_player to:mainMixer fromBus:0 toBus:0 format:stereoFormat];
[self startEngine];
}
Above is the general setup of the engine and the player node.
Then we implement the player with a play button:
- (IBAction)play:(id)sender {
if (!self.playerIsPlaying)
{
[self setupAudioOne];
[_player scheduleBuffer:_playerLoopBuffer atTime:nil options:AVAudioPlayerNodeBufferLoops completionHandler:nil];
[_player play];
}
}
And finally we stop the player with a stop button:
- (IBAction)stopHipHopOne:(id)sender {
if (self.playerIsPlaying) {
[_player stop];
}
playerIsPlaying is just a simple BOOL that determines if the _player is playing.
So basically my question is, when you hit the stop button as this program is written now, no memory will be freed.
Surely there is a simple line of code that I can add to the stop button that frees up the memory the engine and player is using?
Any thoughts?
Yes, there is. After stopping the player node, you can call:
[_engine disconnectNodeInput:_player];
[_engine detachNode:_player];
I saw you're also keeping a reference to the audio buffer, so you might want to nil that one as well. Let me know if that doesn't work for you. Something else could be leaking.

AVAudioRecorder doesn't record while screen is locked

I've tried to overcome this for a while. I'm trying to record sound, but the AVAudioRecorder doesn't record while screen is locked. It does continue to record once screen is unlocked, but the audio recorded when screen was locked is lost forever. I can't find anything wrong with what I'm doing:
-(void) startRecording
{
// Begin the recording session.
_session = [AVAudioSession sharedInstance];
NSError *setCategoryError = nil;
NSError *startRecordError;
[_session setActive:YES error:&startRecordError];
[self GKLog:[NSString stringWithFormat:#"recorder session error? :%#", startRecordError]];
[_session setCategory: AVAudioSessionCategoryRecord error: &setCategoryError];
if (setCategoryError) { NSLog(#"some error");}
//set me as delegate
_session.delegate=(id <AVAudioSessionDelegate>) self;
NSMutableDictionary* recordSetting = [[NSMutableDictionary alloc] init];
[recordSetting setValue :[NSNumber numberWithInt:kAudioFormatAppleIMA4] forKey:AVFormatIDKey];
[recordSetting setValue :[NSNumber numberWithInt:8] forKey:AVEncoderBitRateKey];
[recordSetting setValue:[NSNumber numberWithFloat:8000.0] forKey:AVSampleRateKey];
[recordSetting setValue:[NSNumber numberWithInt: 1] forKey:AVNumberOfChannelsKey];
if (!self.currentPath)
{
NSLog(#"can't record, no path set!");
return;
}
NSError *error;
NSURL *url=[NSURL fileURLWithPath:self.currentPath];
//Setup the recorder to use this file and record to it.
_recorder = [[ AVAudioRecorder alloc] initWithURL:url settings:recordSetting error:&error];
[self GKLog:[NSString stringWithFormat:#" recorder:%#",_recorder]];
_recorder.delegate=(id <AVAudioRecorderDelegate>) self;
[_recorder prepareToRecord];
//Start the actual Recording
[_recorder record];
}
Any ideas, please?
Ok, so the answer to my own question, which took me a long time to find out, is the following: The code I posted is good, but to actually work it needs to work in the background after screen was locked. For this one needs to add a UIBackgroundModes array in the app's plist file, and add 'audio' as one of its objects. This tells the system to let the app work with audio in the background.
Here's the not-so-easy to find documentation. Unfortunately apple doesn't specify that in their documentation of the audio session categories where they claim certain categories work in the background. Anyway, hopefully this answer will be available for others who have a similar problem...
You may want to consider setting the category as AVAudioSessionCategoryRecord to the AudioSession
How about disabling the screen lock until you are done recording?
[UIApplication sharedApplication].idleTimerDisabled = YES;
// Do recording here
[UIApplication sharedApplication].idleTimerDisabled = NO;
Just don't forget to re-enable the screen lock when you're done!

iphone iOS5 audioPlayerDidFinishPlaying not called screen lock?

I have a series of audio files which I want to play in sequence. I setup audioPlayerDidFinishPlaying delegate method to start the next file in the sequence. This works with no problem if I turn off screen lock or if I run it on the emulator or on an iOS4 device. However, when I run it on a disconnected device, iOS5, screen locked, it stops after the first track. Is there some way to start a new sound track after the screen lock has come on?
Here is my basic code
// Registers this class as the delegate of the audio session.
[[AVAudioSession sharedInstance] setDelegate: self];
// Use this code instead to allow the app sound to continue to play when the screen is locked.
[[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayback error: nil];
UInt32 doSetProperty = 0;
AudioSessionSetProperty ( kAudioSessionProperty_OverrideCategoryMixWithOthers,
sizeof (doSetProperty),
&doSetProperty
);
// Registers the audio route change listener callback function
AudioSessionAddPropertyListener (
kAudioSessionProperty_AudioRouteChange,
audioRouteChangeListenerCallback,
self
);
// Activates the audio session.
NSError *activationError = nil;
[[AVAudioSession sharedInstance] setActive: YES error: &activationError];
NSString *path = [[NSBundle mainBundle] pathForResource:filename ofType:#"m4a"];
float vol = [appSoundPlayer volume];
AVAudioPlayer * newAudio=[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
self.appSoundPlayer = newAudio; // automatically retain audio and dealloc old file if new file is loaded
[newAudio release]; // release the audio safely
appSoundPlayer.delegate = self;
[appSoundPlayer prepareToPlay];
[appSoundPlayer setNumberOfLoops:0];
[appSoundPlayer play];
[appSoundPlayer setVolume:vol];
[progTime setProgress:0];
}
- (void) audioPlayerDidFinishPlaying: (AVAudioPlayer *) lappSoundPlayer successfully: (BOOL) flag {
//... code here to select nex sound track
[lappSoundPlayer play];
}
I have already made it so the app continues to play the current track even when screen lock comes on, I just can't get it to start up the next track.
Note: I have verified that this was working in iOS4.
Have you set up an audio session category, specifically AVAudioSessionCategoryPlayback, as described here?
[[AVAudioSession sharedInstance] setDelegate: self];
NSError *error = nil;
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&error];
If that doesn't work, another thing to try, according to user ascanio in this message in the Apple dev forums, is to set your app up to listen for remote control events:
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];

4 second lag in AVAudioRecorder record

I'm using AVAudioRecorder to record audio but I'm experiencing a 4 second delay between button press and beginning to record.
Here's my setup code:
NSDictionary *recordSettings = [NSDictionary
dictionaryWithObjectsAndKeys:
[NSNumber numberWithFloat: 16000.0],AVSampleRateKey,
[NSNumber numberWithInt: kAudioFormatAppleIMA4],AVFormatIDKey,
[NSNumber numberWithInt: 1], AVNumberOfChannelsKey,
[NSNumber numberWithInt: AVAudioQualityMax],AVEncoderAudioQualityKey,nil];
NSError *error = nil;
audioRecorder = [[AVAudioRecorder alloc]
initWithURL:soundFileURL
settings:recordSettings
error:&error];
if (error)
{
NSLog(#"error: %#", [error localizedDescription]);
} else {
NSLog(#"prepare to record");
[audioRecorder prepareToRecord];
}
-(void)record {
NSLog(#"Record");
//[audioRecorder prepareToRecord];
if (!audioRecorder.recording)
{
NSLog(#"Record 2");
[audioRecorder record];
NSLog(#"Record 3");
}
}
Record is the function called on button press. I know prepareToRecord is called implicitly via 'record' but I wanted to see if it would affect the delay at all. It does not.
Here's the console log:
2011-10-18 21:48:06.508 [2949:707] Record
2011-10-18 21:48:06.509 [2949:707] Record 2
2011-10-18 21:48:10.047 [2949:707] Record 3
There's about 3.5 seconds before it starts recording.
Are these settings too much for the iPhone? (iPhone 4). Am I initializing it wrong?
I'm seeing the same issue on the iOS5/iPhone4 combo. iOS4 and iPhone4S are fine. There seems to be an issue when using the new OS on older hardware. I've tried many combinations of settings as well.
Try setting the AVAudioSession category when you initialize the Audio Recorder:
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
I had the same issue on iPod touch 4th generation (iOS4, iOS5 and JailBreak - I used more devices) and on different devices it runs different, but I think #Charles Chase is right, it does depend of device's iOS you are testing on. Your recordSettings dictionary is ok, code also looks right except one thing, you need to add this:
audioRecorder.delegate = self;
right after you alloc and init your recorder:
audioRecorder = [[AVAudioRecorder alloc]
initWithURL:soundFileURL
settings:recordSettings
error:&error];
audioRecorder.delegate = self;
I had the same problem. Also, since I was switching between Playback and Record mode, this was causing long delays constantly. But when I switched to Play and Record it refused to use the loudspeaker on iPhone, it would only use the headset speaker.
Solution:
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
UInt32 audioRouteOverride = kAudioSessionOverrideAudioRoute_Speaker;
AudioSessionSetProperty(kAudioSessionProperty_OverrideAudioRoute,
sizeof(audioRouteOverride), &audioRouteOverride);

ASIHttp Synchronous request is running delegate methods after returning

I am trying to download a file from a server. My code is following. In didFinishLaunchingWithOptions method, I create a new thread using detachNewThreadSelector which runs the following code.
NSString *destPath = [self.home_dir_path stringByAppendingPathComponent:[NSString stringWithFormat:#"_%#",content_data_file_name]];
[ContentBO downloadFile:destPath content_name:content_data_file_name];
if([self updatesAvailable]){
//update content
}else{
//launch app
}
My code for downloadFile is:
#try{
NSString *url = [NSString stringWithFormat:#"%#/%#",ServerURL,content_name];
NSLog(#"downloading URL is: %#",url);
self.request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:[url stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding]]];
[self.request setRequestMethod:#"GET"];
[self.request setDownloadDestinationPath:destFilePath];
NSLog(#"destination path is: %#",destFilePath);
[self.request setTimeOutSeconds:30];
[self.request setDelegate:self];
[self.request startSynchronous];
NSError *error = [self.request error];
NSData *receivedData = nil;
if (!error) {
isSuccess = YES;
self.responseStr = [request responseString];
receivedData = [NSData dataWithData:[self.request responseData]];
}
else {
isSuccess = NO;
NSLog(#"The following error occurred: %#", error);
}
}#catch(NSException *e){
NSLog(#"exception occured.");
}
What my understanding about synchronous call is that this is a blocking call and control should not go below
[ContentBO downloadFile:destPath content_name:content_data_file_name];
until control is out of requestFinished method of ASIHTTPRequestDelegate. In my case what happening is that the control is simultaneously executing code in requestFinished and below
[ContentBO downloadFile:destPath content_name:content_data_file_name];
But I don't want the control to go below [ContentBO downloadFile...] before coming out of requestFinished method.
The requestFinished delegate call is run on the main thread asynchronously, and your code is not running on the main thread, so it is expected that both would run at the same time.
However, as you are using synchronous requests why not remove the contents of requestFinished and put the code after the 'startSyncronous' line? You are guaranteed the request has finished when startSynchronous returns.
In one of my projects the app had to do heavy server side data syncing. In that process one operation should had start after the successful execution of it's previous process and I was using ASIHttp synchronous requests in that. I was facing the same issue you mentioned, so to tackle it I used NSCondiiton. All it requires that you lock the thread after you call:
[self.request startSynchronous];. When the requests delegate method is called after the exection of the request, issue a signal command, and the next line of the code after the thread lock statement will be executed. Here is a rough example:
//declare a pointer to NSCondition in header file:
NSCondition *threadlock;
-(id) init
{
threadlock = [[NSCondition alloc] init]; //release it in dealloc
}
-(void)downLoadFile
{
[thread lock];
//your request code
[self.request setDidFinishSelector:#selector(downLoadFileRequestDone:)];
[self.request setDidFailSelector:#selector(downLoadFileRequestWentWrong:)];
[self.request startSynchronous];
[thread wait];
//the lines here will be executed after you issue a signal command in the request delegate method
[thread unlock];
}
-(void) downLoadFileRequestDone:(ASIHTTPRequest *)request
{
[thread lock];
//perform desire functionality
//when you are done call:
[thread signal];
[thread unlock];
}
It worked perfect for me... hope it will help.