У меня есть пример приложения, которое использует AudioKit для записи звука и отображения формы этих аудиоданных. Это примерное приложение имеет два диспетчера viewControllers, а root vc - пустая страница с кнопкой, которая приведет пользователя к странице записи звука.
По какой-то причине, только на iPhone X (iOS 11.4.1), при записи звука, если я нажму кнопку "Назад" на панели навигации (вверху слева), а затем попытаюсь вернуться и снова записать приложение, произойдет сбой.
В частности, приложение появляется при appendDataFromBufferList: withBufferSize:
когда метод записи appendDataFromBufferList: withBufferSize:
вызывает ExtAudioFileWrite(self.info->extAudioFileRef, bufferSize, bufferList)
. Сообщение об ошибке, которое печатается на консоли, следующее:
testAudioCrash (1312,0x16e203000) malloc: * ** ошибка для объекта 0x109803a00: некорректная контрольная сумма для освобожденного объекта - объект, вероятно, был изменен после освобождения. * ** установить точку останова в malloc_error_break для отладки
Я прошел профилирование зомби, профилирование утечек, перешел через логику и стек, но я не могу понять, почему это происходит.
Ниже я представил код для тестового приложения, а также скриншоты стека и вывод консоли. Любая помощь в выяснении причин этого сбоя будет очень признательна. К сожалению, тот факт, что эта авария также не воспроизводится на 100%, делает ее немного более неясной для меня.
Примечания для кода ниже: В файлах.h нет пользовательского кода, поэтому я этого не делал. Для этого есть xib файлы для каждого контроллера представления с компонентами пользовательского интерфейса. Они довольно просты, поэтому я не предоставлял информацию об этих проблемах, хотя у меня нет проблем с предоставлением какой-либо информации о них, которую каждый запрашивает. Я также могу закрепить проект и поделиться им, если кто-то сочтет это необходимым.
Действия Repro: 1) запустить приложение 2) нажать на запись Кнопка аудио 3) нажать на кнопку записи 4) нажать кнопку на панели навигации
5) повторите шаги 2-4 до тех пор, пока не произойдет сбой
Код AppDelegate.m:
#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:
#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:
#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