27

I try the following 2 methods of appending UIImages pixelbuffer to ASSETWriterInput. Everything looks good except there's No data in the video file. What's wrong?

1 Adaptor class

AVAssetWriterInputPixelBufferAdaptor * avAdaptor = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:writerInput sourcePixelBufferAttributes:NULL];

[avAdaptor appendPixelBufferixelBuffer withPresentationTime:CMTimeMake(1, 10)];

2 Making the

// Create sample buffer.
CMSampleBufferRef sampleBuffer = NULL;
result = CMSampleBufferCreateForImageBuffer(kCFAllocatorDef ault, pixelBuffer, true, NULL, NULL, videoInfo, &timing, &sampleBuffer);

// Ship out the frame.
NSParameterAssert(CMSampleBufferDataIsReady(sample Buffer));
NSParameterAssert([writerInput isReadyForMoreMediaData]);
BOOL success = [writerInput appendSampleBuffer:sampleBuffer];
Ash Furrow
  • 12,391
  • 3
  • 57
  • 92
lilzz
  • 5,243
  • 14
  • 59
  • 87

3 Answers3

27

I found that for some reason I needed to append the buffer more than once. The timing in this example from a test app I made might not be proper, but since it works it should give you a good idea.

+ (void)writeImageAsMovie:(UIImage*)image toPath:(NSString*)path size:(CGSize)size duration:(int)duration 
{
    NSError *error = nil;
    AVAssetWriter *videoWriter = [[AVAssetWriter alloc] initWithURL:
                                  [NSURL fileURLWithPath:path] fileType:AVFileTypeQuickTimeMovie
                                                              error:&error];
    NSParameterAssert(videoWriter);

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

    AVAssetWriterInputPixelBufferAdaptor *adaptor = [AVAssetWriterInputPixelBufferAdaptor
                                                     assetWriterInputPixelBufferAdaptorWithAssetWriterInput:writerInput
                                                     sourcePixelBufferAttributes:nil];
    NSParameterAssert(writerInput);
    NSParameterAssert([videoWriter canAddInput:writerInput]);
    [videoWriter addInput:writerInput];

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

    //Write samples:
    CVPixelBufferRef buffer = [Utils pixelBufferFromCGImage:image.CGImage size:size];
    [adaptor appendPixelBuffer:buffer withPresentationTime:kCMTimeZero];
    [adaptor appendPixelBuffer:buffer withPresentationTime:CMTimeMake(duration-1, 2)];

    //Finish the session:
    [writerInput markAsFinished];
    [videoWriter endSessionAtSourceTime:CMTimeMake(duration, 2)];
    [videoWriter finishWriting];
}

This method is not required, but is used here as an example of a pixel buffer source:

+ (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;
}
Peter DeWeese
  • 18,141
  • 8
  • 79
  • 101
  • excellent it worked. that double commands are bugs? anyway, any ideas how to add audio to that video as well. I am using AvAudioPlayer. – lilzz Oct 22 '10 at 01:26
  • You can make an AVMutableComposition and insert your audio and video into the composition. If you want to fade, etc., you can use an AVMutableAudioMix. The composition and audioMix can be added to your AVPlayerItem for your player. – Peter DeWeese Oct 22 '10 at 13:04
  • 1
    I also discovered you have to append the buffer twice -- at least, I haven't figure out how to make it work without this kludge. Anyone know why this is necessary? – John Sep 21 '11 at 19:52
  • Will it work without implementing above method?And what to pass in duration parameter ? – Dhruv Jul 09 '12 at 05:21
  • @iApple: take duration in int..like int duration=10; :) – Tripti Kumar Jul 31 '12 at 06:31
  • getting this error: Invalid parameter not satisfying: bufferPool != NULL. Not able to figure out why. please help – coder1010 Apr 24 '13 at 10:40
  • I was able to open the movie in this example in a movie player and also after uploading to a CMS via web with no issues. – Peter DeWeese Dec 24 '13 at 12:53
  • I am getting the issue that video is not compatible to save in the library. – Imran Feb 15 '14 at 05:22
  • when i try to merge this video with another `.mov` file , the whole video is corrupted , play fine on iPhone but doesn't plays on web :( – Dhiru Feb 06 '17 at 05:15
  • From what web browser? You might need to select a different codec for the writer. – Peter DeWeese Feb 06 '17 at 22:18
3

I've had a bit of a problem with this code. It geave me a skewed image as a result.

Changing:

CGContextRef context = CGBitmapContextCreate(pxdata,
                                             size.width,
                                             size.height,
                                             8,
                                             4 * size.width,
                                             rgbColorSpace,
                                             kCGImageAlphaNoneSkipFirst);

To:

CGContextRef context = CGBitmapContextCreate(pxdata,
                                             size.width,
                                             size.height,
                                             8,
                                             CVPixelBufferGetBytesPerRow(pxbuffer),
                                             rgbColorSpace,
                                             kCGImageAlphaNoneSkipFirst);

helped.

Szymon Kuczur
  • 5,783
  • 3
  • 16
  • 14
  • Thank you, thank you, thank you! I've been banging my head for a few hours on this. My code worked great on a 3x scale device but not others. This was the solution. – VaporwareWolf Aug 23 '16 at 17:07
2

Hold on, though the answer given by @Peter DeWeese is the direction to follow, the code has two huge issues: firstly, you need to wait while the system is ready to append a new media and secondly, you've got a great memory leak as you need to release your buffer after it was appended to the video writer.

This is true in your very case, but even more in a general case, where you want to loop in multiple frame as follows:

NSInteger i = 0;

for (; i<n; i++) {

     image = allImages[i];

     CVPixelBufferRef buffer = [self pixelBufferFromCGImage:image.CGImage cropFrame:frame];

    // wait for more media data is ready
    while (adaptor.assetWriterInput.readyForMoreMediaData == FALSE) {
        NSDate *maxDate = [NSDate dateWithTimeIntervalSinceNow:0.1];
        [[NSRunLoop currentRunLoop] runUntilDate:maxDate];
    }
    NSLog(@"Panorama: appending frame %ld out of %ld", (long)i, (long)n);

    // Append data
    [adaptor appendPixelBuffer:buffer withPresentationTime:CMTimeMake(i, freq)];//kCMTimeZero];


    // release the buffer
    CVPixelBufferRelease(buffer);

}
Stéphane de Luca
  • 12,745
  • 9
  • 57
  • 95