Crash when EZRecorder calls ExtAudioFileWrite on iPhone X
Asked Answered
M

0

14

I have a sample app that uses AudioKit to record audio and display a waveform of that audio data. This sample app has two viewControllers with the root vc being a blank page with a button that will take the user to the audio recording page.

For some reason, only on iPhone X (iOS 11.4.1), while recording audio, if I hit the back button on the navigation bar (top left) and then try to go and record again the app will crash.

Specifically the app appears to crash when the recorder's method appendDataFromBufferList: withBufferSize: calls ExtAudioFileWrite(self.info->extAudioFileRef, bufferSize, bufferList). The error message that is printed in the console is:

testAudioCrash(1312,0x16e203000) malloc: * **error for object 0x109803a00: incorrect checksum for freed object - object was probably modified after being freed. * **set a breakpoint in malloc_error_break to debug

I've gone through zombie profiling, leak profiling, stepped through the logic and the stack but I can't seem to figure out why this is happening.

Below i've provided the code for the test app as well as screenshots of the stack and the console output. Any help with figuring out why this is crashing would be greatly appreciated. Unfortunately the fact that this crash is also not 100% reproducible makes it a little more obscure to me.

Notes for code below: There is no custom code in the .h files so I have not provided that. There are xib files for each view controller with the UI components for this. They're pretty simple so I have not provided information on those as well though I have no problem in providing any information on them, that anyone requests. I can also zip up the project and share that if anyone feels it's necessary.

Repro steps: 1) launch app 2) tap on record Audio button 3) tap on record button 4) hit back button on navigation bar

5) repeat steps 2-4 until crash happens

AppDelegate.m code:

#import "AppDelegate.h"
#import "testViewController.h"

@interface AppDelegate ()
@end

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.

    testViewController* rootVC = [[testViewController alloc] initWithNibName: @"testViewController" bundle: NSBundle.mainBundle];
    UINavigationController* nav = [[UINavigationController alloc] initWithRootViewController: rootVC];
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.rootViewController = nav;
    [self.window makeKeyAndVisible];
    return YES;
}


- (void)applicationWillResignActive:(UIApplication *)application {
    // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
    // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}


- (void)applicationDidEnterBackground:(UIApplication *)application {
    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}


- (void)applicationWillEnterForeground:(UIApplication *)application {
    // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}


- (void)applicationDidBecomeActive:(UIApplication *)application {
    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}


- (void)applicationWillTerminate:(UIApplication *)application {
    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}


@end

testViewController.m code:

#import "testViewController.h"
#import "testSecondViewController.h"

@interface testViewController ()

@end

@implementation testViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (IBAction)AudioRecording:(id)sender
{
    testSecondViewController* sVC = [[testSecondViewController alloc] initWithNibName: @"testSecondViewController" bundle: NSBundle.mainBundle];
    [self.navigationController pushViewController: sVC animated: YES];
}

@end

testSecondViewController.m code:

#import "testSecondViewController.h"
@import AudioKit;
@import AudioKitUI;

@interface testSecondViewController () <EZMicrophoneDelegate, EZRecorderDelegate>
@property (nonatomic, strong) EZRecorder* recorder;
@property (nonatomic, strong) EZMicrophone* mic;
@property (nonatomic, strong) EZAudioPlayer* player;
@property (strong, nonatomic) IBOutlet EZAudioPlot *audioPlot;
@property (nonatomic, strong) NSURL *finishedRecordingURL;
@property (atomic, assign) BOOL isRecording;

@end

@implementation testSecondViewController

- (void)dealloc
{
    if(_isRecording) [self pauseRecording: _mic];
    if(_recorder) [self finalizeAudioFile: _recorder];
    _recorder.delegate = nil;
    _mic.delegate = nil;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    [EZAudioUtilities setShouldExitOnCheckResultFail: NO];

    [self setupUI];
    [self setupConfig];
    [self audioKitSetup];
}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

#pragma mark UI Methods
-(void)setupUI
{
    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Cancel" style: UIBarButtonItemStylePlain target: nil action:@selector(cancelButtonClicked)];
    [self configureWaveFormViewForAudioInput];
}

-(void)setupConfig
{
    [self initializeMic];
    [self initializeRecorder];
}

-(void)initializeMic
{
    self.mic = [[EZMicrophone alloc] initWithMicrophoneDelegate: self];
    self.isRecording = NO;
}

-(void)initializeRecorder
{
    NSURL *fileUrl = [self testFilePathURL];
    self.finishedRecordingURL = fileUrl;

    self.recorder = [[EZRecorder alloc] initWithURL: fileUrl clientFormat: [self.mic audioStreamBasicDescription] fileType: EZRecorderFileTypeM4A delegate: self];
}

#pragma mark - Utils
- (NSArray *)applicationDocuments
{
  return NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
}


- (NSString *)applicationDocumentsDirectory
{
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
    return basePath;
}

- (NSURL *)testFilePathURL
{
    self.finishedRecordingURL = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/%@",
                               [self applicationDocumentsDirectory],
                               @"test2.m4a"]];

    if (self.finishedRecordingURL && [[NSFileManager defaultManager] fileExistsAtPath:self.finishedRecordingURL.path])
    {
        NSError *error;
        [[NSFileManager defaultManager] removeItemAtURL:self.finishedRecordingURL error:&error];
        if(error){
            printf("%s", error.description);
        }
    }

    return self.finishedRecordingURL;
}

