简体   繁体   English

AVAssetWriter 问题

[英]AVAssetWriter Woes

I'm trying to use AVAssetWriter to write CGImages to a file to create a video from images.我正在尝试使用 AVAssetWriter 将 CGImages 写入文件以从图像创建视频。

I've gotten this to work successfully in three different ways on the simulator, but every method fails on an iPhone 4 running iOS 4.3.我已经让它在模拟器上以三种不同的方式成功运行,但是在运行 iOS 4.3 的 iPhone 4 上,每种方法都失败了。

This all has to do with pixel buffers.这一切都与像素缓冲区有关。

My first method was to just create the pixel buffers as needed without using a pool.我的第一种方法是根据需要创建像素缓冲区而不使用池。 That works, but is too memory intensive to work on the device.这行得通,但是 memory 过于密集,无法在设备上工作。

My second method was to use the recommended AVAssetWriterInputPixelBufferAdaptor and then pull pixel buffers from the adaptors pixelBufferPool with CVPixelBufferPoolCreatePixelBuffer.我的第二种方法是使用推荐的 AVAssetWriterInputPixelBufferAdaptor,然后使用 CVPixelBufferPoolCreatePixelBuffer 从适配器 pixelBufferPool 中提取像素缓冲区。

That also works on the simulator, but fails on the device because the adaptor's pixel buffer pool is never allocated.这也适用于模拟器,但在设备上失败,因为适配器的像素缓冲池从未分配过。 I get no error messages.我没有收到错误消息。

Lastly, I attempted to create by own pixel buffer pool with CVPixelBufferPoolCreate.最后,我尝试使用 CVPixelBufferPoolCreate 创建自己的像素缓冲池。 That also works in the simulator but on the device, everything works fine until I try to append the pixel buffer with appendPixelBuffer which fails every time.这也适用于模拟器,但在设备上,一切正常,直到我尝试使用 appendPixelBuffer 对像素缓冲区进行 append ,但每次都失败。

I've found very minimal info on this on the web.我在 web 上找到了非常少的信息。 I've based my code on the examples I've found, but no luck for days now.我的代码基于我找到的示例,但现在几天都没有运气。 If ANYONE has experience doing this with AVAssetWriter successfully, please take a look and let me know if you see anything out of place.如果任何人都有使用 AVAssetWriter 成功执行此操作的经验,请看一下,如果您发现任何不合适的地方,请告诉我。

NOTE: you will see commented out block of attempts.注意:您将看到已注释掉的尝试块。

First, the setup一、设置

