Утечка памяти в CMSampleBufferGetImageBuffer

Я получаю UIImage из видеобуфера CMSampleBufferRef каждые N видеокадров, как:

- (void)imageFromVideoBuffer:(void(^)(UIImage* image))completion {
    CMSampleBufferRef sampleBuffer = _myLastSampleBuffer;
    if (sampleBuffer != nil) {
        CFRetain(sampleBuffer);
        CIImage *ciImage = [CIImage imageWithCVPixelBuffer:CMSampleBufferGetImageBuffer(sampleBuffer)];
        _lastAppendedVideoBuffer.sampleBuffer = nil;
        if (_context == nil) {
            _context = [CIContext contextWithOptions:nil];
        }
        CVPixelBufferRef buffer = CMSampleBufferGetImageBuffer(sampleBuffer);
        CGImageRef cgImage = [_context createCGImage:ciImage fromRect:
                              CGRectMake(0, 0, CVPixelBufferGetWidth(buffer), CVPixelBufferGetHeight(buffer))];
        __block UIImage *image = [UIImage imageWithCGImage:cgImage];

        CGImageRelease(cgImage);
        CFRelease(sampleBuffer);

        if(completion) completion(image);

        return;
    }
    if(completion) completion(nil);
}

XCode и Instruments обнаруживают утечку памяти, но я не могу от нее избавиться. Я выпускаю CGImageRef и CMSampleBufferRef как обычно:

CGImageRelease(cgImage);
CFRelease(sampleBuffer);

[ОБНОВЛЕНИЕ] Я положил в AVCapture выходной обратный вызов, чтобы получить sampleBuffer,

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
    if (captureOutput == _videoOutput) {
        _lastVideoBuffer.sampleBuffer = sampleBuffer;
        id<CIImageRenderer> imageRenderer = _CIImageRenderer;

        dispatch_async(dispatch_get_main_queue(), ^{
            @autoreleasepool {
                CIImage *ciImage = nil;
                ciImage = [CIImage imageWithCVPixelBuffer:CMSampleBufferGetImageBuffer(sampleBuffer)];
                if(_context==nil) {
                    _context = [CIContext contextWithOptions:nil];
                }
                CGImageRef processedCGImage = [_context createCGImage:ciImage
                                                             fromRect:[ciImage extent]];
                //UIImage *image=[UIImage imageWithCGImage:processedCGImage];
                CGImageRelease(processedCGImage);
                NSLog(@"Captured image %@", ciImage);
            }
        });

Код, который течет, является createCGImage:ciImage:

CGImageRef processedCGImage = [_context createCGImage:ciImage
                                                             fromRect:[ciImage extent]];

даже имея autoreleasepool, CGImageRelease из CGImage ссылка и CIContext как свойство экземпляра.

Похоже, это та же проблема, что и здесь: не удается сохранить CIImage в файл на iOS без утечек памяти.

[ОБНОВЛЕНИЕ] Утечка, кажется, из-за ошибки. Проблема хорошо описана в " Утечка памяти" на CIContext createCGImage в iOS 9?

Пример проекта показывает, как воспроизвести эту утечку: http://www.osamu.co.jp/DataArea/VideoCameraTest.zip

Последние комментарии уверяют, что

Похоже, они исправили это в 9.1b3. Если кому-то нужен обходной путь, который работает на iOS 9.0.x, я смог заставить его работать с этим:

в тестовом коде (в данном случае Swift):

[self.stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler: ^(CMSampleBufferRef imageSampleBuffer, NSError *error)  
    {  
        if (error) return;  

        __block NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"ipdf_pic_%i.jpeg",(int)[NSDate date].timeIntervalSince1970]];  

        NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];  
        dispatch_async(dispatch_get_main_queue(), ^  
        {  

            @autoreleasepool  
            {  
                CIImage *enhancedImage = [CIImage imageWithData:imageData];  

                if (!enhancedImage) return;  

                static CIContext *ctx = nil; if (!ctx) ctx = [CIContext contextWithOptions:nil];  

                CGImageRef imageRef = [ctx createCGImage:enhancedImage fromRect:enhancedImage.extent format:kCIFormatBGRA8 colorSpace:nil];  

                UIImage *image = [UIImage imageWithCGImage:imageRef scale:1.0 orientation:UIImageOrientationRight];  

                [[NSFileManager defaultManager] createFileAtPath:filePath contents:UIImageJPEGRepresentation(image, 0.8) attributes:nil];  

                CGImageRelease(imageRef);  
            }  
        });  
    }]; 

