6

问题:

保存在我的应用程序中录制的视频时,如果视频大小/持续时间太大/太长,我的应用程序会崩溃而没有日志/异常。

我的设置:

在我的应用程序中,我使用 UIImagePickerController 来录制视频。现在我注意到,如果我制作的视频长度很长(例如,使用 UIImagePickerControllerQualityTypeMedium 时为 30 分钟,或者使用 UIImagePickerControllerQualityTypeIFrame1280x720 时超过一分钟),则在保存视频时,应用程序会崩溃。有时有警告,有时没有警告。现在我开始调试并注意到它与内存有关(malloc_error)。

我使用分析器实时检查分配,并注意到当它要保存视频时,分配突然变得非常大(我猜这与视频的临时内存使用有关?)在它最终崩溃之前。这是分析器的屏幕截图: 分配截图

该应用程序必须能够录制最长持续时间为 1 小时的视频(以任何指定的质量)。

我试过的:

  • 设置 picker.videoMaximumDuration 更短/更长
  • 使用探查器/仪器进行调试
  • 检查泄漏
  • 关闭设备上所有打开的应用程序和已删除的应用程序(用于存储清理)以获取更多内存

代码:

- (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
    }    
}

并在选择图像时编写代码:

- (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];  
    }
}

最后,当它被保存时:

+ (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;

}

我在 iOS 5.1.1 中使用 ARC

更新: 我在 malloc_error_break 上放了一个断点,在仪器中我可以看到它是从以下位置调用的:

#   Address Category    Timestamp   Live    Size    Responsible Library Responsible Caller
0   0x10900000  Malloc 473,29 MB    02:08.951.332   •   496283648   Foundation  -[NSData(NSData) initWithContentsOfFile:]

解决方案: 正如lawicko & john.k.doe 所说,我试图将视频从它的路径加载到一个NSData 变量中。这导致整个视频被加载到内存中。而不是这样做,我现在只是移动文件(&重命名)copyItemAtPath

NSError *error = nil;
if (![fileManager copyItemAtPath:path toPath:fullPath error:&error])
    NSLog(@"Error: %@", error);
4

2 回答 2

10

你的问题是这一行:

[NSData dataWithContentsOfPath:self.tempVideoPath]

您显然是想一次将这个文件的内容全部加载到内存中,但 iOS 永远不会让您一次加载那么多。您的saveVideo方法似乎只将文件从临时位置复制到文档目录。如果这是您唯一需要做的事情,请查看 NSFileManager 的copyItemAtPath:toPath:error方法。您可以更改saveVideo方法以将临时文件路径作为参数,而不是数据。

于 2012-06-26T11:26:56.297 回答
2

为什么要费心将媒体流的全部内容从
[[info objectForKey:UIImagePickerControllerMediaURL] path]一个中拉NSData*出来,然后再写出来呢?您的媒体流将是巨大的!

保存时发生这种情况的原因是因为您正在记录数据,它将“磁盘”,然后您将其读入内存以将其写回另一个磁盘文件。

您是否考虑过使用NSFileManager将文件复制[[info objectForKey:UIImagePickerControlMediaURL] path]到您创建的名称(fullPath)?这应该避免将整个事情拉入内存;它应该是“文件到文件”传输。

于 2012-06-26T11:27:53.230 回答