一次“Error Domain=AVFoundationErrorDomain Code=-11841”的调试

起因

最近在重构视频输出模块的时候,调试碰到AVAssetReader 调用开始方法总是返回NO而失败,代码如下:

if ([reader startReading] == NO)
{
NSLog(@"Error reading from file at URL: %@", self.url);
return;
}

reader的创建代码如下,主要用的是GPUImageMovieCompostion.

- (AVAssetReader*)createAssetReader
{
NSError *error = nil;
AVAssetReader *assetReader = [AVAssetReader assetReaderWithAsset:self.compositon error:&error]; NSDictionary *outputSettings = @{(id)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)};
AVAssetReaderVideoCompositionOutput *readerVideoOutput = [AVAssetReaderVideoCompositionOutput assetReaderVideoCompositionOutputWithVideoTracks:[_compositon tracksWithMediaType:AVMediaTypeVideo]
videoSettings:outputSettings];
readerVideoOutput.videoComposition = self.videoComposition;
readerVideoOutput.alwaysCopiesSampleData = NO;
if ([assetReader canAddOutput:readerVideoOutput]) {
[assetReader addOutput:readerVideoOutput];
} NSArray *audioTracks = [_compositon tracksWithMediaType:AVMediaTypeAudio];
BOOL shouldRecordAudioTrack = (([audioTracks count] > 0) && (self.audioEncodingTarget != nil) );
AVAssetReaderAudioMixOutput *readerAudioOutput = nil; if (shouldRecordAudioTrack)
{
[self.audioEncodingTarget setShouldInvalidateAudioSampleWhenDone:YES];
NSDictionary *audioReaderSetting = @{AVFormatIDKey: @(kAudioFormatLinearPCM),
AVLinearPCMIsBigEndianKey: @(NO),
AVLinearPCMIsFloatKey: @(NO),
AVLinearPCMBitDepthKey: @(16)};
readerAudioOutput = [AVAssetReaderAudioMixOutput assetReaderAudioMixOutputWithAudioTracks:audioTracks audioSettings:audioReaderSetting];
readerAudioOutput.audioMix = self.audioMix;
readerAudioOutput.alwaysCopiesSampleData = NO;
[assetReader addOutput:readerAudioOutput];
} return assetReader;
}

然后在该处断点,查看reader的status和error,显示Error Domain=AVFoundationErrorDomain Code=-11841。查了一下文档发现如下:

AVErrorInvalidVideoComposition = -11841

You attempted to perform a video composition operation that is not supported.

应该就是readerVideoOutput.videoComposition = self.videoComposition; 这个videoCompostion有问题了。AVMutableVideoComposition这个类在视频编辑里面非常重要,包含对AVComposition中的各个视频track如何融合的信息,我做的是两个视频的融合小demo,就需要用到它,设置这个类稍微有点复杂,比较容易出错,特别是设置AVMutableVideoCompositionInstruction这个类的timeRange属性,我的这次错误就是因为这个。

开始调试

马上调用AVMutableVideoComposition的如下方法:


BOOL isValid = [self.videoComposition isValidForAsset:self.compositon timeRange:CMTimeRangeMake(kCMTimeZero, self.compositon.duration) validationDelegate:self];

这个时候isValid为No,确定就是这个videoCompostion问题了,添加了AVVideoCompositionValidationHandling协议四个方法打印一下,这个协议太贴心了,估计知道这个地方出错概率比较高,所以特地弄的吧。代码如下:


