ASSETWriterInput在iPhone上从UIImages制作视频的问题

27

我尝试了以下两种方法将UIImage的像素缓冲区附加到ASSETWriterInput中。除了在视频文件中没有数据之外,一切都看起来很好。有什么问题吗?

1.适配器类

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

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

2 制作

// 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];
3个回答

27

我发现出于某种原因,我需要多次添加缓冲区。虽然这个示例来自我制作的测试应用程序的时间可能不正确,但由于它能正常工作,所以应该会给你一个很好的想法。

+ (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];
}

这个方法不是必须的,但在这里被用作像素缓冲源的示例:

+ (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;
}

1
我还发现你必须两次附加缓冲区——至少,我还没有想出如何在不使用这个丑陋的方法的情况下使其工作。有人知道为什么需要这样做吗? - John
出现以下错误:无效参数,不满足:bufferPool!= NULL。 无法弄清楚原因。请帮忙。 - coder1010
运行得非常好!谢谢老兄。 - Pradeep Rajkumar
我能够在电影播放器中打开这个示例中的电影,并且在通过网络上传到CMS后也没有任何问题。 - Peter DeWeese
我遇到了一个问题,视频无法保存到库中。 - Imran
显示剩余6条评论

3

我遇到了代码问题,导致结果出现了图像失真。

更改为:

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

致:

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

已协助完成。


谢谢,谢谢,谢谢!我在这上面烦了几个小时。我的代码在3倍比例设备上运行得很好,但在其他设备上却不能正常运行。这就是解决方案。 - VaporwareWolf

2

请稍等,虽然@Peter DeWeese提供的答案是要遵循的方向,但代码存在两个重大问题:首先,您需要等待系统准备好附加新媒体,其次,您需要在将缓冲区附加到视频编写器后释放它,否则会发生内存泄漏。

虽然这在您的情况下是正确的,但在一般情况下更为重要,例如您希望在多个帧之间循环:

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);

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接