Утечка памяти iOS при создании видео из изображений с помощью AVAssetWriter - PullRequest
2 голосов
/ 13 января 2012


Я новичок в разработке под iOS и в стеке, поэтому, пожалуйста, потерпите меня, если мой код не выглядит так хорошо, как должен.

Я настроил тестовое приложение, использующее ARC и AVAssetWriter для создания видео с изображениями, которые находятся в моем комплекте приложений. Все работает, как ожидалось, и видео создается правильно, но когда я профилирую свое приложение с помощью инструментов, у меня возникают утечки памяти, которые я не знаю, как исправить, поскольку я не вижу ничего в подробном представлении, которое связано с моим кодом (все просочившиеся объекты - Malloc, а Responsible Library - VideoToolbox).

Вот метод, который я вызываю, чтобы начать писать видео в классе моего контроллера представления:

- (void)writeVideo
{
    // Set the frameDuration ivar (50/600 = 1 sec / 12 number of frames)
    frameDuration = CMTimeMake(50, 600);
    nextPresentationTimeStamp = kCMTimeZero;

    [self deleteTempVideo];

    NSError *error = nil;
    AVAssetWriter *writer = [[AVAssetWriter alloc] initWithURL:[NSURL fileURLWithPath:self.videoPath] fileType:AVFileTypeQuickTimeMovie error:&error];
    if (!error) {
        // Define video settings to be passed to the AVAssetWriterInput instance
        NSDictionary *videoSettings = [NSDictionary dictionaryWithObjectsAndKeys:
                                       AVVideoCodecH264, AVVideoCodecKey, 
                                       [NSNumber numberWithInt:640],AVVideoWidthKey, 
                                       [NSNumber numberWithInt:480], AVVideoHeightKey, nil];
        // Instanciate the AVAssetWriterInput
        AVAssetWriterInput *writerInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:videoSettings];
        // Instanciate the AVAssetWriterInputPixelBufferAdaptor to be connected to the writer input
        AVAssetWriterInputPixelBufferAdaptor *pixelBufferAdaptor = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:writerInput sourcePixelBufferAttributes:nil];
        // Add the writer input to the writer and begin writing
        [writer addInput:writerInput];
        [writer startWriting];
        [writer startSessionAtSourceTime:nextPresentationTimeStamp];
        //
        dispatch_queue_t mediaDataRequestQueue = dispatch_queue_create("Media data request queue", NULL);
        [writerInput requestMediaDataWhenReadyOnQueue:mediaDataRequestQueue usingBlock:^{
            while (writerInput.isReadyForMoreMediaData) {
                CVPixelBufferRef nextBuffer = [self fetchNextPixelBuffer];
                if (nextBuffer) {
                    [pixelBufferAdaptor appendPixelBuffer:nextBuffer withPresentationTime:nextPresentationTimeStamp];
                    nextPresentationTimeStamp = CMTimeAdd(nextPresentationTimeStamp, frameDuration);
                    CVPixelBufferRelease(nextBuffer);                    
                    dispatch_async(dispatch_get_main_queue(), ^{
                        NSUInteger totalFrames = [self.imagesNames count]; 
                        float progress = 1.0 * (totalFrames - [self.imageNamesCopy count]) / totalFrames;
                        [self.progressBar setProgress:progress animated:YES];
                    });
                } else {
                    [writerInput markAsFinished];
                    [writer finishWriting];
                    [self loadVideo];
                    dispatch_release(mediaDataRequestQueue);
                    break;
                }
            }
        }];
    }
}


И вот метод, который я использую для выборки буферов пикселей для добавления к адаптеру буферов пикселей, созданного в предыдущем методе:

// Consume the imageNamesCopy mutable array and return a CVPixelBufferRef relative to the last object of the array
- (CVPixelBufferRef)fetchNextPixelBuffer
{
    NSString *imageName = [self.imageNamesCopy lastObject];
    if (imageName) [self.imageNamesCopy removeLastObject];
    // Create an UIImage instance
    UIImage *image = [UIImage imageNamed:imageName];
    CGImageRef imageRef = image.CGImage;    

    CVPixelBufferRef buffer = NULL;
    size_t width = CGImageGetWidth(imageRef);
    size_t height = CGImageGetHeight(imageRef);
    // Pixel buffer options
    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                             [NSNumber numberWithBool:YES], kCVPixelBufferCGImageCompatibilityKey, 
                             [NSNumber numberWithBool:YES], kCVPixelBufferCGBitmapContextCompatibilityKey, nil];
    // Create the pixel buffer
    CVReturn result = CVPixelBufferCreate(NULL, width, height, kCVPixelFormatType_32ARGB, (__bridge CFDictionaryRef) options, &buffer);
    if (result == kCVReturnSuccess && buffer) {
        CVPixelBufferLockBaseAddress(buffer, 0);
        void *bufferPointer = CVPixelBufferGetBaseAddress(buffer);
        // Define the color space
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        // Create the bitmap context to draw the image
        CGContextRef context = CGBitmapContextCreate(bufferPointer, width, height, 8, 4 * width, colorSpace, kCGImageAlphaNoneSkipFirst);
        CGColorSpaceRelease(colorSpace);
        if (context) {
            CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
            CGContextRelease(context);
        }
        CVPixelBufferUnlockBaseAddress(buffer, 0);
    }
    return buffer;
}

Ответы [ 2 ]

1 голос
/ 13 января 2012

Я обнаружил, что утечки памяти не связаны с кодом записи видео. Утечка кода, похоже, является методом [self deleteTempVideo], который я вызываю изнутри - (void)writeVideo

Мне все еще нужно выяснить, что с ним не так, но я полагаю, что мой вопрос выходит за рамки данного вопроса.
Вот код - (void)deleteTempVideo:

- (void)deleteTempVideo
{
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if ([fileManager isReadableFileAtPath:self.videoPath]) [fileManager removeItemAtPath:self.videoPath error:nil];
}

А вот геттер, который я использую для доступа к self.videoPath @property:

- (NSString *)videoPath
{
    if (!_videoPath) {
        NSString *fileName = @"test.mov";
        NSString *directoryPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
        _videoPath = [directoryPath stringByAppendingPathComponent:fileName];
    }
    return _videoPath;
}
0 голосов
/ 26 сентября 2013

Что касается моего опыта, когда вы используете и дугу, и блок, вы не должны напрямую использовать переменные-члены.Вместо этого вы можете использовать слабые свойства.

Предположим, у вас есть класс с именем ClassA и имя переменной-члена _memberA.Обычно я определяю свойство memberA, затем я могу написать код, как показано ниже.

__weak ClassA *weakSelf = self;
SomeVariable.function = ^() {
    weakSelf.memberA ..... (not use _memberA Here)
}

PS.Если блок из какой-то статической функции, такой как [UIView animation: duration: завершение:], то, когда вы используете переменную-член, все в порядке.

Я решил многие утечки памяти таким образом.

...