objective-c – AVAssetWriter有时会失败,状态为AVAssetWriterStatusFailed.似乎是随机的

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

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

有时候作家失败了.它的状态是AVAssetWriterStatusFailed,AVAssetWriter对象的错误属性是:

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,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,&kcfTypeDictionaryKeyCallBacks,&kcfTypeDictionaryValueCallBacks);

            attrs = CFDictionaryCreateMutable(kcfAllocatorDefault,1,&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);
        }

    }];

}

解决方法

好吧,我想我自己解决了.坏人就是这条线:
[writerVideoInput requestMediaDataWhenReadyOnQueue:dispatch_get_global_queue(disPATCH_QUEUE_PRIORITY_DEFAULT,0) usingBlock:^{ ....

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

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

assetWriterQueue = dispatch_queue_create("AssetWriterQueue",disPATCH_QUEUE_SERIAL);

[writerVideoInput requestMediaDataWhenReadyOnQueue:assetWriterQueue usingBlock:^{...

相关文章

本程序的编译和运行环境如下(如果有运行方面的问题欢迎在评...
水了一学期的院选修,万万没想到期末考试还有比较硬核的编程...
补充一下,先前文章末尾给出的下载链接的完整代码含有部分C&...
思路如标题所说采用模N取余法,难点是这个除法过程如何实现。...
本篇博客有更新!!!更新后效果图如下: 文章末尾的完整代码...
刚开始学习模块化程序设计时,估计大家都被形参和实参搞迷糊...