2

Problem

My AVAssetWriter is failing after appending 5 or so images to it using a AVAssetWriterInputPixelBufferAdaptor, and I have no idea why.

Details

This popular question helped but isn't working for my needs:

How do I export UIImage array as a movie?

Everything works as planned, I even delay the assetWriterInput until it can handle more media. But for some reason, it always fails after 5 or so images. The images I'm using are extracted frames from a GIF

Code

Here is my iteration code:

-(void)writeImageData
{

     __block int i = 0;
     videoQueue = dispatch_queue_create("com.videoQueue", DISPATCH_QUEUE_SERIAL);
    [self.writerInput requestMediaDataWhenReadyOnQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0) usingBlock:^{

     while (self.writerInput.readyForMoreMediaData) {
        if (i >= self.imageRefs.count){
            [self endSession];
            videoQueue = nil;
            [self saveToLibraryWithCompletion:^{
                NSLog(@"Saved");
            }];
            break;
        }

        if (self.writerInput.readyForMoreMediaData){
            CGImageRef imageRef = (__bridge CGImageRef)self.imageRefs[i];
            CVPixelBufferRef buffer = [self pixelBufferFromCGImageRef:imageRef];


            CGFloat timeScale = (CGFloat)self.imageRefs.count / self.originalDuration;
            BOOL accepted = [self.adaptor appendPixelBuffer:buffer withPresentationTime:CMTimeMake(i, timeScale)];
            CVBufferRelease(buffer);
            if (!accepted){
                NSLog(@"Buffer did not add %@, index %d, timescale %f", self.writer.error, i, timeScale);
            }else{
                NSLog(@"Buffer did nothing wrong");
            }
            i++;
        }
    }
}];

}

My other bits of code match the code from the Link above. This is only slightly different:

-(CVPixelBufferRef)pixelBufferFromCGImageRef:(CGImageRef)image
{
   NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                         [NSNumber numberWithBool:YES], kCVPixelBufferCGImageCompatibilityKey,
                         [NSNumber numberWithBool:YES], kCVPixelBufferCGBitmapContextCompatibilityKey,
                         nil];
   CVPixelBufferRef pxbuffer = NULL;
   CGFloat width = 640;
   CGFloat height = 640;
   CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, width,
                                      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, width,
                                             height, 8, 4*width, rgbColorSpace,
                                             kCGImageAlphaNoneSkipFirst);
   NSParameterAssert(context);

   CGContextDrawImage(context, CGRectMake(0, 0, width,
                                       height), image);
   CGColorSpaceRelease(rgbColorSpace);
   CGContextRelease(context);

   CVPixelBufferUnlockBaseAddress(pxbuffer, 0);
   return pxbuffer;

}

Community
  • 1
  • 1
Rob Caraway
  • 3,856
  • 3
  • 30
  • 37
  • It would help if you explained the problem. Saying "it fails" doesn't tell us anything. Provide more details. Errors? Crash? iPhone explodes? What? – rmaddy May 23 '14 at 22:40
  • The log "Buffer Did not Add" gets called. The AVAssetWriter's error appears, but it only says 'The operation could not be completed. An unknown error occured' – Rob Caraway May 23 '14 at 22:47
  • One thing that stands out to me is your use of `CMTimeMake(adjustedTime, 1)`. Can you try changing that to `CMTimeMake (adjustedTime * 24, 24)`? Let me know the result! – Jack May 26 '14 at 15:39
  • Jack, that dramatically improved the amount of frames the buffer gets through before failing. Before it would get through 3-8 frames, now its doing 15+ frames. Its even getting through some of them. Why is this? – Rob Caraway May 26 '14 at 15:54
  • Okay, so that seems like we're going in the right direction. I would urge you to go read up a bit on CMTime and correctly calculate the time for each frame. The 2nd paramenter in CMTimeMake should be your target frame rate, and the 1st should just be the count of your frame. If those are calculated correctly it might just be what you need! – Jack May 26 '14 at 16:06
  • My suspicion is that you might be passing 2 of the same CMTimes to the writer. Note that the first parameter in `CMTimeMake` is an **Integer**. Do your calculations! – Jack May 26 '14 at 16:07
  • @RobCaraway One last note is that when replying to comments, please use the `@` symbol like I just did there, it makes your reply show up in my notifications – Jack May 26 '14 at 16:10
  • @JackWu I've changed the CMTime and I've edited the code to reflect how it looks. Its working! But it still fails every now and then. Is this normal? Is 100% success rate unheard of? – Rob Caraway May 26 '14 at 16:33
  • @RobCaraway I've got a meeting now but I'll come back and take a look soon. 100% success rate is not a myth! – Jack May 26 '14 at 16:34
  • @RobCaraway is it still failing in the same way as before? Is there more information provided in the log? What is the value of `timeScale` usually? Is there a pattern of `timeScale`'s value to when it crashes? – Jack May 26 '14 at 17:07
  • @JackWu The error is the same as first comment. I've recorded a few, it appears to fail more the higher the frame count. I had one with 180 image frames that took 4 tries to succeed with a `timeScale` of 10 (18 second duration). It failed at index 7,9 and 59. – Rob Caraway May 26 '14 at 17:49
  • I just noticed another thing in your update, you create `videoQueue` but don't use it. Can you try replacing `dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)` with your `videoQueue`? A concurrent queue might be causing some random issues? – Jack May 26 '14 at 17:55
  • 2
    @JackWu Wow. Duh. 100% success rate. Thanks for the help Jack. – Rob Caraway May 26 '14 at 17:58

1 Answers1

1

One thing that stands out to me is your use of CMTimeMake(adjustedTime, 1).

You need to calculate the time of each frame properly. Note that CMTime takes two integers, and passing them as floating point values in truncates them.

The second issue is that you weren't using your serial dispatch queue :)

Jack
  • 16,677
  • 8
  • 47
  • 51