【问题标题】:OS X objective-C app uses excessive memoryOS X Objective-C 应用程序使用过多的内存
【发布时间】:2014-04-27 16:31:55
【问题描述】:

我正在编写一个 OS X 应用程序,它将使用一系列图像创建一个视频。它是使用此处的代码开发的:Make movie file with picture Array and song file, using AVAsset,但不包括音频部分。

代码运行并创建一个 mpg 文件。

问题是内存压力。它似乎没有释放任何内存。使用 XCode Instruments 我发现最大的罪魁祸首是:

CVPixelBufferCreate
[image TIFFRepresentation];
CGImageSourceCreateWithData
CGImageSourceCreateImageAtIndex

我尝试添加代码以发布,但 ARC 应该已经这样做了。 最终 OS X 将挂起或崩溃。

不知道如何处理内存问题。代码中没有 malloc。 我愿意接受建议。似乎许多其他人都使用了相同的代码。

这是基于上面链接的代码:

- (void)ProcessImagesToVideoFile:(NSError **)error_p size:(NSSize)size videoFilePath:(NSString *)videoFilePath jpegs:(NSMutableArray *)jpegs fileLocation:(NSString *)fileLocation
{

    AVAssetWriter *videoWriter = [[AVAssetWriter alloc] initWithURL:
                              [NSURL fileURLWithPath:videoFilePath]
                                                       fileType:AVFileTypeMPEG4
                                                          error:&(*error_p)];
    NSParameterAssert(videoWriter);

    NSDictionary *videoSettings = [NSDictionary dictionaryWithObjectsAndKeys:
                               AVVideoCodecH264, AVVideoCodecKey,
                               [NSNumber numberWithInt:size.width], AVVideoWidthKey,
                               [NSNumber numberWithInt:size.height], AVVideoHeightKey,
                               nil];
     AVAssetWriterInput* videoWriterInput = [AVAssetWriterInput
                                        assetWriterInputWithMediaType:AVMediaTypeVideo
                                        outputSettings:videoSettings];


        AVAssetWriterInputPixelBufferAdaptor *adaptor = [AVAssetWriterInputPixelBufferAdaptor
                                                     assetWriterInputPixelBufferAdaptorWithAssetWriterInput:videoWriterInput
                                                 sourcePixelBufferAttributes:nil];



    NSParameterAssert(videoWriterInput);
    NSParameterAssert([videoWriter canAddInput:videoWriterInput]);

    videoWriterInput.expectsMediaDataInRealTime = YES;

    [videoWriter addInput:videoWriterInput];
    //Start a session:
    [videoWriter startWriting];
    [videoWriter startSessionAtSourceTime:kCMTimeZero];


    CVPixelBufferRef buffer = NULL;

    //Write all picture array in movie file.

    int frameCount = 0;

    for(int i = 0; i<[jpegs count]; i++)
    {
        NSString *filePath = [NSString stringWithFormat:@"%@%@", fileLocation, [jpegs objectAtIndex:i]];
        NSImage *jpegImage = [[NSImage alloc ]initWithContentsOfFile:filePath];
        CMTime frameTime = CMTimeMake(frameCount,(int32_t) 24);

        BOOL append_ok = NO;
        int j = 0;
        while (!append_ok && j < 30)
        {
            if (adaptor.assetWriterInput.readyForMoreMediaData)
            {
                if ((frameCount % 25) == 0)
                {
                    NSLog(@"appending %d to %@ attemp %d\n", frameCount, videoFilePath, j);
                }


                buffer = [self pixelBufferFromCGImage:jpegImage  andSize:size];
                append_ok = [adaptor appendPixelBuffer:buffer withPresentationTime:frameTime];
                if (append_ok == NO) //failes on 3GS, but works on iphone 4
                {
                    NSLog(@"failed to append buffer");
                    NSLog(@"The error is %@", [videoWriter error]);
                }
                //CVPixelBufferPoolRef bufferPool = adaptor.pixelBufferPool;
                //NSParameterAssert(bufferPool != NULL);

                if(buffer)
                {
                    CVPixelBufferRelease(buffer);
                    //CVBufferRelease(buffer);
                }
            }
            else
            {
                printf("adaptor not ready %d, %d\n", frameCount, j);
                [NSThread sleepForTimeInterval:0.1];
            }
            j++;
        }

        if (!append_ok)
        {
            printf("error appending image %d times %d\n", frameCount, j);
        }

        frameCount++;
            //CVBufferRelease(buffer);
        jpegImage = nil;
        buffer = nil;
    }

    //Finish writing picture:
    [videoWriterInput markAsFinished];
    [videoWriter finishWritingWithCompletionHandler:^(){
    NSLog (@"finished writing");

    }];
}

- (CVPixelBufferRef) pixelBufferFromCGImage: (NSImage *) image andSize:(CGSize) size
{
    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                         [NSNumber numberWithBool:YES],     kCVPixelBufferCGImageCompatibilityKey,
                         [NSNumber numberWithBool:YES],     kCVPixelBufferCGBitmapContextCompatibilityKey,
                         nil];
    CVPixelBufferRef pxbuffer = NULL;

    CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault,
                                      size.width,
                                      size.height,
                                      kCVPixelFormatType_32ARGB,
                                      (__bridge CFDictionaryRef) options,
                                      &pxbuffer);

    NSParameterAssert(status == kCVReturnSuccess && pxbuffer != NULL);

    CVPixelBufferLockBaseAddress(pxbuffer, 0);
    void *pxdata = CVPixelBufferGetBaseAddress(pxbuffer);
    NSParameterAssert(pxdata != NULL);

    CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate(pxdata, size.width, size.height,
                                             8, 4*size.width, rgbColorSpace,
                                             kCGImageAlphaPremultipliedFirst);
    NSParameterAssert(context);
    CGContextConcatCTM(context, CGAffineTransformMakeRotation(0));
    CGImageRef imageRef = [self nsImageToCGImageRef:image];
    CGRect imageRect = CGRectMake(0, 0, CGImageGetWidth(imageRef),     CGImageGetHeight(imageRef));
    CGContextDrawImage(context, imageRect, imageRef);


    CGColorSpaceRelease(rgbColorSpace);
    CGContextRelease(context);

    CVPixelBufferUnlockBaseAddress(pxbuffer, 0);
    imageRef = nil;
    context = nil;
    rgbColorSpace = nil;
    return pxbuffer;
}

