iOS - Code only works when I insert a breakpoint - iphone

Update
I'm starting to figure this out. The AVAudioRecorder session gets activated and I get mic level readings for a few seconds. Then the async video code completes, the camera view displays, and I stop getting readings. It seems like the video is killing the audio session.
What's strange is that the code works on iOS 7 and won't work on iOS 6. Any ideas how to get around this. Is it a limitation on iOS 6?
I'm getting sound levels via the mic and I can only get them when I place a breakpoint on the code that sets up my audio recorder object. If I run without pausing at that point for a few seconds it can't get the levels. This code is in viewDidLoad and is and I can place the breakpoint on one of those first 3 lines:
_recorder = [[AVAudioRecorder alloc] initWithURL:url settings:settings error:&error];
[_recorder prepareToRecord];
_recorder.meteringEnabled = YES;
_levelTimer = [NSTimer scheduledTimerWithTimeInterval: 0.03 target: self selector: #selector(levelTimerCallback) userInfo: nil repeats: YES];
I get my levels in the levelTimerCallback method.
This code goes in viewDidAppear:
NSError *error;
if (_recorder) {
[_recorder record];
} else
NSLog(#"Error: %#", [error description]);
Edit:
levelTimerCallback code:
[_recorder updateMeters];
const double ALPHA = 0.05;
double peakPowerForChannel = pow(10, (0.05 * [_recorder peakPowerForChannel:0]));
_lowPassResults = ALPHA * peakPowerForChannel + (1.0 - ALPHA) * _lowPassResults;
NSLog(#"Average input: %f Peak input: %f", [_recorder averagePowerForChannel:0], [_recorder peakPowerForChannel:0]);
Async code run above recorder code:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[[self captureManager] session] startRunning];
});
Camera code:
if ([self captureManager] == nil) {
AVCamCaptureManager *manager = [[AVCamCaptureManager alloc] init];
[self setCaptureManager:manager];
[[self captureManager] setDelegate:self];
if ([[self captureManager] setupSession]) {
// Create video preview layer and add it to the UI
AVCaptureVideoPreviewLayer *newCaptureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:[[self captureManager] session]];
UIView *view = [self videoPreviewView];
CALayer *viewLayer = [view layer];
[viewLayer setMasksToBounds:YES];
CGRect bounds = [view bounds];
[newCaptureVideoPreviewLayer setFrame:bounds];
if ([[newCaptureVideoPreviewLayer connection] isVideoOrientationSupported]) {
[[newCaptureVideoPreviewLayer connection] setVideoOrientation:AVCaptureVideoOrientationPortrait];
}
[newCaptureVideoPreviewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
[viewLayer insertSublayer:newCaptureVideoPreviewLayer below:[[viewLayer sublayers] objectAtIndex:0]];
[self setCaptureVideoPreviewLayer:newCaptureVideoPreviewLayer];
// Start the session. This is done asychronously since -startRunning doesn't return until the session is running.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[[self captureManager] session] startRunning];
});
[self addObserver:self forKeyPath:#"captureManager.videoInput.device.focusMode" options:NSKeyValueObservingOptionNew context:AVCamFocusModeObserverContext];
// Add a single tap gesture to focus on the point tapped, then lock focus
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapToAutoFocus:)];
[singleTap setDelegate:self];
[singleTap setNumberOfTapsRequired:1];
[view addGestureRecognizer:singleTap];
// Add a double tap gesture to reset the focus mode to continuous auto focus
UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapToContinouslyAutoFocus:)];
[doubleTap setDelegate:self];
[doubleTap setNumberOfTapsRequired:2];
[singleTap requireGestureRecognizerToFail:doubleTap];
[view addGestureRecognizer:doubleTap];
}
}
NSURL *url = [NSURL fileURLWithPath:#"/dev/null"];
NSDictionary *settings = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithFloat: 44100.0], AVSampleRateKey,
[NSNumber numberWithInt: kAudioFormatAppleLossless], AVFormatIDKey,
[NSNumber numberWithInt: 1], AVNumberOfChannelsKey,
[NSNumber numberWithInt: AVAudioQualityMax], AVEncoderAudioQualityKey,
nil];
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
[audioSession setActive: YES error: nil];
[[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryRecord error: nil];
NSError *error;
_recorder = [[AVAudioRecorder alloc] initWithURL:url settings:settings error:&error];

I believe you should call _levelTimer = [NSTimer scheduledTimerWithTimeInterval: 0.03 target: self selector: #selector(levelTimerCallback) userInfo: nil repeats: YES];
at the end of viewDidAppear instead, and nil it out and invalidate it in viewDidDisappear. Having a timer firing from viewDidLoad does not make sense when you have the other code in viewDidAppear. You may also want to check using the main thread instead of the background thread for [[[self captureManager] session] startRunning];.

Related

iOS - Overwrite the particular audio recording In specific time

Hi I need to develop an app that can record, play, stop and overwrite audio. I have done the Recording by using AvAudioRecorder:
NSDictionary *audioSettings = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithFloat: 44100],AVSampleRateKey,
[NSNumber numberWithInt: kAudioFormatLinearPCM],AVFormatIDKey,
[NSNumber numberWithInt: 2],AVNumberOfChannelsKey,
[NSNumber numberWithInt: AVAudioQualityLow], AVEncoderAudioQualityKey,
nil];
self.audioRecorder = [[AVAudioRecorder alloc]
initWithURL:audioFileURL
settings:audioSettings
error:nil];
[sliderTimer invalidate];
[self.audioRecorder record];
sliderTimer = [NSTimer scheduledTimerWithTimeInterval:0.2
target:self
selector:#selector(updateSlider)
userInfo:nil repeats:YES];
...and playback is done using AVplayer.
But I dont know how to overwrite the recording. This the outline of my overwrite implementation:
Stop Recording
Move the slider position to the particular point
Then, start recording.
This is the functionality to overwrite the previous recordings.
So, As per the steps I have written
[self.audioRecorder stop];
[[self audioSlider] setValue:self.audioSlider.value animated:YES];
[self.audioRecorder recordAtTime:self.audioSlider.value forDuration:self.audioSlider.maximumValue];
...but its not working. Instead, they totally re-record the file. Could any body help me for this critical situation.
Create audioSession object,its upto you whether you want to activate or deactivate your app’s audio session.(AVAudioSession)
audioSession = [AVAudioSession sharedInstance];
[audioSession setCategory :AVAudioSessionCategoryPlayAndRecord error:&err];
if(err) {
NSLog(#"audioSession: %# %d %#", [err domain], [err code], [[err userInfo] description]);
return;
}
[audioSession setActive:YES error:&err];
Start recording proces,
-startRecording
{
[recordSetting setValue :[NSNumber numberWithInt:kAudioFormatAppleIMA4] forKey:AVFormatIDKey];
// We can use 44100, 32000, 24000, 16000 or 12000 depending on sound quality
[recordSetting setValue:[NSNumber numberWithFloat:32000.0] forKey:AVSampleRateKey];
// We can use 2(if using additional h/w) or 1 (iPhone only has one microphone)
[recordSetting setValue:[NSNumber numberWithInt: 1] forKey:AVNumberOfChannelsKey];
mediaPath = [[NSString stringWithFormat:#"%#/myVoice.mp3", DOCUMENTS_FOLDER] retain];//where you want to save your recorded audio.
NSURL *url = [NSURL fileURLWithPath:mediaPath];
err = nil;
NSData *audioData = [NSData dataWithContentsOfFile:[url path] options: 0 error:&err];
recorder = [[ AVAudioRecorder alloc] initWithURL:url settings:recordSetting error:&err];
[recorder setDelegate:self];
[recorder prepareToRecord];
recorder.meteringEnabled = YES;
BOOL audioHWAvailable = audioSession.inputAvailable;
if (! audioHWAvailable) {
UIAlertView *cantRecordAlert = [[UIAlertView alloc] initWithTitle: #"Warning"
message: #"Audio input hardware not available"
delegate: nil cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[cantRecordAlert show];
[cantRecordAlert release];
return;
}
[recorder record];
}
Pause recording:
-pauseRecording
{
[recorder pause];
//[recorder updateMeters];
}
Again resume the process..audiosession will do it for you...
-resumerecording
{
[recorder record];
//[recorder updateMeters];
}
EDIT: [recorder updateMeters]; should be called periodically which refreshes the average and peak power values for all channels of an audio recorder.
You can use timer for that.
For example:
NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(updateAudioDisplay) userInfo:NULL repeats:YES];
-(void)updateAudioDisplay
{
if (!recorder.isRecording)
{
[recorder updateMeters];
}
else
{
if (recorder == nil) {
//recording is not yet started
}
else
{
//paused
}
}
}
You can download the sample code from here.

AVCaptureSession With STT(Speech to text)

I am using AVCapture Session to record video same time i am using STT(google Speech to text api) to convert voice into text. I have facing a problem when I click on the speak button then camera get freezes. Any correct answer will be acceptable. Thanks in advance .
To start camera in
-(void)viewDidLoad;
if ([[self captureManager] setupSession]) {
// Create video preview layer and add it to the UI
AVCaptureVideoPreviewLayer *newCaptureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:[[self captureManager] session]];
UIView *view = [self videoPreviewView];
CALayer *viewLayer = [view layer];
[viewLayer setMasksToBounds:YES];
CGRect bounds = [view bounds];
[newCaptureVideoPreviewLayer setFrame:bounds];
if ([newCaptureVideoPreviewLayer isOrientationSupported]) {
[newCaptureVideoPreviewLayer setOrientation:AVCaptureVideoOrientationLandscapeLeft|AVCaptureVideoOrientationLandscapeRight];
}
[newCaptureVideoPreviewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
[viewLayer insertSublayer:newCaptureVideoPreviewLayer below:[[viewLayer sublayers] objectAtIndex:0]];
[self setCaptureVideoPreviewLayer:newCaptureVideoPreviewLayer];
[newCaptureVideoPreviewLayer release];
// Start the session. This is done asychronously since -startRunning doesn't return until the session is running.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[[self captureManager] session] startRunning];
});
[self updateButtonStates];
}
- (BOOL) setupSession
{
BOOL success = NO;
// Set torch and flash mode to auto
if ([[self backFacingCamera] hasFlash]) {
if ([[self backFacingCamera] lockForConfiguration:nil]) {
if ([[self backFacingCamera] isFlashModeSupported:AVCaptureFlashModeAuto]) {
[[self backFacingCamera] setFlashMode:AVCaptureFlashModeAuto];
}
[[self backFacingCamera] unlockForConfiguration];
}
}
if ([[self backFacingCamera] hasTorch]) {
if ([[self backFacingCamera] lockForConfiguration:nil]) {
if ([[self backFacingCamera] isTorchModeSupported:AVCaptureTorchModeAuto]) {
[[self backFacingCamera] setTorchMode:AVCaptureTorchModeAuto];
}
[[self backFacingCamera] unlockForConfiguration];
}
}
// Init the device inputs
AVCaptureDeviceInput *newVideoInput = [[AVCaptureDeviceInput alloc] initWithDevice:[self frontFacingCamera] error:nil];
AVCaptureDeviceInput *newAudioInput = [[AVCaptureDeviceInput alloc] initWithDevice:[self audioDevice] error:nil];
// Setup the still image file output
AVCaptureStillImageOutput *newStillImageOutput = [[AVCaptureStillImageOutput alloc] init];
NSDictionary *outputSettings = [[NSDictionary alloc] initWithObjectsAndKeys:
AVVideoCodecJPEG, AVVideoCodecKey,
nil];
[newStillImageOutput setOutputSettings:outputSettings];
[outputSettings release];
// Create session (use default AVCaptureSessionPresetHigh)
AVCaptureSession *newCaptureSession = [[AVCaptureSession alloc] init];
// Add inputs and output to the capture session
if ([newCaptureSession canAddInput:newVideoInput]) {
[newCaptureSession addInput:newVideoInput];
}
if ([newCaptureSession canAddInput:newAudioInput]) {
[newCaptureSession addInput:newAudioInput];
}
if ([newCaptureSession canAddOutput:newStillImageOutput]) {
[newCaptureSession addOutput:newStillImageOutput];
}
[self setStillImageOutput:newStillImageOutput];
[self setVideoInput:newVideoInput];
[self setAudioInput:newAudioInput];
[self setSession:newCaptureSession];
[newStillImageOutput release];
[newVideoInput release];
[newAudioInput release];
[newCaptureSession release];
// Set up the movie file output
NSURL *outputFileURL = [self tempFileURL];
AVCamRecorder *newRecorder = [[AVCamRecorder alloc] initWithSession:[self session] outputFileURL:outputFileURL];
[newRecorder setDelegate:self];
// Send an error to the delegate if video recording is unavailable
if (![newRecorder recordsVideo] && [newRecorder recordsAudio]) {
NSString *localizedDescription = NSLocalizedString(#"Video recording unavailable", #"Video recording unavailable description");
NSString *localizedFailureReason = NSLocalizedString(#"Movies recorded on this device will only contain audio. They will be accessible through iTunes file sharing.", #"Video recording unavailable failure reason");
NSDictionary *errorDict = [NSDictionary dictionaryWithObjectsAndKeys:
localizedDescription, NSLocalizedDescriptionKey,
localizedFailureReason, NSLocalizedFailureReasonErrorKey,
nil];
NSError *noVideoError = [NSError errorWithDomain:#"AVCam" code:0 userInfo:errorDict];
if ([[self delegate] respondsToSelector:#selector(captureManager:didFailWithError:)]) {
[[self delegate] captureManager:self didFailWithError:noVideoError];
}
}
[self setRecorder:newRecorder];
[newRecorder release];
success = YES;
return success;
}

AVAudioPlayer and AVAudioRecorder: Delegate Methods not Called

My delegate methods audioRecorderDidFinishRecording and audioPlayerDidFinishPlaying are not being called. Those methods should trigger a 'stopanimation` method that stops an animation after recording is finished.
I have placed a call call to the stopanimation method at the end of audioPlayerDidFinishPlaying.
Here is the relevant code where the delegate is assigned:
VoiceEditor.h
#interface VoiceEditor : UIViewController <UITextFieldDelegate, AVAudioRecorderDelegate, AVAudioPlayerDelegate>{
}
VoiceEditor.m
- (void)record{
recorder = [[AVAudioRecorder alloc] initWithURL:[NSURL fileURLWithPath:pathString] settings:recordSettings error:nil];
[recorder setDelegate:self];
[recorder record];
recording = YES;
[pauseButton setImage:[UIImage imageNamed:#"stop.png"] forState:UIControlStateNormal];
[pauseButton setEnabled:YES];
[playButton setEnabled:NO];
[recordButton setEnabled:NO];
[self beginAnimation];
}
- (void)play{
player = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:pathString] error:nil];
double seconds=[player duration];
NSLog(#"%f",seconds);
[player setDelegate:self];
[player play];
playing = YES;
[recordButton setEnabled:NO];
[pauseButton setEnabled:YES];
[playButton setEnabled:NO];
[self beginAnimation];
}
Your code setting the delegate looks fine.
Are you assuming that the delegate methods are not called only because the animation does not stop or have you tried to log/breakpoint the methods directly? If you haven't tested directly do so before working on the assumption that they aren't.
If you have confirmed they are not being called the, most likely, your animation is tying up the self object such that it cannot respond to the AV delegate methods.
Comment out the animation and just put a log in the AV delegate methods to see if they are called.
Try to pass the error parameter to be sure your recorder object is being created properly.
NSError *err=nil;
recorder = [[AVAudioRecorder alloc] initWithURL:[NSURL fileURLWithPath:pathString] settings:recordSettings error:&err];
if(!recorder){
NSLog(#"recorder: %# %d %#", [err domain], [err code], [[err userInfo] description]);
}
Check the current AVAudioSession category. If the category is not set to AVAudioSessionRecord or AVAudioSessionPlayAndRecord, the recorder won't work and none of its delegate methods will be called. The AVAudioSession category can be changed with the setCategory:error: method. For example:
NSError *sessionCategoryError = nil;
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
BOOL success = [audioSession setCategory: AVAudioSessionCategoryPlayAndRecord
error: &sessionCategoryError];

Differences in behavior of AVAudioRecorder in iOS 4.1 and iOS3.1.3

I want to play an audio when someone speaks in the iPhone's mic. This code works perfectly in iPhone 2G(OS 3.1.3), but when it is tested in iPhone 4, it doesnt work. I checked for API and method differences, but could not find any.
- (void)viewDidLoad {
NSURL *url = [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/Audio.mp3", [[NSBundle mainBundle] resourcePath]]];
NSError *error;
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
audioPlayer.delegate=self;
if (audioPlayer == nil)
NSLog(#" %# ", [error description]);
audioSession = [[AVAudioSession sharedInstance] retain];
[audioSession setActive:YES error: nil];
[super viewDidLoad]; }
The action event:
-(IBAction) micAction{
[audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error: nil];
NSURL *url = [NSURL fileURLWithPath:#"/dev/null"];
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:url settings:settings error:&error];
if (recorder) {
[recorder prepareToRecord];
recorder.meteringEnabled = YES;
[recorder record];
levelTimer = [NSTimer scheduledTimerWithTimeInterval: 0.03 target: self selector: #selector(levelTimerCallback:) userInfo: nil repeats: NO];
} }
- (void)levelTimerCallback:(NSTimer *)t {
[recorder updateMeters];
if([recorder averagePowerForChannel:0]>-38){
if ([audioPlayer prepareToPlay]) {
[recorder stop];
[recorder release];
[levelTimer invalidate];
[self playSound];
}
}}
-(void)playSound{
[audioSession setCategory:AVAudioSessionCategoryPlayback error: nil];
if ([audioPlayer prepareToPlay])
[audioPlayer play]; }
To make sure that the audio is playing in iPhone 4, i'd also set up an IBAction event, which is working fine. Also, for testing I'd set up a label to display the value of
[recorder averagePowerForChannel:0
after the button is pressed. This label updates itself as per the timer in 2G, but in iPhone 4 it displays just one value on the click event.
Am unable to debug the behavior of the method in iPhone 4 since i dont have the device with me now.
Can anyone please point out what the error is?
Thanks
Have you tried saving to a file instead of to /dev/null?
Found the solution. Issue was in this line:
levelTimer = [NSTimer scheduledTimerWithTimeInterval: 0.03 target: self selector: #selector(levelTimerCallback:) userInfo: nil repeats: NO];
Since repeats was set to NO, the timer used to fire only once. So, the continuos monitoring of the averagePowerForChannel value could not be done. After setting it to YES, the code works perfectly.

AVAudioPlayer crash after playing from an AVAudioRecorder

I've got a button the user tap to start recording and tap again to stop. When it stop I want the recorded voice 'echo' back so the user can hear what was recorded. This works fine the first time. If I hit the button for the third time, it starts a new recording and when I hit stop it crashes with EXC_BAD_ACCESS.
- (IBAction) readToMeTapped {
if(recording)
{
recording = NO;
[readToMeButton setTitle:#"Stop Recording" forState: UIControlStateNormal ];
NSMutableDictionary *recordSetting = [[NSDictionary alloc] initWithObjectsAndKeys:
[NSNumber numberWithFloat: 44100.0], AVSampleRateKey,
[NSNumber numberWithInt: kAudioFormatAppleLossless], AVFormatIDKey,
[NSNumber numberWithInt: 1], AVNumberOfChannelsKey,
[NSNumber numberWithInt: AVAudioQualityMax], AVEncoderAudioQualityKey,
nil];
// Create a new dated file
NSDate *now = [NSDate dateWithTimeIntervalSinceNow:0];
NSString *caldate = [now description];
recordedTmpFile = [NSURL fileURLWithPath:[[NSString stringWithFormat:#"%#/%#.caf", DOCUMENTS_FOLDER, caldate] retain]];
error = nil;
recorder = [[ AVAudioRecorder alloc] initWithURL:recordedTmpFile settings:recordSetting error:&error];
[recordSetting release];
if(!recorder){
NSLog(#"recorder: %# %d %#", [error domain], [error code], [[error userInfo] description]);
UIAlertView *alert =
[[UIAlertView alloc] initWithTitle: #"Warning"
message: [error localizedDescription]
delegate: nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
[alert release];
return;
}
NSLog(#"Using File called: %#",recordedTmpFile);
//Setup the recorder to use this file and record to it.
[recorder setDelegate:self];
[recorder prepareToRecord];
[recorder recordForDuration:(NSTimeInterval) 5]; //recording for a limited time
}
else
{ // it crashes the second time it gets here!
recording = YES;
NSLog(#"Recording YES Using File called: %#",recordedTmpFile);
[readToMeButton setTitle:#"Start Recording" forState:UIControlStateNormal ];
[recorder stop]; //Stop the recorder.
//playback recording
AVAudioPlayer * newPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:recordedTmpFile error:&error];
[recordedTmpFile release];
self.aPlayer = newPlayer;
[newPlayer release];
[aPlayer setDelegate:self];
[aPlayer prepareToPlay];
[aPlayer play];
}
}
- (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)sender successfully:(BOOL)flag {
NSLog (#"audioRecorderDidFinishRecording:successfully:");
[recorder release];
recorder = nil;
}
Checking the debugger, it flags the error here
#synthesize aPlayer, recorder;
This is the part I don't understand. I thought it may have something to do with releasing memory but I've been careful. Have I missed something?
After working on it for a while I stumbled on this debugging tip. It showed me that AVAudioPlayer was deallocated the second time around, causing the crash. So the Delegate must have done the clean up? I checked this SO thread and it is suggestion that the Delegate does not deallocate. However, if I remove the line
[newPlayer release];
My program works! After reading this SO thread, I believe my issue is that I should implement AVAudioPlayerDelegate's - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag method and release the audio player there, after the sound is done playing. I had done it for AVAudioRecorder.
Did you make sure that #property (retain) does not include nonatomic? I'm thinking that some of the code that accesses those properties may be doing so from another thread. Also, you should always access those properties via their getters by using self.recorder and self.player, in order to make sure they are accessed within their respective locks.