当保存录制时间过长的视频时,应用程序崩溃。

6
问题: 当我的应用程序录制的视频大小/时长过大时,如果视频长度过长(例如使用UIImagePickerControllerQualityTypeMedium录制了30分钟或更多,或者使用UIImagePickerControllerQualityTypeIFrame1280x720录制了超过一分钟),保存视频时应用程序会崩溃,没有日志/异常。 我的设置: 在我的应用中,我使用UIImagePickerController来录制视频。现在我注意到,如果我让我的视频长度非常长(例如使用UIImagePickerControllerQualityTypeMedium录制了30分钟或更长时间,或使用UIImagePickerControllerQualityTypeIFrame1280x720录制了超过一分钟),那么在保存视频时应用程序会崩溃。有时带有警告,有时没有。现在我开始调试并注意到它与内存(malloc_error)有关。
我使用探查器实时检查分配情况,并注意到当即将保存视频时,分配突然变得非常大(我猜与视频的临时内存使用有关?)然后最终崩溃。这是来自探查器的屏幕截图: Allocations screenshot 该应用程序必须能够记录具有最大持续时间为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上设置了断点,在instruments中可以看到它被调用于:

#   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);

你能找出哪种类型的对象具有最大的活动字节吗? - nhahtdh
@nhahtdh,它说它被调用了:# 地址 类别 时间戳 现场 大小 负责的库 负责的调用者 0 0x10900000 Malloc 473,29 MB 02:08.951.332 • 496283648 Foundation -[NSData(NSData) initWithContentsOfFile:] - Thermometer
1
[LocalVideoStorage saveVideo:[NSData dataWithContentsOfPath:self.tempVideoPath]...你正在将整个临时文件读入NSData对象,然后再将其保存到磁盘上。难道你不能直接将临时文件复制到你选择的永久存储文件夹中吗? - Rog
2个回答

10

您遇到的问题是这一行代码:

[NSData dataWithContentsOfPath:self.tempVideoPath]

你显然在尝试一次性将该文件的内容全部加载到内存中,但是iOS不允许你这样做。你的 saveVideo 方法似乎只是将文件从临时位置复制到文档目录。如果这是你需要做的唯一操作,那么可以查看 NSFileManager 的 copyItemAtPath:toPath:error 方法。你可以将 saveVideo 方法更改为接受临时文件路径作为参数,而不是数据。


1
非常感谢!我只需要更改的是[fileManager createFileAtPath:fullPath contents:video attributes:nil];NSError *error = nil; if (![fileManager copyItemAtPath:path toPath:fullPath error:&error]) NSLog(@"Error: %@", error); - Thermometer

2

为什么要从[[info objectForKey:UIImagePickerControllerMediaURL] path]中提取整个媒体流的内容,并将其转换为NSData*,然后再写回去呢?你的媒体流会非常大!

这是因为在保存时正在记录数据,它正在“磁盘”上,然后您将其读入内存以将其写回到另一个磁盘文件中。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接