AVFoundation: Fit Video to CALayer correctly when exporting

徘徊边缘 提交于 2020-05-28 03:29:32

问题


Problem:

I'm having issues getting videos I'm creating with AVFoundation to show in the VideoLayer, a CALayer, with correct dimensions.

Example:

Here is what the video should look like (as its displayed to the user in the app)

However, here's the resulting video when it's exported:

Details

As you can see, its meant to be a square video, with green background, with the video fitting to a specified frame. However, the resulting video doesn't fit the CALayer used to contain it (see the black space where the video should be stretched to?).

Sometimes the video does fill the layer but is stretched beyond the bounds (either too much width or too much height) and often doesn't maintain the natural aspect radio of the video.

Code

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();
         }
     });
 }];

What I've tried:

I tried adjusting the videoLayer's frame, bounds, and contentGravity which did nothing of use.

I tried adding a transform to the AVMutableVideoCompositionLayerInstruction to scale the video to the size of the displayRect (many different videos can be chosen from, and their width and height are variable. Each video shows differently in the resulting video, none of them correctly) Transforming would sometimes get one dimension right (usually the width), but mess up the other one. And it would never get one dimension consistently right if I cropped/scaled the video in a slightly different way.

I've tried changing the renderSize of the videoComposition but that ruins the square crop.

I can't seem to get it right. How can I get the video to perfectly fill the videoLayer with the displayedFrame frame (final note: the naturalSize of the video differs from the displayedFrame which is why I tried transforming it)?


回答1:


When the video is rendered in your videoLayer, it has an implicit transform t applied (we don't have access to it, but that's some initial transform that the render tool internally applies to the video). To make the video exactly fill that layer on export, we have to understand where that initial transform comes from. t shows a strange behavior: It is dependent on the renderSize of your video composition (In your example that would be a square). You can see that if you set the renderSize to anything else, the scale and aspect ratio of the video rendered in videoLayer changes too - even if you didn't change the videoLayer's frame at all. I don't see how this behavior would make sense (the frame of the composition and the frame of the video layer that's part of the composition should be completely independent), so I think it's a bug in AVVideoCompositionCoreAnimationTool.

To correct the ominous behavior of t, apply the following transform to your videoInstruction:

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

The video will then exactly fill videoLayer.

If the video doesn't have the standard orientation, two more transforms have to be applied to fix the orientation and scale:

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)



回答2:


You are facing this problem because of aspect ratio in which camera records. While recording you fill your AVCaptureVideoPreviewLayer by setting content gravity, but preview layer has nothing to do with aspect ratio in which camera records. Its just sets what you see when you record. From the picture you posted it can be figured out that camera is recording in 16:9 ratio. So either you have to set the layer's bound in this ratio or play with the frames of your video to transform them. For second approach, you need to use - (void)setTransform:(CGAffineTransform)transform atTime:(CMTime)time on your AVMutableVideoCompositionLayerInstruction.

You have to form the transform yourself according to your layer bounds. Its a bit of try and error because it will be combination of some CGAffineTransformMakeTranslation and CGAffineTransformMakeScale. So for the case in the posted picture, you need to scale up and translate. If bounds go out then you have to scale down.




回答3:


Swift 3

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


来源:https://stackoverflow.com/questions/32211261/avfoundation-fit-video-to-calayer-correctly-when-exporting

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!