我正在使用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:^{...