- (BOOL) openVideoFile: (NSString *) path withSize:(CGSize)imageSize {
size = CGSizeMake (480.0, 320.0);//imageSize;

NSError *error = nil;
videoWriter = [[AVAssetWriter alloc] initWithURL:
                              [NSURL fileURLWithPath:path] fileType:AVFileTypeQuickTimeMovie
                                                          error:&error];
if (error != nil)
    return NO;

NSDictionary *videoCleanApertureSettings = [NSDictionary dictionaryWithObjectsAndKeys:
                                            [NSNumber numberWithDouble:size.width], AVVideoCleanApertureWidthKey,
                                            [NSNumber numberWithDouble:size.height], AVVideoCleanApertureHeightKey,
                                            [NSNumber numberWithInt:10], AVVideoCleanApertureHorizontalOffsetKey,
                                            [NSNumber numberWithInt:10], AVVideoCleanApertureVerticalOffsetKey,
                                            nil];


NSDictionary *videoAspectRatioSettings = [NSDictionary dictionaryWithObjectsAndKeys:
                                          [NSNumber numberWithInt:1], AVVideoPixelAspectRatioHorizontalSpacingKey,
                                          [NSNumber numberWithInt:1],AVVideoPixelAspectRatioVerticalSpacingKey,
                                          nil];



NSDictionary *codecSettings = [NSDictionary dictionaryWithObjectsAndKeys:
                               //[NSNumber numberWithInt:960000], AVVideoAverageBitRateKey,
                              // [NSNumber numberWithInt:1],AVVideoMaxKeyFrameIntervalKey,
                               videoCleanApertureSettings, AVVideoCleanApertureKey,
                               videoAspectRatioSettings, AVVideoPixelAspectRatioKey,
                               //AVVideoProfileLevelH264Main31, AVVideoProfileLevelKey,
                               nil];

NSDictionary *videoSettings = [NSDictionary dictionaryWithObjectsAndKeys:
                               AVVideoCodecH264, AVVideoCodecKey,
                               codecSettings,AVVideoCompressionPropertiesKey,
                               [NSNumber numberWithDouble:size.width], AVVideoWidthKey,
                               [NSNumber numberWithDouble:size.height], AVVideoHeightKey,
                               nil];
writerInput = [[AVAssetWriterInput
                                    assetWriterInputWithMediaType:AVMediaTypeVideo
                                    outputSettings:videoSettings] retain];
NSMutableDictionary * bufferAttributes = [[NSMutableDictionary alloc] init];
[bufferAttributes setObject: [NSNumber numberWithInt: kCVPixelFormatType_32ARGB]
                   forKey: (NSString *) kCVPixelBufferPixelFormatTypeKey];
[bufferAttributes setObject: [NSNumber numberWithInt: 480]
                   forKey: (NSString *) kCVPixelBufferWidthKey];
[bufferAttributes setObject: [NSNumber numberWithInt: 320]
                   forKey: (NSString *) kCVPixelBufferHeightKey];


//NSDictionary *bufferAttributes = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:kCVPixelFormatType_32ARGB], kCVPixelBufferPixelFormatTypeKey, nil];
//[bufferAttributes setObject: [NSNumber numberWithInt: 640]
//                   forKey: (NSString *) kCVPixelBufferWidthKey];
//[bufferAttributes setObject: [NSNumber numberWithInt: 480]
//                   forKey: (NSString *) kCVPixelBufferHeightKey];
adaptor = [[AVAssetWriterInputPixelBufferAdaptor
            assetWriterInputPixelBufferAdaptorWithAssetWriterInput:writerInput
            sourcePixelBufferAttributes:nil] retain];

//CVPixelBufferPoolCreate (kCFAllocatorSystemDefault,NULL,(CFDictionaryRef)bufferAttributes,&pixelBufferPool);
//Create buffer pool
NSMutableDictionary*     attributes;
attributes = [NSMutableDictionary dictionary];

int width = 480;
int height = 320;

[attributes setObject:[NSNumber numberWithInt:kCVPixelFormatType_32ARGB] forKey:(NSString*)kCVPixelBufferPixelFormatTypeKey];
[attributes setObject:[NSNumber numberWithInt:width] forKey: (NSString*)kCVPixelBufferWidthKey];
[attributes setObject:[NSNumber numberWithInt:height] forKey: (NSString*)kCVPixelBufferHeightKey];
CVReturn theError = CVPixelBufferPoolCreate(kCFAllocatorDefault, NULL, (CFDictionaryRef) attributes, &pixelBufferPool);                                           


NSParameterAssert(writerInput);
NSParameterAssert([videoWriter canAddInput:writerInput]);
[videoWriter addInput:writerInput];

writerInput.expectsMediaDataInRealTime = YES;

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

buffer = NULL;
lastTime = kCMTimeZero;
presentTime = kCMTimeZero;

return YES;
}

Next, the two methods that append the writer and create the pixel buffer to append.接下来,append writer 和 append 创建像素缓冲区的两种方法。

- (void) writeImageToMovie:(CGImageRef)image 
{
    if([writerInput isReadyForMoreMediaData])
    {
//          CMTime frameTime = CMTimeMake(1, 20);
//          CMTime lastTime=CMTimeMake(i, 20); //i is from 0 to 24 of the loop above
//          CMTime presentTime=CMTimeAdd(lastTime, frameTime);

        buffer = [self pixelBufferFromCGImage:image];
        BOOL success = [adaptor appendPixelBuffer:buffer withPresentationTime:presentTime];
        if (!success) NSLog(@"Failed to appendPixelBuffer");
        CVPixelBufferRelease(buffer);

        presentTime = CMTimeAdd(lastTime, CMTimeMake(5, 1000));
        lastTime = presentTime;
    }
    else
    {
        NSLog(@"error - writerInput not ready");
    }
}