и обходной путь для iOS9.0 должен быть

extension CIContext {  
    func createCGImage_(image:CIImage, fromRect:CGRect) -> CGImage {  
        let width = Int(fromRect.width)  
        let height = Int(fromRect.height)  

        let rawData =  UnsafeMutablePointer<UInt8>.alloc(width * height * 4)  
        render(image, toBitmap: rawData, rowBytes: width * 4, bounds: fromRect, format: kCIFormatRGBA8, colorSpace: CGColorSpaceCreateDeviceRGB())  
        let dataProvider = CGDataProviderCreateWithData(nil, rawData, height * width * 4) {info, data, size in UnsafeMutablePointer<UInt8>(data).dealloc(size)}  
        return CGImageCreate(width, height, 8, 32, width * 4, CGColorSpaceCreateDeviceRGB(), CGBitmapInfo(rawValue: CGImageAlphaInfo.PremultipliedLast.rawValue), dataProvider, nil, false, .RenderingIntentDefault)!  
    }  
}  

2 ответа

У нас возникла похожая проблема в приложении, которое мы создали, где мы обрабатываем каждый кадр для ключевых точек функций с помощью OpenCV и отправляем кадр каждые пару секунд. Через некоторое время работы мы получим довольно много сообщений о давлении памяти.

Нам удалось исправить это, запустив наш код обработки в его собственном пуле автоматического выпуска, например так (jpegDataFromSampleBufferAndCrop делает что-то похожее на то, что вы делаете, с добавлением обрезки):

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
        @autoreleasepool {

            if ([self.lastFrameSentAt timeIntervalSinceNow] < -kContinuousRateInSeconds) {

                NSData *imageData = [self jpegDataFromSampleBufferAndCrop:sampleBuffer];

                if (imageData) {
                    [self processImageData:imageData];
                }

                self.lastFrameSentAt = [NSDate date];

                imageData = nil;
            }
        }
    }
}

Я могу подтвердить, что эта утечка памяти все еще существует на iOS 9.2. (Я также разместил на форуме разработчиков Apple.)

Я получаю ту же утечку памяти на iOS 9.2. Я протестировал удаление EAGLContext с помощью MetalKit и MLKDevice. Я тестировал с использованием различных методов CIContext, таких как drawImage, createCGImage и render, но, похоже, ничего не работает.

Совершенно очевидно, что это ошибка для iOS 9. Попробуйте сами, загрузив пример приложения от Apple (см. Ниже), а затем запустите тот же проект на устройстве с iOS 8.4, а затем на устройстве с iOS 9.2. и обратите внимание на индикатор памяти в Xcode.

Загрузить https://developer.apple.com/library/ios/samplecode/AVBasicVideoOutput/Introduction/Intro.html

Добавьте это к APLEAGLView.h:20

@property (strong, nonatomic) CIContext* ciContext;

Замените APLEAGLView.m:118 этим

[EAGLContext setCurrentContext:_context];
 _ciContext = [CIContext contextWithEAGLContext:_context];

И, наконец, заменить APLEAGLView.m:341-343 с этим

glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);  

    @autoreleasepool  
    {  
        CIImage* sourceImage = [CIImage imageWithCVPixelBuffer:pixelBuffer];  
        CIFilter* filter = [CIFilter filterWithName:@"CIGaussianBlur" keysAndValues:kCIInputImageKey, sourceImage, nil];  
        CIImage* filteredImage = filter.outputImage;  

        [_ciContext render:filteredImage toCVPixelBuffer:pixelBuffer];  
    }  

glBindRenderbuffer(GL_RENDERBUFFER, _colorBufferHandle);  
Другие вопросы по тегам