14
votes

I'm trying to bake a CALayer into portrait-mode video (on export) using an AVMutableComposition, an AVMutableVideoComposition and a AVVideoCompositionCoreAnimationTool on iOS 4.3. This all works in landscape. If I capture video in portrait, however, the AVVideoCompositionCoreAnimationTool is ignoring the transform on the video track. That is, for portrait-mode video, I am setting AVMutableCompositionTrack.preferredTransform to the preferredTransform value from the original asset video track. As long as I don't use a AVVideoCompositionCoreAnimationTool, this works, and the video comes out in portrait mode. As soon as I add a AVVideoCompositionCoreAnimationTool and CALayer, however, the file comes out in landscape. (The CALayer appears correctly, but the video behind it is on its side, and the aspect ratio of the file is off). I've tried applying the transform to the CALayer, and setting a transform in the ACVideoComposition. Neither of these change the orientation of the file produced (it is still 480x369, not 360x480). Is there a way to render portrait-mode video with AVVideoCompositionCoreAnimationTool?

First I set up a AVMutableComposition and AVMutableVideoComposition

AVMutableComposition *composition = [AVMutableComposition composition];
AVMutableCompositionTrack *compositionVideoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
AVURLAsset *videoAsset = [AVURLAsset URLAssetWithURL:url options:nil];
CMTimeRange timeRange = CMTimeRangeMake(kCMTimeZero, [videoAsset duration]);
AVAssetTrack *clipVideoTrack = [[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];

CGSize videoSize = CGSizeApplyAffineTransform(clipVideoTrack.naturalSize, clipVideoTrack.preferredTransform);
videoSize.width = fabs(videoSize.width);
videoSize.height = fabs(videoSize.height);

CMTime titleDuration = CMTimeMakeWithSeconds(5, 600);
CMTimeRange titleRange = CMTimeRangeMake(kCMTimeZero, titleDuration);

[compositionVideoTrack insertTimeRange:titleRange ofTrack:nil atTime:kCMTimeZero error:nil];
[compositionVideoTrack insertTimeRange:timeRange ofTrack:clipVideoTrack atTime:titleDuration error:nil];
compositionVideoTrack.preferredTransform = clipVideoTrack.preferredTransform;

AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoComposition];
AVMutableVideoCompositionInstruction *passThroughInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
passThroughInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, [composition duration]);
AVAssetTrack *videoTrack = [[composition tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
AVMutableVideoCompositionLayerInstruction *passThroughLayer = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack];    
passThroughInstruction.layerInstructions = [NSArray arrayWithObject:passThroughLayer];
videoComposition.instructions = [NSArray arrayWithObject:passThroughInstruction];       
videoComposition.frameDuration = CMTimeMake(1, 30); 
videoComposition.renderSize = videoSize;
videoComposition.renderScale = 1.0;

And a CALayer with a title

CALayer *animationLayer = [CALayer layer];
animationLayer.bounds = CGRectMake(0, 0, videoSize.width, videoSize.height);

CATextLayer *titleLayer = [CATextLayer layer];
titleLayer.string = [effect valueForKey:@"title"];
titleLayer.font = [effect valueForKey:@"font"];
titleLayer.fontSize = 30;

titleLayer.alignmentMode = kCAAlignmentCenter;
titleLayer.bounds = CGRectMake(0, 0, videoSize.width, videoSize.height / 6);

[animationLayer addSublayer:titleLayer];
titleLayer.anchorPoint =  CGPointMake(0.5, 0.5);
titleLayer.position = CGPointMake(CGRectGetMidX(layer.bounds), CGRectGetMidY(layer.bounds));    

CABasicAnimation *fadeAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
fadeAnimation.fromValue = [NSNumber numberWithFloat:1.0];
fadeAnimation.toValue = [NSNumber numberWithFloat:0.0];
fadeAnimation.additive = NO;
fadeAnimation.removedOnCompletion = NO;
fadeAnimation.beginTime = 3.5;
fadeAnimation.duration = 1.0;
fadeAnimation.fillMode = kCAFillModeBoth;
[titleLayer addAnimation:fadeAnimation forKey:nil];

Finally I add the CALayer to the AVMutableVideoComposition

CALayer *parentLayer = [CALayer layer];
CALayer *videoLayer = [CALayer layer];

parentLayer.bounds = CGRectMake(0, 0, videoSize.width, videoSize.height);
parentLayer.anchorPoint =  CGPointMake(0, 0);
parentLayer.position = CGPointMake(0, 0);

videoLayer.bounds = CGRectMake(0, 0, videoSize.width, videoSize.height);
[parentLayer addSublayer:videoLayer];
videoLayer.anchorPoint =  CGPointMake(0.5, 0.5);
videoLayer.position = CGPointMake(CGRectGetMidX(parentLayer.bounds), CGRectGetMidY(parentLayer.bounds));
[parentLayer addSublayer:layer];    
animationLayer.anchorPoint =  CGPointMake(0.5, 0.5);
animationLayer.position = CGPointMake(CGRectGetMidX(parentLayer.bounds), CGRectGetMidY(parentLayer.bounds));
videoComposition.animationTool = [AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer];

And export!

AVAssetExportSession *exportSession = [[[AVAssetExportSession alloc] initWithAsset:composition presetName:AVAssetExportPresetMediumQuality] autorelease];
exportSession.videoComposition = videoComposition; 

NSURL *segmentFileURL = //some local URL
exportSession.outputFileType = @"com.apple.quicktime-movie";
exportSession.outputURL = segmentFileURL;


[exportSession exportAsynchronouslyWithCompletionHandler:^{
    switch ([exportSession status]) {
        case AVAssetExportSessionStatusFailed:
            Log(@"Export failed: %@", [exportSession error]);
            break;
        case AVAssetExportSessionStatusCancelled:
            Log(@"Export canceled");
            break;
        case AVAssetExportSessionStatusCompleted:
            Log(@"Export done");
            break;
    }
}]; 

This code works in landscape mode, and also in portrait if I remove the line videoComposition.animationTool = [AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer];

1
I am having the same issue. Did you find a solution to this?Jeremie Weldin
Yes, although I encountered a bunch of other problems with portrait mode compositions as well. The trick IS to set the transform on the AVMutableVideoCompositionLayerInstruction. However, the correct transform is not the asset's preferred transform. Start with the preferred transform, and apply additional translations and flips. You'll find something that works (as a function of the frame size). Unfortunately the correct transform is different for every orientation. Basically, there is a bug in the composition, and we're working around it by positioning the export layer. I did get it to work.wombat57

1 Answers

3
votes

Try this and add your other instructions as well.

///turn video 90 degrees
AVMutableVideoCompositionLayerInstruction *passThroughLayer = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:clipVideoTrack];
CGAffineTransform rotationTransform = CGAffineTransformMakeRotation(degreesToRadians(90.0));
CGAffineTransform rotateTranslate = CGAffineTransformTranslate(rotationTransform,320,0);
[passThroughLayer setTransform:rotateTranslate atTime:kCMTimeZero];

Let me know how it goes!

MacMike