The problem:
When saving a video that is recorded in my app, if the video size/duration is too big/long, my app crashes without a log/exception.
My Setup:
In my App I use a UIImagePickerController to record videos. Now I have noticed that if I make my videos very long in length (for example 30minutes with UIImagePickerControllerQualityTypeMedium, or more than a minute with UIImagePickerControllerQualityTypeIFrame1280x720), when saving the video, the app crashes. Sometimes with and sometimes without a warning. Now I started to debug and noticed it had something to do with memory (malloc_error).
I used the profiler to check allocations live, and noticed that when it was going to save the video, the allocation suddenly became very big (I guess something to do with temporary memory usage for the video?) before it ultimately crashed. Here is a screenshot from the profiler:
The app must be able to record video with a maximum duration of 1 hour (in any quality specified).
What I have tried:
Code:
- (void)openCamera:(id)sender context:(NSManagedObjectContext*)context {
self.context = context;
//Set self as delegate (UIImagePickerControllerDelegate)
[self.picker setDelegate:self];
//If the device has a camera
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
self.picker.sourceType = UIImagePickerControllerSourceTypeCamera;
self.picker.allowsEditing = YES;
self.picker.videoQuality = [Settings videoQualitySetting];
//If the camera can record video
NSString *desired = (NSString *)kUTTypeMovie;
if ([[UIImagePickerController availableMediaTypesForSourceType:self.picker.sourceType] containsObject:desired]) {
//Let the user record video
self.picker.mediaTypes = [NSArray arrayWithObject:desired];
self.picker.videoMaximumDuration = MaxDuration;
}
else {
NSLog(@"Can't take videos with this device"); //debug
}
//Present the picker fullscreen/in popover
if ([Settings shouldDisplayFullScreenCamera]){
[self presentModalViewController:self.picker animated:YES];
[self.masterPopoverController dismissPopoverAnimated:YES];
}
else {
if (!_popover){
_popover = [[UIPopoverController alloc] initWithContentViewController:self.picker];
}
[_popover presentPopoverFromBarButtonItem:sender permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
}
else {
NSLog(@"Does not have a camera"); //debug
}
}
And code when the image is picked:
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
NSString *mediaType = [info objectForKey: UIImagePickerControllerMediaType];
// Save the video, and close the overlay
if (CFStringCompare ((__bridge CFStringRef) mediaType, kUTTypeMovie, 0)
== kCFCompareEqualTo) {
self.tempVideoPath = [[info objectForKey:
UIImagePickerControllerMediaURL] path];
[LocalVideoStorage saveVideo:[NSData dataWithContentsOfPath:self.tempVideoPath name:self.videoName];
[_picker dismissModalViewControllerAnimated: YES];
[[_picker parentViewController] dismissModalViewControllerAnimated:YES];
[_popover dismissPopoverAnimated:YES];
}
}
And finally, when it is saved:
+ (NSString*)saveVideo:(NSData*)video:(NSString*)videoName {
NSFileManager *fileManager = [NSFileManager defaultManager];//create instance of NSFileManager
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); //create an array and store result of our search for the documents directory in it
NSString *documentsDirectory = [paths objectAtIndex:0]; //create NSString object, that holds our exact path to the documents directory
NSString *fullPath = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.MOV", videoName]]; //add our video to the path
[fileManager createFileAtPath:fullPath contents:video attributes:nil]; //finally save the path (video)
NSLog(@"Video saved!");
return fullPath;
}
I am using ARC with iOS 5.1.1
Update: I have put a breakpoint on malloc_error_break, and in instruments I can see it is called from:
# Address Category Timestamp Live Size Responsible Library Responsible Caller
0 0x10900000 Malloc 473,29 MB 02:08.951.332 • 496283648 Foundation -[NSData(NSData) initWithContentsOfFile:]
Solution: As lawicko & john.k.doe said, I tried to load the video from it's path into an NSData variable. This caused the whole video to be loaded into memory. Instead of doing that, I now just move the file (& rename) copyItemAtPath
NSError *error = nil;
if (![fileManager copyItemAtPath:path toPath:fullPath error:&error])
NSLog(@"Error: %@", error);
Your problem is this line:
[NSData dataWithContentsOfPath:self.tempVideoPath]
You are clearly trying to load the contents of this file to memory all at once, but iOS will never let you load that much at one time. Your saveVideo
method seems to only copy the file from temporary location to the documents directory. If this is the only thing that you need to do there, then take a look at copyItemAtPath:toPath:error method of NSFileManager. Your could change the saveVideo
method to take the temporary file path as a parameter, rather than the data.
why bother pulling the entire contents of the media stream from[[info objectForKey:UIImagePickerControllerMediaURL] path]
into an NSData*
and then writing them back out? your media stream is going to be huge!
the reason this is happening at save is because you are recording the data, it is going to "disk", and then you read it into memory to write it back out to another disk file.
have you considered using NSFileManager
to copy the file from [[info objectForKey:UIImagePickerControlMediaURL] path]
to the name you create (fullPath) ? this should avoid pulling the whole thing into memory; it should be a "file-to-file" transfer.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With