AVFoundation:导出时将视频正确适配到CALayer

5

问题:

我使用AVFoundation创建视频时,无法使视频正确地显示在一个CALayer中,以适合指定的尺寸。

示例:

这是视频应该呈现给用户的样子(在应用程序中):

enter image description here

但是,当导出视频时,结果如下所示:

enter image description here

详情

你可以看到,它应该是一个方形视频,绿色背景,视频适合指定框架。但是,生成的视频不适合用于包含它的CALayer(请看视频应该被拉伸到的黑色空间)。有时视频会填充图层,但超出范围(宽度或高度过大),并且通常不能保持视频的自然比例。

代码

CGRect displayedFrame = [self adjustedVideoBoundsFromVideo:gifVideo];//the cropped frame
CGRect renderFrame = [self renderSizeForGifVideo:gifVideo]; //the full rendersize
AVAsset * originalAsset = self.videoAsset;

AVAssetTrack * videoTrack = [[originalAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
AVMutableComposition * mainComposition = [AVMutableComposition composition];

AVMutableCompositionTrack * compositionTrack = [mainComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];

[compositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, originalAsset.duration) ofTrack:videoTrack atTime:kCMTimeZero error:nil];

CALayer * parentLayer = [CALayer layer];
CALayer * backgroundLayer = [CALayer layer];
CALayer * videoLayer = [CALayer layer];
parentLayer.frame = renderFrame;
backgroundLayer.frame = parentLayer.bounds;
backgroundLayer.backgroundColor = self.backgroundColor.CGColor;
videoLayer.frame = displayedFrame;
[parentLayer addSublayer:backgroundLayer];
[parentLayer addSublayer:videoLayer];


AVMutableVideoComposition * videoComposition = [AVMutableVideoComposition videoComposition];
videoComposition.frameDuration = CMTimeMake(1, 30);
videoComposition.renderSize = CGSizeMake(renderFrame.size.width, renderFrame.size.height);




videoComposition.animationTool = [AVVideoCompositionCoreAnimationTool
                         videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer];



AVMutableVideoCompositionInstruction * instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
instruction.timeRange = CMTimeRangeMake(kCMTimeZero, mainComposition.duration);

AVMutableVideoCompositionLayerInstruction * layerInstruction = [AVMutableVideoCompositionLayerInstruction
                                                                videoCompositionLayerInstructionWithAssetTrack:videoTrack];


instruction.layerInstructions = @[layerInstruction];
videoComposition.instructions = @[instruction];



NSString* videoName = @"myNewGifVideo.mp4";

NSString *exportPath = [NSTemporaryDirectory() stringByAppendingPathComponent:videoName];
NSURL    *exportUrl = [NSURL fileURLWithPath:exportPath];
if ([[NSFileManager defaultManager] fileExistsAtPath:exportPath])
{
    [[NSFileManager defaultManager] removeItemAtPath:exportPath error:nil];
}

AVAssetExportSession * exporter = [[AVAssetExportSession alloc] initWithAsset:mainComposition presetName:AVAssetExportPresetHighestQuality];
exporter.videoComposition = videoComposition;
exporter.outputFileType = AVFileTypeMPEG4;
exporter.outputURL = exportUrl;

[exporter exportAsynchronouslyWithCompletionHandler:^
 {
     dispatch_async(dispatch_get_main_queue(), ^{
         self.finalVideo = exportUrl;
         [self.delegate shareManager:self didCreateVideo:self.finalVideo];
         if (completionBlock){
             completionBlock();
         }
     });
 }];

我尝试过的方法:

我尝试了调整videoLayerframeboundscontentGravity,但都没有用。

我尝试在AVMutableVideoCompositionLayerInstruction中添加transform来将视频缩放到displayRect的大小(可以选择许多不同的视频,它们的宽度和高度是可变的。每个视频在生成的视频中显示不同,没有一个正确的)。有时候转换会使一个维度正确(通常是宽度),但会搞砸另一个维度。如果我稍微以不同的方式裁剪/缩放视频,它就永远无法保持一个维度的一致性。

我尝试更改videoCompositionrenderSize,但这会破坏正方形裁剪。

我似乎做不好。如何使视频完美填充videoLayerdisplayedFrame帧(最后注意:视频的naturalSizedisplayedFrame不同,这就是我尝试转换它的原因)?

3个回答

13
当视频在您的videoLayer中进行渲染时,它会应用一个隐式的变换t(我们无法访问它,但这是渲染工具内部应用于视频的一些初始变换)。为了使视频在导出时完全填充该图层,我们必须了解该初始变换来自何处。t表现出奇怪的行为:它依赖于视频合成的renderSize(在您的示例中,这将是一个正方形)。如果您将renderSize设置为其他任何值,则即使您根本没有更改videoLayerframevideoLayer中呈现的视频的比例和纵横比也会发生变化。我不明白这种行为怎么讲得通(合成的帧和作为合成一部分的视频图层的帧应该是完全独立的),因此我认为这是AVVideoCompositionCoreAnimationTool中的一个bug。
为了纠正t的可怕行为,请将以下变换应用于您的videoInstruction:
let bugFixTransform = CGAffineTransform(scaleX: renderSize.width/videoTrack.naturalSize.width,
                                        y: renderSize.height/videoTrack.naturalSize.height)
videoLayerInstruction.setTransform(bugFixTransform, at: .zero)

视频将会完全填充到videoLayer中。
如果视频方向不标准,还需要应用两个转换来修正方向和比例:
let orientationAspectTransform: CGAffineTransform
let sourceVideoIsRotated: Bool = videoTrack.preferredTransform.a == 0
if sourceVideoIsRotated {
  orientationAspectTransform = CGAffineTransform(scaleX: videoTrack.naturalSize.width/videoTrack.naturalSize.height,
                                                 y: videoTrack.naturalSize.height/videoTrack.naturalSize.width)
} else {
  orientationAspectTransform = .identity
}

let bugFixTransform = CGAffineTransform(scaleX: compositionSize.width/videoTrack.naturalSize.width,
                                        y: compositionSize.height/videoTrack.naturalSize.height)
let transform =
  videoTrack.preferredTransform
    .concatenating(bugFixTransform)
    .concatenating(orientationAspectTransform)
videoLayerInstruction.setTransform(transform, at: .zero)

更新:如果你正在使用preferredTransform,请注意该方法也存在问题(包括iOS 14),在某些情况下可能会导致意外的转换。请参考这个问题以获取描述和解决方法。


3
Theo,你是我绝对的英雄!!+1 - brontea

2
您面临的问题是由于相机记录的宽高比。在录制时,通过设置内容重力来填充您的 AVCaptureVideoPreviewLayer,但预览层与相机记录的宽高比无关。它只是设置您录制时所看到的内容。从您发布的图片可以看出,相机以16:9的比例进行记录。因此,您需要将该图层的边界设置为此比例,或者调整视频帧来进行转换。
对于第二种方法,您需要在 AVMutableVideoCompositionLayerInstruction 上使用 - (void)setTransform:(CGAffineTransform)transform atTime:(CMTime)time
您需要根据图层边界自行形成变换。这需要一些尝试和错误,因为它将是一些 CGAffineTransformMakeTranslationCGAffineTransformMakeScale 的组合。因此,在发布的图片中,您需要进行缩放和平移。如果边界超出范围,则需要进行缩小。

-1

Swift 3

let mainComposition = AVMutableVideoComposition()
mainComposition.renderSize = CGSize(width: yourWidth, height: yourHeight)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接