AVPlayer 播放视频合成结果不正确
AVPlayer plays video composition result incorrectly
我需要一个简单的东西:在旋转视频的同时播放视频并在其上应用 CIFilter
。
首先,我创建播放器项目:
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));
然后我需要应用视频合成。最初,我想创建一个包含 2 条指令的 AVVideoComposition
- 一条是用于轮换的 AVVideoCompositionLayerInstruction
,另一条是 CIFilter
应用程序。但是,我抛出一个异常 "Expecting video composition to contain only AVCoreImageFilterVideoCompositionInstruction" ,这意味着 Apple 不允许组合这两条指令。结果,我在过滤下结合了两者,这里是代码:
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;
我在调试期间制作的屏幕截图显示图像已成功旋转并应用了滤镜(在本例中它是一个不改变图像的身份滤镜)。以下是在上述评论中标记的位置截取的屏幕截图1和屏幕截图2:
如您所见,旋转成功,生成的框架的范围也正确。
当我尝试在播放器中播放此视频时出现问题。这是我得到的:
所以似乎所有的帧都被缩放并向下移动了。绿色区域是空帧信息,当我钳制到一定范围以使帧无限大时,它显示边框像素而不是绿色。我有一种感觉,玩家在 AVPlayerItem
旋转之前仍然需要一些旧的尺寸信息,这就是为什么在上面的第一个代码片段中我记录了尺寸和变换,有日志:
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] 方向并先前保存在设备存储中。该应用程序在纵向模式下录制的视频完全没问题(顺便说一下,纵向视频的大小和转换日志与横向视频完全相同!奇怪...也许 iphone 将旋转信息放在视频中并修复它)。所以缩放和移动视频看起来像是 "aspect fill" 和旋转前旧分辨率信息的组合。顺便说一句,由于缩放以填充具有不同纵横比的播放器区域,纵向视频帧会部分显示,但这是预期的行为。
让我知道您对此的想法,如果您知道如何更好地完成我所需要的,那么我会很高兴知道。
更新: 有一种更简单的方法可以在播放期间 "change" AVPlayerItem
视频尺寸 - 设置 renderSize
属性 的视频合成(可以使用 AVMutableVideoComposition
class)。
下面是我的旧答案:
经过大量调试,我理解了问题并找到了解决方案。我最初的猜测 AVPlayer 仍然认为视频的原始大小 是正确的。在下图中解释了发生了什么:
至于解决方案,我找不到在 AVAsset
或 AVPlayerItem
中更改视频大小的方法。所以我只是操纵视频以适应 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;
我需要一个简单的东西:在旋转视频的同时播放视频并在其上应用 CIFilter
。
首先,我创建播放器项目:
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));
然后我需要应用视频合成。最初,我想创建一个包含 2 条指令的 AVVideoComposition
- 一条是用于轮换的 AVVideoCompositionLayerInstruction
,另一条是 CIFilter
应用程序。但是,我抛出一个异常 "Expecting video composition to contain only AVCoreImageFilterVideoCompositionInstruction" ,这意味着 Apple 不允许组合这两条指令。结果,我在过滤下结合了两者,这里是代码:
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;
我在调试期间制作的屏幕截图显示图像已成功旋转并应用了滤镜(在本例中它是一个不改变图像的身份滤镜)。以下是在上述评论中标记的位置截取的屏幕截图1和屏幕截图2:
如您所见,旋转成功,生成的框架的范围也正确。
当我尝试在播放器中播放此视频时出现问题。这是我得到的:
所以似乎所有的帧都被缩放并向下移动了。绿色区域是空帧信息,当我钳制到一定范围以使帧无限大时,它显示边框像素而不是绿色。我有一种感觉,玩家在 AVPlayerItem
旋转之前仍然需要一些旧的尺寸信息,这就是为什么在上面的第一个代码片段中我记录了尺寸和变换,有日志:
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] 方向并先前保存在设备存储中。该应用程序在纵向模式下录制的视频完全没问题(顺便说一下,纵向视频的大小和转换日志与横向视频完全相同!奇怪...也许 iphone 将旋转信息放在视频中并修复它)。所以缩放和移动视频看起来像是 "aspect fill" 和旋转前旧分辨率信息的组合。顺便说一句,由于缩放以填充具有不同纵横比的播放器区域,纵向视频帧会部分显示,但这是预期的行为。
让我知道您对此的想法,如果您知道如何更好地完成我所需要的,那么我会很高兴知道。
更新: 有一种更简单的方法可以在播放期间 "change" AVPlayerItem
视频尺寸 - 设置 renderSize
属性 的视频合成(可以使用 AVMutableVideoComposition
class)。
下面是我的旧答案:
经过大量调试,我理解了问题并找到了解决方案。我最初的猜测 AVPlayer 仍然认为视频的原始大小 是正确的。在下图中解释了发生了什么:
至于解决方案,我找不到在 AVAsset
或 AVPlayerItem
中更改视频大小的方法。所以我只是操纵视频以适应 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;