#pragma mark AudioKit Util methods
- (void) audioKitSetup
{
    [AKSettings setDefaultToSpeaker: YES];
    [AKSettings setAudioInputEnabled: YES];
    [AKSettings setPlaybackWhileMuted: YES];
    [AKSettings setSampleRate: 44100];
    [AKSettings setChannelCount: 1];
}

- (void) configureWaveFormViewForAudioInput
{
//    self.audioPlot.gain = 6;
//    self.audioPlot.color = [UIColor blueColor];
    self.audioPlot.plotType = EZPlotTypeRolling;
//    self.audioPlot.shouldFill = YES;
//    self.audioPlot.shouldMirror = YES;
    [self.view addSubview: self.audioPlot];
    self.audioPlot.clipsToBounds = YES;
}

- (IBAction)startRecording:(id)sender
{
    if (!self.mic)
    {
        self.mic = [EZMicrophone microphoneWithDelegate: self];
    }

    if (!self.recorder)
    {
        if (self.finishedRecordingURL && [[NSFileManager defaultManager] fileExistsAtPath:self.finishedRecordingURL.path])
        {
            self.recorder = [EZRecorder recorderWithURL: self.finishedRecordingURL clientFormat: [self.mic audioStreamBasicDescription] fileType: EZRecorderFileTypeM4A delegate: self];
        }
        else
        {
            self.recorder = [EZRecorder recorderWithURL: [self testFilePathURL] clientFormat: [self.mic audioStreamBasicDescription] fileType: EZRecorderFileTypeM4A delegate: self];
            self.finishedRecordingURL = self.recorder.url;
        }
    }

    [self.mic startFetchingAudio];
    self.isRecording = YES;
}

- (IBAction)pauseRecording:(id)sender
{
    [self.mic stopFetchingAudio];
    self.isRecording = NO;
}

- (void) finalizeAudioFile: (EZRecorder*) recorder
{
    if (self.isRecording)
    {
        [self.mic stopFetchingAudio];
    }

    [recorder closeAudioFile];
}

- (IBAction)cancelButtonClicked:(id)sender
{
        if(self.isRecording)
    {
        [self pauseRecording: self.mic];
    }

    UIAlertController *alert = [UIAlertController alertControllerWithTitle: @"Delete recording?" message:@"Would you like to delete your audio recording and stop recording?" preferredStyle: UIAlertControllerStyleAlert];

        UIAlertAction* yesButton = [UIAlertAction
                                actionWithTitle:@"Discard"
                                style:UIAlertActionStyleDefault
                                handler:^(UIAlertAction * action) {

                                    [self finalizeAudioFile: self.recorder];

                                    NSError *error;
                                    [[NSFileManager defaultManager] removeItemAtURL:self.finishedRecordingURL error:&error];
                                    if(error){
                                        printf("%s", error.description);
                                    }

                                     [self dismissViewControllerAnimated:YES completion:NULL];
                                }];

        UIAlertAction* noButton = [UIAlertAction
                               actionWithTitle:@"Cancel"
                               style:UIAlertActionStyleDefault
                               handler:^(UIAlertAction * action) {
                                   [alert dismissViewControllerAnimated:YES completion: nil];
                               }];

                                   [alert addAction:yesButton];
    [alert addAction:noButton];

    [self presentViewController:alert animated:YES completion:nil];
}

#pragma mark - EZMicrophone Delegate methods
- (void)  microphone:(EZMicrophone *)microphone
    hasAudioReceived:(float **)buffer
      withBufferSize:(UInt32)bufferSize
withNumberOfChannels:(UInt32)numberOfChannels
{
    __weak typeof (self) weakling = self;
    dispatch_async(dispatch_get_main_queue(), ^{
        [weakling.audioPlot updateBuffer:buffer[0]
                          withBufferSize:bufferSize];
    });
}

- (void)  microphone:(EZMicrophone *)microphone
       hasBufferList:(AudioBufferList *)bufferList
      withBufferSize:(UInt32)bufferSize
withNumberOfChannels:(UInt32)numberOfChannels
{
    if (self.isRecording)
    {
        [self.recorder appendDataFromBufferList:bufferList
                                 withBufferSize:bufferSize];
    }
}

- (void)microphone:(EZMicrophone *)microphone changedPlayingState:(BOOL)isPlaying
{
    self.isRecording = isPlaying;
}

@end

images: enter image description here

enter image description here

Mingmingche answered 25/7, 2018 at 21:35 Comment(1)
Small Update: I believe the issue is only happening on 6 core devices (iPhone 8/Plus/X). While the repro of this crash is inconsistent, I've only been able to reproduce this on an iPhone 8 and an iPhone X after repeated attempts.Mingmingche

© 2022 - 2024 — McMap. All rights reserved.