- (BOOL)videoComposition:(AVVideoComposition *)videoComposition shouldContinueValidatingAfterFindingInvalidValueForKey:(NSString *)key
{
NSLog(@"%s===%@",__func__,key);
return YES;
} - (BOOL)videoComposition:(AVVideoComposition *)videoComposition shouldContinueValidatingAfterFindingEmptyTimeRange:(CMTimeRange)timeRange
{
NSLog(@"%s===%@",__func__,CFBridgingRelease(CMTimeRangeCopyDescription(kCFAllocatorDefault, timeRange)));
return YES;
} - (BOOL)videoComposition:(AVVideoComposition *)videoComposition shouldContinueValidatingAfterFindingInvalidTimeRangeInInstruction:(id<AVVideoCompositionInstruction>)videoCompositionInstruction
{
NSLog(@"%s===%@",__func__,videoCompositionInstruction);
return YES;
} - (BOOL)videoComposition:(AVVideoComposition *)videoComposition shouldContinueValidatingAfterFindingInvalidTrackIDInInstruction:(id<AVVideoCompositionInstruction>)videoCompositionInstruction layerInstruction:(AVVideoCompositionLayerInstruction *)layerInstruction asset:(AVAsset *)asset
{
NSLog(@"%s===%@===%@",__func__,layerInstruction,asset);
return YES;
}

重新运行一下发现第三个方法打印输出,就是instruction的timeRange问题了,又重新看了一下两个instruction的设置问题,感觉又没什么问题,代码如下:


- (void)buildCompositionWithAssets:(NSArray *)assetsArray
{
for (int i = 0; i < assetsArray.count; i++) {
AVURLAsset *asset = assetsArray[i];
NSArray *videoTracks = [asset tracksWithMediaType:AVMediaTypeVideo];
NSArray *audioTracks = [asset tracksWithMediaType:AVMediaTypeAudio]; AVAssetTrack *videoTrack = videoTracks[0];
AVAssetTrack *audioTrack = audioTracks[0];
NSError *error = nil;
AVMutableCompositionTrack *videoT = [self.avcompostions.mutableComps addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
AVMutableCompositionTrack *audioT = [self.avcompostions.mutableComps addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
[videoT insertTimeRange:videoTrack.timeRange ofTrack:videoTrack atTime:self.offsetTime error:&error];
[audioT insertTimeRange:audioTrack.timeRange ofTrack:audioTrack atTime:self.offsetTime error:nil];
NSAssert(!error, @"insert error = %@",error);
AVMutableVideoCompositionInstruction *instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
AVMutableVideoCompositionLayerInstruction *layerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoT];
instruction.layerInstructions = @[layerInstruction];
instruction.timeRange = CMTimeRangeMake(self.offsetTime, asset.duration);
[self.instrucionArray addObject:instruction];
self.offsetTime = CMTimeAdd(self.offsetTime,asset.duration);
}
}

这个数组里面会有两个加载好的AVAsset对象,加载AVAsset的代码如下:


- (void)loadAssetFromPath:(NSArray *)paths
{
NSMutableArray *assetsArray = [NSMutableArray arrayWithCapacity:paths.count];
dispatch_group_t dispatchGroup = dispatch_group_create();
for (int i = 0; i < paths.count; i++) {
NSString *path = paths[i];
//first find from cache
AVAsset *asset = [self.assetsCache objectForKey:path];
if (!asset) {
NSDictionary *inputOptions = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:AVURLAssetPreferPreciseDurationAndTimingKey];
asset = [AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath:path] options:inputOptions];
// cache asset
NSAssert(asset != nil, @"Can't create asset from path", path);
NSArray *loadKeys = @[@"tracks", @"duration", @"composable"];
[self loadAsset:asset withKeys:loadKeys usingDispatchGroup:dispatchGroup];
[self.assetsCache setObject:asset forKey:path];
}else {
}
[assetsArray addObject:asset];
}
dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^{
!self.assetLoadBlock?:self.assetLoadBlock(assetsArray); });
} - (void)loadAsset:(AVAsset *)asset withKeys:(NSArray *)assetKeysToLoad usingDispatchGroup:(dispatch_group_t)dispatchGroup
{
dispatch_group_enter(dispatchGroup);
[asset loadValuesAsynchronouslyForKeys:assetKeysToLoad completionHandler:^(){
for (NSString *key in assetKeysToLoad) {
NSError *error; if ([asset statusOfValueForKey:key error:&error] == AVKeyValueStatusFailed) {
NSLog(@"Key value loading failed for key:%@ with error: %@", key, error);
goto bail;
}
}
if (![asset isComposable]) {
NSLog(@"Asset is not composable");
goto bail;
}
bail:
dispatch_group_leave(dispatchGroup);
}];
}

