7

我正在使用 AVAssetWriter 编写一个 MP4 视频文件AVAssetWriterInputPixelBufferAdaptor

源是来自 的视频UIImagePickerController,可以是从相机或资产库中新捕获的。现在的质量是UIImagePickerControllerQualityTypeMedium

有时作家会失败。它的状态是AVAssetWriterStatusFailedAVAssetWriter对象错误属性是:

Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" 
UserInfo=0xf5d8990 {NSLocalizedFailureReason=An unknown error occurred (-536870210), 
NSUnderlyingError=0x4dd8e0 "The operation couldn’t be completed. (OSStatus error -536870210.)",
NSLocalizedDescription=The operation could not be completed

错误发生在代码运行的大约 20% 的时间里。在 iPhone 4 / 4S 上似乎比在 iPhone 5 上更频繁地失败。

如果源视频质量更高,它也会更频繁地发生。使用 UIImagePickerControllerQualityTypeLow 错误不会经常发生。使用 UIImagePickerControllerQualityTypeHigh,错误发生的频率更高一些。

我还注意到别的东西:它似乎是一波又一波的。当它失败时,即使我删除应用程序并重新安装它,以下运行也会经常失败。这让我想知道,我的程序是否泄漏了一些内存,即使应用程序被杀死,该内存是否仍然存在(这甚至可能吗?)。

这是我用来渲染视频的代码:

- (void)writeVideo
{
    offlineRenderingInProgress = YES;

/* --- Writer Setup --- */

    [locationQueue cancelAllOperations];

    [self stopWithoutRewinding];

    NSError *writerError = nil;

    BOOL succes;

    succes = [[NSFileManager defaultManager] removeItemAtURL:self.outputURL error:nil];

    // DLog(@"Url: %@, succes: %i, error: %@", self.outputURL, succes, fileError);

    writer = [AVAssetWriter assetWriterWithURL:self.outputURL fileType:(NSString *)kUTTypeQuickTimeMovie error:&writerError];
    //writer.shouldOptimizeForNetworkUse = NO;

    if (writerError) {
        DLog(@"Writer error: %@", writerError);
        return;
    }

    float bitsPerPixel;
    CMVideoDimensions dimensions = CMVideoFormatDescriptionGetDimensions((__bridge CMVideoFormatDescriptionRef)([readerVideoOutput.videoTracks[0] formatDescriptions][0]));
    int numPixels = dimensions.width * dimensions.height;
    int bitsPerSecond;

    // Assume that lower-than-SD resolutions are intended for streaming, and use a lower bitrate
    if ( numPixels < (640 * 480) )
        bitsPerPixel = 4.05; // This bitrate matches the quality produced by AVCaptureSessionPresetMedium or Low.
    else
        bitsPerPixel = 11.4; // This bitrate matches the quality produced by AVCaptureSessionPresetHigh.

    bitsPerSecond = numPixels * bitsPerPixel;

    NSDictionary *videoCompressionSettings = [NSDictionary dictionaryWithObjectsAndKeys:
                                          AVVideoCodecH264, AVVideoCodecKey,
                                          [NSNumber numberWithFloat:videoSize.width], AVVideoWidthKey,
                                          [NSNumber numberWithInteger:videoSize.height], AVVideoHeightKey,
                                          [NSDictionary dictionaryWithObjectsAndKeys:
                                           [NSNumber numberWithInteger:30], AVVideoMaxKeyFrameIntervalKey,
                                           nil], AVVideoCompressionPropertiesKey,
                                          nil];

    writerVideoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:videoCompressionSettings];
    writerVideoInput.transform =  movie.preferredTransform;
    writerVideoInput.expectsMediaDataInRealTime = YES;
    [writer addInput:writerVideoInput];

    NSDictionary *sourcePixelBufferAttributesDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
                                                       [NSNumber numberWithInt:kCVPixelFormatType_32ARGB], kCVPixelBufferPixelFormatTypeKey, nil];

    writerPixelAdaptor = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:writerVideoInput
                                                                                      sourcePixelBufferAttributes:sourcePixelBufferAttributesDictionary];
    BOOL couldStart = [writer startWriting];

    if (!couldStart) {
        DLog(@"Could not start AVAssetWriter!");
        abort = YES;
        [locationQueue cancelAllOperations];
        return;
    }

    [self configureFilters];

    CIContext *offlineRenderContext = [CIContext contextWithOptions:@{kCIContextUseSoftwareRenderer : @NO}];


    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

    if (!self.canEdit) {
        [self createVideoReaderWithAsset:movie timeRange:CMTimeRangeFromTimeToTime(kCMTimeZero, kCMTimePositiveInfinity) forOfflineRender:YES];
    } else {
        [self createVideoReaderWithAsset:movie timeRange:CMTimeRangeWithNOVideoRangeInDuration(self.thumbnailEditView.range, movie.duration) forOfflineRender:YES];
    }

    CMTime startOffset = reader.timeRange.start;

    DLog(@"startOffset: %llu", startOffset.value);

    [self.thumbnailEditView removeFromSuperview];
    //    self.thumbnailEditView = nil;

    [glLayer removeFromSuperlayer];
    glLayer = nil;

    [playerView removeFromSuperview];
    playerView = nil;

    glContext = nil;



    [writerVideoInput requestMediaDataWhenReadyOnQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) usingBlock:^{

        @try {


        BOOL didWriteSomething = NO;

        DLog(@"Preparing to write...");

        while ([writerVideoInput isReadyForMoreMediaData]) {

            if (abort) {
                NSLog(@"Abort == YES");
                [locationQueue cancelAllOperations];
                [writerVideoInput markAsFinished];
                videoConvertCompletionBlock(NO, writer.error.localizedDescription);
            }

            if (writer.status == AVAssetWriterStatusFailed) {
                DLog(@"Writer.status: AVAssetWriterStatusFailed, error: %@", writer.error);

                [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithInt:1] forKey:@"QualityOverride"];
                [[NSUserDefaults standardUserDefaults] synchronize];

                abort = YES;
                [locationQueue cancelAllOperations];
                videoConvertCompletionBlock(NO, writer.error.localizedDescription);
                return;
                DLog(@"Source file exists: %i", [[NSFileManager defaultManager] fileExistsAtPath:movie.URL.relativePath]);
            }

            DLog(@"Writing started...");

            CMSampleBufferRef buffer = nil;

            if (reader.status != AVAssetReaderStatusUnknown) {

                if (reader.status == AVAssetReaderStatusReading) {
                    buffer = [readerVideoOutput copyNextSampleBuffer];
                    if (didWriteSomething == NO) {
                        DLog(@"Copying sample buffers...");
                    }
                }

                if (!buffer) {

                    [writerVideoInput markAsFinished];

                    DLog(@"Finished...");

                    CGColorSpaceRelease(colorSpace);

                    [self offlineRenderingDidFinish];


                    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

                        [writer finishWriting];
                        if (writer.error != nil) {
                            DLog(@"Error: %@", writer.error);
                        } else {
                            DLog(@"Succes!");
                        }

                        if (writer.status == AVAssetWriterStatusCompleted) {

                            videoConvertCompletionBlock(YES, nil);
                        }

                        else {
                            abort = YES;
                            videoConvertCompletionBlock(NO, writer.error.localizedDescription);
                        }

                    });


                    return;
                }

                didWriteSomething = YES;
            }
            else {

                DLog(@"Still waiting...");
                //Reader just needs a moment to get ready...
                continue;
            }

            CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(buffer);

            if (pixelBuffer == NULL) {
                DLog(@"Pixelbuffer == NULL");
                continue;
            }

            //DLog(@"Sample call back! Pixelbuffer: %lu", CVPixelBufferGetHeight(pixelBuffer));

            //NSDictionary *options = [NSDictionary dictionaryWithObject:(__bridge id)CGColorSpaceCreateDeviceRGB() forKey:kCIImageColorSpace];

            CIImage *ciimage = [CIImage imageWithCVPixelBuffer:pixelBuffer options:nil];

            CIImage *outputImage = [self filteredImageWithImage:ciimage];


            CVPixelBufferRef outPixelBuffer = NULL;
            CVReturn status;

            CFDictionaryRef empty; // empty value for attr value.
            CFMutableDictionaryRef attrs;
            empty = CFDictionaryCreate(kCFAllocatorDefault, // our empty IOSurface properties dictionary
                                       NULL,
                                       NULL,
                                       0,
                                       &kCFTypeDictionaryKeyCallBacks,
                                       &kCFTypeDictionaryValueCallBacks);

            attrs = CFDictionaryCreateMutable(kCFAllocatorDefault,
                                              1,
                                              &kCFTypeDictionaryKeyCallBacks,
                                              &kCFTypeDictionaryValueCallBacks);

            CFDictionarySetValue(attrs,
                                 kCVPixelBufferIOSurfacePropertiesKey,
                                 empty);

            CFDictionarySetValue(attrs,
                                 kCVPixelBufferCGImageCompatibilityKey,
                                 (__bridge const void *)([NSNumber numberWithBool:YES]));

            CFDictionarySetValue(attrs,
                                 kCVPixelBufferCGBitmapContextCompatibilityKey,
                                 (__bridge const void *)([NSNumber numberWithBool:YES]));


            status = CVPixelBufferCreate(kCFAllocatorDefault, ciimage.extent.size.width, ciimage.extent.size.height, kCVPixelFormatType_32BGRA, attrs, &outPixelBuffer);

            //DLog(@"Output image size: %f, %f, pixelbuffer height: %lu", outputImage.extent.size.width, outputImage.extent.size.height, CVPixelBufferGetHeight(outPixelBuffer));

            if (status != kCVReturnSuccess) {
                DLog(@"Couldn't allocate output pixelBufferRef!");
                continue;
            }

            [offlineRenderContext render:outputImage toCVPixelBuffer:outPixelBuffer bounds:outputImage.extent colorSpace:colorSpace];

            CMTime currentSourceTime = CMSampleBufferGetPresentationTimeStamp(buffer);
            CMTime currentTime = CMTimeSubtract(currentSourceTime, startOffset);
            CMTime duration = reader.timeRange.duration;
            if (CMTIME_IS_POSITIVE_INFINITY(duration)) {
                duration = movie.duration;
            }
            CMTime durationConverted = CMTimeConvertScale(duration, currentTime.timescale, kCMTimeRoundingMethod_Default);

            float durationFloat = (float)durationConverted.value;
            float progress =  ((float) currentTime.value) / durationFloat;

            //DLog(@"duration : %f, progress: %f", durationFloat, progress);

            [self updateOfflineRenderProgress:progress];

            if (pixelBuffer != NULL && writerVideoInput.readyForMoreMediaData) {
                [writerPixelAdaptor appendPixelBuffer:outPixelBuffer withPresentationTime:currentTime];
            } else {
                continue;
            }

            if (writer.status == AVAssetWriterStatusWriting) {
                DLog(@"Writer.status: AVAssetWriterStatusWriting");
            }

            CFRelease(buffer);
            CVPixelBufferRelease(outPixelBuffer);
        }

        }

        @catch (NSException *exception) {
            DLog(@"Catching exception: %@", exception);
        }

    }];

}
4

1 回答 1

14

好的,我想我自己解决了。坏人是这行:

[writerVideoInput requestMediaDataWhenReadyOnQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) usingBlock:^{ ....

我传递的全局队列是一个并发队列。这允许在前一个回调完成之前进行新的回调。资产编写器并非设计为一次从多个线程写入。

创建和使用新的串行队列似乎可以解决这个问题:

assetWriterQueue = dispatch_queue_create("AssetWriterQueue", DISPATCH_QUEUE_SERIAL);

[writerVideoInput requestMediaDataWhenReadyOnQueue:assetWriterQueue usingBlock:^{...
于 2013-01-02T14:26:15.283 回答