AVPlayer不正确地播放video合成结果

我需要一个简单的东西: 在旋转和应用CIFilter同时播放video。

首先,我创build玩家项目:

 AVPlayerItem *item = [AVPlayerItem playerItemWithURL:videoURL]; // DEBUG LOGGING AVAssetTrack *track = [[item.asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]; NSLog(@"Natural size is: %@", NSStringFromCGSize(track.naturalSize)); NSLog(@"Preffered track transform is: %@", NSStringFromCGAffineTransform(track.preferredTransform)); NSLog(@"Preffered asset transform is: %@", NSStringFromCGAffineTransform(item.asset.preferredTransform)); 

然后我需要应用video作品。 最初,我正在考虑用2条指令创build一个AVVideoComposition ,一个是AVVideoCompositionLayerInstruction ,另一个是CIFilter应用程序。 然而,我得到一个exception抛出说: “期望video组成只包含AVCoreImageFilterVideoCompositionInstruction”这意味着苹果不允许结合这两个指令。 所以,我把这两个过滤结合起来,这里是代码:

 AVAsset *asset = playerItem.asset; CGAffineTransform rotation = [self transformForItem:playerItem]; AVVideoComposition *composition = [AVVideoComposition videoCompositionWithAsset:asset applyingCIFiltersWithHandler:^(AVAsynchronousCIImageFilteringRequest * _Nonnull request) { // Step 1: get the input frame image (screenshot 1) CIImage *sourceImage = request.sourceImage; // Step 2: rotate the frame CIFilter *transformFilter = [CIFilter filterWithName:@"CIAffineTransform"]; [transformFilter setValue:sourceImage forKey: kCIInputImageKey]; [transformFilter setValue: [NSValue valueWithCGAffineTransform: rotation] forKey: kCIInputTransformKey]; sourceImage = transformFilter.outputImage; CGRect extent = sourceImage.extent; CGAffineTransform translation = CGAffineTransformMakeTranslation(-extent.origin.x, -extent.origin.y); [transformFilter setValue:sourceImage forKey: kCIInputImageKey]; [transformFilter setValue: [NSValue valueWithCGAffineTransform: translation] forKey: kCIInputTransformKey]; sourceImage = transformFilter.outputImage; // Step 3: apply the custom filter chosen by the user extent = sourceImage.extent; sourceImage = [sourceImage imageByClampingToExtent]; [filter setValue:sourceImage forKey:kCIInputImageKey]; sourceImage = filter.outputImage; sourceImage = [sourceImage imageByCroppingToRect:extent]; // Step 4: finish processing the frame (screenshot 2) [request finishWithImage:sourceImage context:nil]; }]; playerItem.videoComposition = composition; 

我在debugging过程中所做的屏幕截图显示,图像被成功旋转并应用了filter(在本例中,它是一个不改变图像的身份filter)。 以下是截图1和截图2在上面注释中标出的点:

在这里输入图像说明

在这里输入图像说明

正如你所看到的,旋转是成功的,结果帧的范围也是正确的。

当我尝试在播放器中播放此video时,问题就开始了。 这是我得到的:

在这里输入图像说明

所以好像所有的框架都缩小了。 绿色区域是空的框架信息,当我钳制到使框架无限大的程度时,它显示边界像素而不是绿色。 我有一种感觉,在AVPlayerItem旋转之前,播放器仍然需要一些旧的尺寸信息,这就是为什么在上面的第一个代码片段中,我logging了尺寸和变换,还有日志:

 Natural size is: {1920, 1080} Preffered track transform is: [0, 1, -1, 0, 1080, 0] Preffered asset transform is: [1, 0, 0, 1, 0, 0] 

玩家是这样设置的:

 layer.videoGravity = AVLayerVideoGravityResizeAspectFill; layer.needsDisplayOnBoundsChange = YES; 

请注意最重要的事情:这只发生在应用程序本身使用横向 iPhone [6s]方向拍摄并保存在设备存储器中的video。 应用程序在肖像模式下logging的video是完全正常的(顺便说一句,肖像video得到完全相同的大小和变换日志像风景video!奇怪…也许iphone把旋转信息在video中,并修复它)。 因此,缩放和移动video看起来像旋转之前“方面填充”和旧分辨率信息的组合。 顺便说一下,纵向video帧的显示部分是因为缩放以填充具有不同纵横比的播放器区域,但这是预期的行为。

让我知道你的想法,如果你知道如何完成我所需要的更好的方法,那么知道这件事就太好了。

更新:在回放过程中,“更改” AVPlayerItemvideo尺寸变得更容易 – 设置video合成的renderSize属性(可以使用AVMutableVideoComposition类来完成)。

我下面的答案:


经过大量的debugging,我明白了这个问题,并find了一个解决scheme。 我最初的猜测是, AVPlayer仍然认为原始大小的video是正确的。 在下面的图片中解释了发生了什么:

在这里输入图像说明

至于解决scheme,我找不到一种方法来更改AVAssetAVPlayerItem内的video大小。 所以我只是操纵video来适应AVPlayer所期望的尺寸和比例,然后在玩家使用正确的长宽比和标志进行缩放和填充玩家区域时,一切都看起来不错。 这里是图解说明:

在这里输入图像说明

这里附加的代码需要被插入在问题中提到的applyingCIFiltersWithHandler块中:

 ... after Step 3 in the question codes above // make the frame the same aspect ratio as the original input frame // by adding empty spaces at the top and the bottom of the extent rectangle CGFloat newHeight = originalExtent.size.height * originalExtent.size.height / extent.size.height; CGFloat inset = (extent.size.height - newHeight) / 2; extent = CGRectInset(extent, 0, inset); sourceImage = [sourceImage imageByCroppingToRect:extent]; // scale down to the original frame size CGFloat scale = originalExtent.size.height / newHeight; CGAffineTransform scaleTransform = CGAffineTransformMakeScale(scale, scale); [transformFilter setValue:sourceImage forKey: kCIInputImageKey]; [transformFilter setValue: [NSValue valueWithCGAffineTransform: scaleTransform] forKey: kCIInputTransformKey]; sourceImage = transformFilter.outputImage; // translate the frame to make it's origin start at (0, 0) CGAffineTransform translation = CGAffineTransformMakeTranslation(0, -inset * scale); [transformFilter setValue:sourceImage forKey: kCIInputImageKey]; [transformFilter setValue: [NSValue valueWithCGAffineTransform: translation] forKey: kCIInputTransformKey]; sourceImage = transformFilter.outputImage;