按照正常来说,应该不会有什么问题的,但是问题还是来了。打印出创建的两个AVMutableVideoCompositionInstruction的信息,发现他们的timeRange确实有重合的地方。AVMutableVideoCompositionInstruction的timeRange必须对应AVMutableCompositionTrack里面的一段段插入的track的timeRange。开发文档是这么说的:

to report a video composition instruction with a timeRange that's invalid, that overlaps with the timeRange of a prior instruction, or that contains times earlier than the timeRange of a prior instruction

我在插入的时候使用的是videoTrack.timeRange,而设置instruction.timeRange = CMTimeRangeMake(self.offsetTime, asset.duration);使用的是asset.duration。这个两个竟然是不一样的。asset.duration是{59885/1000 = 59.885},videoTrack.timeRange是{{0/1000 = 0.000}, {59867/1000 = 59.867}}

这两个时长有细微差别,到底哪个是比较准确一点的呢?

视频文件时长的计算

笔者在demo中使用的是mp4格式的文件,mp4文件是若干个不同的类型box组成的,box可以理解为装有数据的容器。其中有一种moov类型的box里面装有视频播放的元数据(metadata),这里面有视频的时长信息:timescale和duration。 duration / timescale = 可播放时长(s)。从mvhd中读到timescal为0x03e8,duration为0xe9ed,也就是59885 / 1000,为59.885。再看看tkhd box里面的内容,这里面是包含单一track的信息。如下图:

从上图可以看出videotrack和audiotrack两个的时长是不一样的,videotrack为0xe9db,也就是59.867,audiotrack为0xe9ed,就是59.885。所以我们在计算timeRange的时候最好统一使用相对精确一点的videotrack,而不要AVAsset的duration,尽量避免时间上的误差,视频精细化的编辑,对这些误差敏感。

一次“Error Domain=AVFoundationErrorDomain Code=-11841”的调试的更多相关文章

  1. Error Domain=AVFoundationErrorDomain Code=-11800 "这项操作无法完成"

    在iOS上开发视频操作的时候,出现错误: 录制视频错误:Error Domain=AVFoundationErrorDomain Code=-11800 "这项操作无法完成" Us ...

  2. iOS json 解析遇到error: Error Domain=NSCocoaErrorDomain Code=3840 "The operation couldn’t be completed.

    Error Domain=NSCocoaErrorDomain Code=3840 "The operation couldn’t be completed. (Cocoa error 38 ...

  3. [ios] 定位报错Error Domain=kCLErrorDomain Code=0 "The operation couldn’t be completed. (kCLErrorDomain error 0.)"

    Error Domain=kCLErrorDomain Code=0 "The operation couldn’t be completed. (kCLErrorDomain error ...

  4. IOS8解决获取位置坐标信息出错(Error Domain=kCLErrorDomain Code=0)(转)

    标题:IOS8解决获取位置坐标信息出错(Error Domain=kCLErrorDomain Code=0) 前几天解决了在ios8上无法使用地址位置服务的问题,最近在模拟器上调试发现获取位置坐标信 ...

  5. ShareSDK 集成 Google+ 登录 400. Error:redirect_uri_mismatch 和 Error Domain=ShareSDKErrorDomain Code=204

    最近在集成ShareSDK中 Google+ 登录授权时候 出现了如下几个问题 1.    400.  Error:redirect_uri_mismatch 出现这种情况, redirectUri应 ...

  6. Error Domain=ASIHTTPRequestErrorDomain Code=8 "Failed to move file from"xxx/xxx"to"xxx/xxx"

    今天真的好高兴呀 我解决了一个折磨了我一周的问题,真的是激动地要哭出来了,为了这个问题,我嘴也烂了,头发抓了一地啊.虽然解决方法,最后还是展现出了“百度”的伟大,但是我还是很开心,在这里我展示一下我的 ...

  7. iOS解析JSON字符串报错Error Domain=NSCocoaErrorDomain Code=3840 "Invalid escape sequence around character 586."

    将服务器返回的JSON string转化成字典时报错: Error Domain=NSCocoaErrorDomain Code=3840 "Invalid escape sequence ...

  8. 关于https的Error:Error Domain=NSURLErrorDomain Code=-1012

    昨天闲着没事就随便搞点demo,随便找了一个https的接口,运行之后,一直发现Error Domain=NSURLErrorDomain Code=-1012.好奇怪,请求https的配置我基本都配 ...

  9. IOS8解决获取位置坐标信息出错(Error Domain=kCLErrorDomain Code=0)

    最近在模拟器上调试发现获取位置坐标信息的时候会报错,错误信息: didFailWithError: Error Domain=kCLErrorDomain Code=0 “The operation ...