- (CGImageRef)nsImageToCGImageRef:(NSImage*)image;
{
    NSData * imageData = [image TIFFRepresentation];// memory hog
    CGImageRef imageRef;
    if(!imageData) return nil;
    CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL);
    imageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);

    imageData = nil;
    imageSource = nil;
    return imageRef;
}

【问题讨论】:

  • 在哪里释放像素缓冲区?
  • 好问题 - 它在帖子中引用的代码中发布。 CVBufferRelease(缓冲区);调用 pixelBufferFromCGImage
  • @MikeWeber 我在您的代码中既找不到CVBufferRelease 也找不到pixelBufferFromCGImage
  • 我在链接中添加了代码的主要部分 - 比期望读者点击链接阅读附加代码更好
  • 你应该期望浏览器在每个链接的站点中搜索...... ;-)

标签: objective-c macos memory


【解决方案1】:

您的代码正在使用 ARC,但您调用的库可能未使用 ARC。他们可能依赖旧的自动释放池系统来释放内存。

你应该读一读它是如何工作的,这是每个 Obj-C 开发人员都需要记住的基本内容,但基本上任何对象都可以添加到当前的对象“池”中,这些对象将在池结束时释放发布。

默认情况下,每次应用进入空闲状态时都会清空主线程上的池。这通常可以正常工作,因为主线程的忙碌时间永远不会超过百分之几秒,而且您无法在这段时间内真正建立太多内存。

当您执行冗长且占用大量内存的操作时,您需要手动设置一个自动释放池,该池最常见的是放在 forwhile 循环中(尽管您实际上可以将它们放在任何您想要的地方,这只是最有用的场景):

for ( ... ) {
  @autoreleasepool {
    // do somestuff
  }
}

此外,ARC 仅适用于 Objective C 代码。它不适用于由 CGColorSpaceCreateDeviceRGB()CVPixelBufferCreate() 等 C 函数创建的对象。确保您手动释放所有这些。

【讨论】:

  • 您的第一段似乎暗示自动释放池在 ARC 下不存在,我认为这不是您的意思。
  • @JoshCaswell 我的理解是 ARC 通常会在使用它们的最后一行代码之后立即释放对象,而不是在创建时自动释放它们?因此,在 ARC 下维护的任何对象都将被释放,而无需进入自动释放池。
  • 你可以让 ARC 管理从没有 ARC 编译的框架返回的对象,只要框架遵循标准的方法命名约定。神奇的 never-enters-autoreleasepool 的东西只有在这两个东西在 ARC 下编译时才会发生(因为 ARC 会在调用堆栈中查找特定调用)。
  • ColorSpace 和 PixelBuffer 都是通过调用 CGColorSpaceRelease 和 CVPixelBufferRelease 释放的。我还将生成视频文件的代码包装在 @autoreleasepool{} 块中,但在代码退出该块后,我没有看到内存被释放。
【解决方案2】:

ARC 仅适用于 retainable object pointers。 ARC 文档将它们定义为

可保留对象指针(或“可保留指针”)是 可保留对象指针类型(“可保留类型”)。有三种 可保留对象指针类型的种类:

  1. 块指针(通过将插入符号 (^) 声明符符号应用于 函数类型)

  2. Objective-C 对象指针(id、Class、NSFoo* 等)

  3. typedefs 标记有 attribute((NSObject)) 其他指针类型, 例如 int* 和 CFStringRef,不受 ARC 的语义和 限制。

您已经在此处明确调用 release

CGContextRelease(context);

您应该对其他对象执行相同的操作。喜欢

CVPixelBufferRelease(pxbuffer);

对于像素缓冲区

【讨论】:

  • @AminNegm-Awad Arc 可以适用于每个可保留对象,但除非您另外指定它不会触及由 C 函数创建的任何内容,包括CGContextCVPixelBuffer。你必须手动释放它们,或者手动告诉 ARC 释放它们。
  • 1. “ARC 仅适用于 NSObject 的子类。”在很多方面都是错误的。 2.创建的地方(C函数或Objective-C方法)对此没有任何影响。 3. 对于每个可保留对象,它以文档化的方式工作,没有任何例外。这与 C 无关。 4. 你把完全不同的东西混为一谈。 5. 请阅读 llvm.org 上的文档。
  • @AminNegm-Awad:ARC 不会自动管理 CoreFoundation 样式的对象,尽管它们是可保留的。
  • 我知道,CF 对象没有被处理。他说:“ARC 只适用于 NSObject 的子类。”这是错误的。你说:“尽管它们是可以保留的”。这是错误的。也适合您:请阅读 llvm.org 上的文档。 “可保留对象指针”有一个定义。
  • ARC 适用于每个可保留的 Objective-C 对象。
猜你喜欢
  • 1970-01-01
  • 2011-10-09
  • 2012-05-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-03
  • 2014-12-26
  • 1970-01-01
相关资源
最近更新 更多