4

I am implementing the AQRecorder class from Apple's SpeakHere example into my project using ARC. To get it to compile, I had to create a class (AQRecorderController) that controls the AQRecorder instance (equivalent to the SpeakHereController in the example). AQRecorderController is connected through the nib of my main view controller and implemented as a property. The problem occurs whether or not the property is strong or weak.

My problem is that shortly after loading the view controller, the AQRecorderController is released, but only when tested on device. In the simulator, this does not occur. It occurs for iPad and iPhone, iOS 5 and iOS 6. I need to maintain this reference throughout the lifetime of my view controller for recording purposes (you can't delete the recorder while recording and expect to have a finished file).

Has anyone run into this or anything similar? If the AQRecorderController property is strong, I get a bad access error when trying to use it, if its weak, I just get a nil, and its unusable.

Any help would be greatly appreciated.

formViewController.h:

#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>

@class AQRecorderController;

@interface formViewController : UIViewController <UIActionSheetDelegate,     UITableViewDelegate, UIGestureRecognizerDelegate> {

    IBOutlet AQRecorderController *aqRecorderController;
}

@property (nonatomic, weak)     IBOutlet AQRecorderController *aqRecorderController;

@end

AQRecorderController.h

#import <Foundation/Foundation.h>
#import "AQRecorder.h"

@interface AQRecorderController : NSObject
{
    AQRecorder *aqRecorder;
}

@property (readonly)            AQRecorder* aqRecorder;
@property (nonatomic, assign)   bool        isRecording;
@property (nonatomic, strong)   NSString*   fileName;

-(bool)startRecording;
-(bool)pauseRecording;
-(bool)stopRecording;
-(bool)initializeRecordSettingsWithCompression:(bool)compressionEnabled;
@end

formView.xib: recorder nib

Here is the stack trace after the AQRecorderController has been released:

 2012-10-23 10:34:09.600 TestApp[510:907] (
       0   TestApp                             0x000f32ab
-[AQRecorderController dealloc] + 138
        1   CoreFoundation                      0x32247311 CFRelease + 100
        2   CoreFoundation                      0x3225195d <redacted> + 140
        3   libobjc.A.dylib                     0x31ad5489 <redacted> + 168
        4   CoreFoundation                      0x32249441 _CFAutoreleasePoolPop + 16
        5   Foundation                          0x37303a7f <redacted> + 466
        6   CoreFoundation                      0x322db5df <redacted> + 14
        7   CoreFoundation                      0x322db291 <redacted> + 272
        8   CoreFoundation                      0x322d9f01 <redacted> + 1232
        9   CoreFoundation                      0x3224cebd CFRunLoopRunSpecific + 356
        10  CoreFoundation                      0x3224cd49 CFRunLoopRunInMode + 104
        11  GraphicsServices                    0x32fb52eb GSEventRunModal + 74
        12  UIKit                               0x34e92301 UIApplicationMain + 1120
        13  TestApp                             0x00081a9d main + 48
        14  TestApp                             0x0005aa68 start + 40
)

This is where the recorder is instantiated.

AQRecorderController.mm:

- (void)awakeFromNib
{
    aqRecorder = new AQRecorder();
}

This is where the recorder is used. By this point, the AQRecorderController has been released and this code never executes (it causes a crash, because the AQRecorderController has been deallocated).

-(bool)startRecording
{
    if (aqRecorder->IsRunning())
    {
            [self stopRecording];
    }
    else // If we're not recording, start.
    {
    @try
    {
        // Start the recorder
        CFStringRef filenameString = (CFStringRef)CFBridgingRetain(self.fileName);
        aqRecorder->StartRecord(filenameString);
    }
    @catch(NSException *ex)
    {
        NSLog(@"Error: %@", [ex description]);
        return NO;
    }
            [self setFileDescriptionForFormat:aqRecorder->DataFormat() withName:@"Recorded File"];
    }

[self checkIfRecording];

return YES;

}

Here is where the AQRecorderController is instantiated.

formViewController.mm:

//this is called in viewDidAppear
-(void)initializeAQRecorder: (NSString*)soundFileName
{
    aqRecorderController = [[AQRecorderController alloc] init];


    NSLog(@"AQRecorderController is being initialized for file %@",soundFileName);
    NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDir = [documentPaths objectAtIndex:0];
    NSString *soundFilePath =[[NSString alloc] initWithFormat:@"%@",[documentsDir stringByAppendingPathComponent:soundFileName]];

    [aqRecorderController setFileName:soundFilePath];
    [aqRecorderController initializeRecordSettingsWithCompression:NO];

}
Glorfindel
  • 21,988
  • 13
  • 81
  • 109
rmooney
  • 6,123
  • 3
  • 29
  • 29
  • 1
    Can you show us code please? Headers of the relevant files and perhaps a screenshot of Interface Builder showing the setup in there. – mattjgalloway Oct 22 '12 at 16:23
  • If you can please add the full stack trace, and where you instantiate and use your AQRecorder. – 8vius Oct 22 '12 at 23:53

3 Answers3

3

My problem is that shortly after loading the view controller, the AQRecorderController is released...I need to maintain this reference throughout the lifetime of my view controller

Mark your property strong instead of weak. weak means that the object pointed to by aqRecorderController won't be retained by the setter; strong will cause it to be retained.

If the AQRecorderController property is strong, I get a bad access error when trying to use it, if its weak, I just get a nil, and its unusable.

That sounds like the property is being set to some invalid value somewhere in your program. Since you can't manually retain the object under ARC and you've marked the property weak, it may be released very early on. I'm not sure why you'd have a problem if you mark it strong... it'd help to see the code where you set the variable or property.

Caleb
  • 124,013
  • 19
  • 183
  • 272
  • I'm not sure that the readonly of the AQRecorder would have any effect on the AQRecorderController being released. It is not the AQRecorder that is being released, its the controller (NSObject) containing it. I will post stack trace tomorrow. – rmooney Oct 23 '12 at 00:36
  • @user1505030 Sorry -- you're quite right... I confused the two properties. I'll edit the answer to fix that. – Caleb Oct 23 '12 at 00:51
1

You're never setting the AQRecorderController to your formViewController from what I see. You need to do self.aqRecorderController = aqRecorderController, I believe it's just disappearing as soon as you leave the scope where you create the controller.

8vius
  • 5,786
  • 14
  • 74
  • 136
  • This did not fix it, but it looks like something I needed to do. Thanks 8vius. – rmooney Oct 23 '12 at 21:45
  • Ok, but we're getting a little closer then. Still the same error? Check what happens to your AQController right after you exit the scope you instantiate it in. – 8vius Oct 23 '12 at 22:27
0

I got it working for now. I haven't completely fixed it, but I can record without it crashing. I commented out every line having to do with AQRecorderController until it stopped being released, then slowly added them back until I found out where it happens. It looks like the audio session setup code somehow provokes it to release the controller. This is the code that causes it (but no errors are thrown here):

From AQRecorderController.mm:

-(void)initializeRecordSettingsWithCompression:(bool)compressionEnabled
{

    OSStatus error = AudioSessionInitialize(NULL, NULL, interruptionListener, (__bridge void*)self);
    if (error) printf("ERROR INITIALIZING AUDIO SESSION! %d\n", (int)error);
    else
    {
            UInt32 category = kAudioSessionCategory_PlayAndRecord;
            error = AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(category), &category);
            if (error) printf("couldn't set audio category!");

            error = AudioSessionAddPropertyListener(kAudioSessionProperty_AudioRouteChange, propListener, (__bridge void*)self);
            if (error) printf("ERROR ADDING AUDIO SESSION PROP LISTENER! %d\n", (int)error);
            UInt32 inputAvailable = 0;
            UInt32 size = sizeof(inputAvailable);

            // we do not want to allow recording if input is not available
            error = AudioSessionGetProperty(kAudioSessionProperty_AudioInputAvailable, &size, &inputAvailable);
            if (error) printf("ERROR GETTING INPUT AVAILABILITY! %d\n", (int)error);

            // we also need to listen to see if input availability changes
            error = AudioSessionAddPropertyListener(kAudioSessionProperty_AudioInputAvailable, propListener, (__bridge void*)self);
            if (error) printf("ERROR ADDING AUDIO SESSION PROP LISTENER! %d\n", (int)error);

            error = AudioSessionSetActive(true);
            if (error) printf("AudioSessionSetActive (true) failed");
    }
}

So far, this isn't necessary for the functioning of my app, but I am curious as to why it would cause the AQRecorderController instance to release.

rmooney
  • 6,123
  • 3
  • 29
  • 29
  • 1
    Is there a reason you're using AQRecorder instead of AVRecorder? – 8vius Oct 23 '12 at 22:28
  • Yes, I need to being able to read the data from the recording buffer (while recording) and upload it to a server while at the same time filling up a local file to save. Its sort of like streaming and as far as I know, AVRecorder does not allow access to the buffers while recording. Of course, it would be great to avoid the C++ and use only AVAudioRecorder, but I have not found that to be a possibility for my project. – rmooney Oct 26 '12 at 17:42