随机推荐

  1. Spring Data MongoDB 级联操作

    DBRef 方式关联 DBRef 就是在两个Collection之间定义的一个关联关系,暂不支持级联的保存功能 例子:一个Person对象有多个Book对象,一对多关系 实体Person public ...

  2. Java中的数据类型转换

    先来看一个题: Java类Demo中存在方法func0.func1.func2.func3和func4,请问该方法中,哪些是不合法的定义?( ) public class Demo{ float fu ...

  3. LaTeX 使用:itemize,enumerate,description 用法

    itemize和enumerate还有description 是LaTeX里列举的三种样式,分别讲一些使用技巧.itemize(意为分条目): \begin{itemize} \item[*] a \ ...

  4. 引入 Tinker 之后如何在 Debug 模式下开启 Instant Run

    在<Tinker + Bugly + Jenkins 爬坑之路>一文中讲了在接入 Tinker 之后,Jenkins 中的一些坑,由此,热修复算告一段落,但是,在直接 Run 模式运行时, ...

  5. Android深入四大组件(四)Android8.0 根Activity启动过程(前篇)

    前言 在几个月前我写了Android深入四大组件(一)应用程序启动过程(前篇)和Android深入四大组件(一)应用程序启动过程(后篇)这两篇文章,它们都是基于Android 7.0,当我开始阅读An ...

  6. Linux修改Oracle用戶

    Linux下SSH登陆后: su - Oracle; sqlplus /nolog; conn system/密码; 或者 connect/as sysdba; alter user 用户名 iden ...

  7. 对数损失函数(Logarithmic Loss Function)的原理和 Python 实现

    原理 对数损失, 即对数似然损失(Log-likelihood Loss), 也称逻辑斯谛回归损失(Logistic Loss)或交叉熵损失(cross-entropy Loss), 是在概率估计上定 ...

  8. java "Too small initial heap" 错误

    Tomcat内存配置 JAVA_OPTS="-server -Duser.timezone=GMT+08-Xms1024m -Xmx1024m -XX:PermSize=1024m -Xmn ...

  9. iOS设计模式 - 装饰

    iOS设计模式 - 装饰 原理图 说明 1. cocoa框架本身实现了装饰模式(category的方式实现了装饰模式) 2. 装饰模式指的是动态的给一个对象添加一些额外的职责,相对于继承子类来说,装饰 ...

  10. windows系统镜像 微软官方资源便捷下载教程

    今天跟小师弟学到了一个下载软件的好办法,省得到各种网站下载带有病毒,插件的资源. 这个神奇的网站叫做   MSDN, 我告诉你,这是一个私人维护的网站,里面有各种官方软件的下载地址.可以直接用下载工具 ...