AVFoundation:导出时正确地将视频适合 CALayer

Rob*_*way 4 core-animation objective-c avfoundation calayer ios

问题:

我在获取使用 AVFoundation 创建的视频以在 VideoLayer a 中显示CALayer正确尺寸时遇到问题。

例子:

以下是视频的外观(在应用中向用户显示)

在此处输入图片说明

但是,这是导出时的结果视频:

在此处输入图片说明

细节

如您所见,它是一个方形视频,背景为绿色,视频适合指定的帧。但是,生成的视频不适合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();
         }
     });
 }];
Run Code Online (Sandbox Code Playgroud)

我试过的:

我尝试调整videoLayer's frame, bounds, 并contentGravity没有任何用处。

我尝试添加一个transform以将AVMutableVideoCompositionLayerInstruction视频缩放到 的大小displayRect(可以选择许多不同的视频,并且它们的宽度和高度是可变的。每个视频在生成的视频中显示不同,没有一个正确)变换有时会得到一个维度正确(通常是宽度),但弄乱了另一个维度。如果我以稍微不同的方式裁剪/缩放视频,它永远不会始终保持正确的维度。

我试过改变 的renderSizevideoComposition但这会破坏方形作物。

我似乎无法正确理解。我怎样才能获得视频完美地填补了videoLayerdisplayedFrame帧(最终注:naturalSize从视频不同的displayedFrame这就是为什么我试图转换它)?

The*_*heo 11

当视频在您的 中呈现时videoLayer,它t应用了隐式变换(我们无权访问它,但这是渲染工具在内部应用于视频的一些初始变换)。为了使视频在导出时完全填充该层,我们必须了解初始变换的来源。t显示出一种奇怪的行为:它取决于renderSize您的视频合成(在您的示例中为正方形)。您可以看到,如果您将 设置renderSize为其他任何内容,则呈现的视频的比例和纵横比videoLayer也会发生变化 - 即使您没有更改videoLayerframe根本。我不明白这种行为有什么意义(合成的帧和作为合成一部分的视频层的帧应该完全独立),所以我认为这是AVVideoCompositionCoreAnimationTool.

要纠正 的不祥行为t,请将以下转换应用于您的videoInstruction

let bugFixTransform = CGAffineTransform(scaleX: renderSize.width/videoTrack.naturalSize.width,
                                        y: renderSize.height/videoTrack.naturalSize.height)
videoLayerInstruction.setTransform(bugFixTransform, at: .zero)
Run Code Online (Sandbox Code Playgroud)

然后视频将完全填充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)
Run Code Online (Sandbox Code Playgroud)

更新:仅针对您正在使用的情况,请preferredTransform注意,它也已损坏(包括 iOS 14),并且在某些情况下可能会导致意外转换,请参阅此问题以获取说明和解决方法。

  • 西奥,你是我绝对的英雄!+1 (2认同)

bla*_*cos 2

由于相机记录的长宽比,您面临这个问题。录制时,您可以AVCaptureVideoPreviewLayer通过设置内容重力来填充内容,但预览层与相机录制的宽高比无关。它只是设置您录制时看到的内容。从您发布的图片可以看出,相机以16:9的比例进行录制。因此,您要么必须按此比例设置图层边界,要么使用视频帧来转换它们。对于第二种方法,您需要- (void)setTransform:(CGAffineTransform)transform atTime:(CMTime)time在您的AVMutableVideoCompositionLayerInstruction.

您必须根据图层边界自行形成变换。它有点尝试和错误,因为它将是 someCGAffineTransformMakeTranslation和的组合CGAffineTransformMakeScale。因此,对于发布的图片中的情况,您需要放大并翻译。如果超出了界限,那么你就必须缩小规模。