问题
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