0

I have several UIImages and I want to create a video from them.

I am used a solution based on this

to create a video from UIImages. In my case, I would like to create a 30 fps video. So, every image is 1/30 of a second.

After setting everything to start saving the video, as mentioned on that page, I have created a method that saves one image to the movie and this method is called by a loop. Something like:

for (int i=0; i<[self.arrayOfFrames count]; i++ {
  UIImage *oneImage = [self.arrayOfFrames objectAtIndex:i];
  [self saveOneFrame:oneImage atTime:i];
}

and the method is

-(void)saveOneFrame:(UIImage *)imagem atTime:(NSInteger)time {
    // I have tried this autorelease pool to drain memory after the method is finished
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    CVPixelBufferRef buffer = NULL;

    buffer = [self pixelBufferFromCGImage:imagem.CGImage size:imagem.size];

    BOOL append_ok = NO;
    int j = 0;
    while (!append_ok && j < 30) 
    {
        if (adaptor.assetWriterInput.readyForMoreMediaData) 
        {
            printf("appending %d attemp %d\n", time, j);

            CMTime oneFrameLength = CMTimeMake(1, 30.0f );  // one frame = 1/30 s
            CMTime lastTime;
            CMTime presentTime;

            if (time == 0) {
                presentTime = CMTimeMake(0, self.FPS);
            } else {
                lastTime = CMTimeMake(tempo-1, self.FPS);
                presentTime = CMTimeAdd(lastTime, duracaoUmFrame);

            }
            // this will always add 1/30 to every new keyframe
            CMTime presentTime = CMTimeAdd(lastTime, oneFrameLength);

            append_ok = [adaptor appendPixelBuffer:buffer withPresentationTime:presentTime];
            CVPixelBufferPoolRef bufferPool = adaptor.pixelBufferPool;
            NSParameterAssert(bufferPool != NULL); 

            [NSThread sleepForTimeInterval:0.05];
        } 
        else 
        {
            printf("adaptor not ready %d, %d\n", time, j);
            [NSThread sleepForTimeInterval:0.1];
        }
        j++;
    }
    if (!append_ok) {
        printf("error appending image %d times %d\n", time, j);
    }
    CVBufferRelease(buffer);
    [pool drain]; // I have tried with and without this autorelease pool in place... no difference

}

The application simply quits, without any warning, after saving 50 frames to the movie...

This is the other method:

-(CVPixelBufferRef) pixelBufferFromCGImage:(CGImageRef)image size:(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, (CFDictionaryRef) options, 
                                          &pxbuffer);
    status=status;//Added to make the stupid compiler not show a stupid warning.
    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, 
                                                 kCGImageAlphaNoneSkipFirst);
    NSParameterAssert(context);

    //CGContextTranslateCTM(context, 0, CGImageGetHeight(image));
    //CGContextScaleCTM(context, 1.0, -1.0);//Flip vertically to account for different origin

    CGContextDrawImage(context, CGRectMake(0, 0, CGImageGetWidth(image), 
                                           CGImageGetHeight(image)), image);
    CGColorSpaceRelease(rgbColorSpace);
    CGContextRelease(context);

    CVPixelBufferUnlockBaseAddress(pxbuffer, 0);

    return pxbuffer;
}

I run instruments and have not detected any leak or exaggerated memory usage that is about the same before the movie starts being saved.

any clues?


NOTE:

After looking at the device logs, I found this:

<Notice>: (UIKitApplication:com.myID.myApp[0xc304]) Bug: launchd_core_logic.c:3732 (25562):3
<Notice>: (UIKitApplication:com.myID.myApp[0xc304]) Assuming job exited: <rdar://problem/5020256>: 10: No child processes
<Warning>: (UIKitApplication:com.myID.myApp[0xc304]) Job appears to have crashed: Segmentation fault: 11
<Warning>: Application 'myApp' exited abnormally with signal 11: Segmentation fault: 11
Community
  • 1
  • 1
Duck
  • 34,902
  • 47
  • 248
  • 470

2 Answers2

0

Maybe you have tried this already but this can help?

In the end, the solution was to restart the iPhone, since some data got corrupted. After the reboot everything was working normally.

Should have thought of the classic "Have you tried turning it off and on again?"

Community
  • 1
  • 1
Thermometer
  • 2,567
  • 3
  • 20
  • 41
  • 1
    Ok, restarting the device "solved" the problem partially. Now, instead of 35, the app is crashing after saving the 50th frame. It is obviously a memory problem, despite Xcode not crashing or not saying a word (btw I told you I hate Xcode 4.x?). I have put an autorelease pool in place, on the method, without success...I am saving very small frames (200x100 pixels). How can this be memory usage when everything is released after the saving ends and I have no leak on my app? – Duck Jun 22 '12 at 18:10
0

Look at it this way, you have an array of images (something which eats a lot of memory). You're making a copy of each one of those images and saving the copy to the finished movie. So your requirements are essentially doubled what you started with. How about if you released each frame after you've added the frame? That will mean that you may end up with the same size (or probably somewhat larger, but still smaller than what you had) memory usage.

Owen Hartnett
  • 5,925
  • 2
  • 19
  • 35
  • I have tried a different way. I am generating the frames on the fly and not building the array anymore. I build one frame, send to the video, build another frame, send to video, etc... and it crashes after the same number of frames recorded... exactly after saving the 50th frame. – Duck Jun 22 '12 at 19:40
  • Do you see your original frames going away? I'm thinking you might be in a tight loop and the phone is waiting until you fall out of your method to autorelease the pool, but you're in the loop so it's not happening until you crash. In that case you could start a thread that builds a frame, and at the end of that, start a new thread for the second frame when you end. – Owen Hartnett Jun 23 '12 at 01:44
  • yes, I see the frames going out. It is not hanging because it is frozen on a loop. I see the message, "appending frame 0, attempt 0", "appending frame 1, attempt 0"... etc... it is being ejected after 50 frames because it is using too much memory... this is strange. – Duck Jun 23 '12 at 04:18
  • I'm not saying that your going to be frozen on a loop. I'm saying that your memory may not be really released until you exit your method. In that case, your memory never goes away because you use up too much memory and it only gets released when you exit the method. Since you're in a loop, you don't exit the method and the memory never goes away. In other words, the memory cleanup only happens after you exit the loop. If this is the case, then you need to have a method that appends and releases one frame as one thread, then when it exits it starts up a new thread for the second frame. – Owen Hartnett Jun 23 '12 at 21:23
  • Doing it like that will eliminate the "loop" and have the threads continue to spawn until you reach the end of the frames. – Owen Hartnett Jun 23 '12 at 21:25