- (CVPixelBufferRef)pixelBufferFromCGImage:(CGImageRef)image
{
CVPixelBufferRef pxbuffer;
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                         [NSNumber numberWithBool:YES], kCVPixelBufferCGImageCompatibilityKey,
                         [NSNumber numberWithBool:YES], kCVPixelBufferCGBitmapContextCompatibilityKey,
                         nil];
if (pixelBufferPool == NULL) NSLog(@"pixelBufferPool is null!");
CVReturn status = CVPixelBufferPoolCreatePixelBuffer (NULL, pixelBufferPool, &pxbuffer); 
/*if (pxbuffer == NULL) {
    CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, size.width,
                                      size.height, kCVPixelFormatType_32ARGB, (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, size.width,
                                             size.height, 8, 4*size.width, rgbColorSpace, 
                                             kCGImageAlphaNoneSkipFirst);
//NSParameterAssert(context);
CGContextConcatCTM(context, CGAffineTransformMakeRotation(0));
CGContextDrawImage(context, CGRectMake(90, 10, CGImageGetWidth(image), 
                                       CGImageGetHeight(image)), image);
CGColorSpaceRelease(rgbColorSpace);
CGContextRelease(context);

CVPixelBufferUnlockBaseAddress(pxbuffer, 0);

return pxbuffer;
}

I've found the solution to this issue.我找到了解决这个问题的方法。

If you want to have AVAudioPlayer and AVAssetWriter behave correctly together, you must have and audio session category that is 'mixable'.如果你想让 AVAudioPlayer 和 AVAssetWriter 一起正常运行,你必须拥有和音频 session 类别是“可混合的”。

You can use a category that is mixable like AVAudioSessionCategoryAmbient.您可以使用可混合的类别,例如 AVAudioSessionCategoryAmbient。

However, I needed to use AVAudioSessionCategoryPlayAndRecord.但是,我需要使用 AVAudioSessionCategoryPlayAndRecord。

You can set any category to be mixable by implementing this:您可以通过实现以下方式将任何类别设置为可混合:

OSStatus propertySetError = 0;

UInt32 allowMixing = true;

propertySetError = AudioSessionSetProperty (
                       kAudioSessionProperty_OverrideCategoryMixWithOthers,  // 1
                       sizeof (allowMixing),                                 // 2
                       &allowMixing                                          // 3
                   );

Well, first you need to pass some bufferAttributes when creating that adaptor object:好吧,首先你需要在创建适配器 object 时传递一些bufferAttributes

    NSDictionary *bufferAttributes = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:kCVPixelFormatType_32BGRA], kCVPixelBufferPixelFormatTypeKey, nil];

AVAssetWriterInputPixelBufferAdaptor *adaptor = [AVAssetWriterInputPixelBufferAdaptor
                                                 assetWriterInputPixelBufferAdaptorWithAssetWriterInput:_videoWriterInput
                                                 sourcePixelBufferAttributes:bufferAttributes];

Then remove that call to CVPixelBufferPoolCreate , there's already a pixel buffer pool created in the adaptor object, so call just this instead:然后删除对CVPixelBufferPoolCreate的调用,适配器 object 中已经创建了一个像素缓冲池,所以只调用这个:

                CVPixelBufferRef pixelBuffer = NULL;
            CVPixelBufferPoolCreatePixelBuffer(NULL, adaptor.pixelBufferPool, &pixelBuffer);
            CVPixelBufferLockBaseAddress(pixelBuffer, 0);

            // ...fill the pixelbuffer here

            CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);

            CMTime frameTime = CMTimeMake(frameCount,(int32_t) 30);

            BOOL res = [adaptor appendPixelBuffer:pixelBuffer withPresentationTime:frameTime];
            CVPixelBufferRelease(pixelBuffer);
            CFRelease(sampleBuffer);

I think that should do it, I've had a similar error at some point and I solved it by creating the adaptor and pixel buffer as shown here..我认为应该这样做,我在某些时候遇到过类似的错误,我通过创建适配器和像素缓冲区来解决它,如此处所示..

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM