Search code examples
iosswiftavfoundationcifilteravvideocomposition

Video rotated after applying AVVideoComposition


After applying an AVVideoComposition to my AVPlayerItem, the filter I apply does work, but the video gets rotated in the AVPlayerLayer.

I know for a fact that the problem is not with the filtered frame because if I show the frame in a UIImageView, the frame is rendered 100% correctly.

The video shows correctly until I apply a videoComposition. Setting the videoGravity on the AVPlayerLayer does not help.

The video gets rotated 90º clockwise and gets stretched in the layer.

Essentially, the video is displayed perfectly in the AVPlayerLayer before the AVPlayerItem is fed through the AVMutableVideoComposition. Once that happens, the video is rotated -90º, and then scaled to fit the same dimensions as the video before filtering. This suggests to me that it does not realize that its transform is already correct, and so it is reapplying the transform on itself.

Why is this happening, and how can I fix it?

Here is some code:

private func filterVideo(with filter: Filter?) {
    if let player = player, let playerItem = player.currentItem {
        let composition = AVMutableComposition()
        let videoAssetTrack = playerItem.asset.tracks(withMediaType: .video).first
        let videoCompositionTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)
        try? videoCompositionTrack?.insertTimeRange(CMTimeRange(start: kCMTimeZero, duration: playerItem.asset.duration), of: videoAssetTrack!, at: kCMTimeZero)
        videoCompositionTrack?.preferredTransform = videoAssetTrack!.preferredTransform

        let videoComposition = AVMutableVideoComposition(asset: composition, applyingCIFiltersWithHandler: { (request) in
            let filteredImage = <...>
            request.finish(with: filteredImage, context: nil)
        })

        playerItem.videoComposition = videoComposition
    }
}

Solution

  • What worked for me at the end:

    private func filterVideo(with filter: Filter?) {
        guard let player = playerLayer?.player, let playerItem = player.currentItem else { return }
    
        let videoComposition = AVVideoComposition(asset: playerItem.asset, applyingCIFiltersWithHandler: { (request) in
            if let filter = filter {
                if let filteredImage = filter.filterImage(request.sourceImage) {
                    let output = filteredImage.cropping(to: request.sourceImage.extent)
                    request.finish(with: output, context: nil)
                } else {
                    printError("Image not filtered")
                    request.finish(with: RenderError.couldNotFilter)
                }
            } else {
                let output = request.sourceImage.cropping(to: request.sourceImage.extent)
                request.finish(with: output, context: nil)
            }
        })
    
        playerItem.videoComposition = videoComposition
    }
    

    This is the filterImage function of Filter, which is just a nice little wrapper for CIFilter:

    func filterImage(_ ciImage: CIImage) -> CIImage? {
        guard let filter = ciFilter else { return nil }
        filter.setDefaults()
        filter.setValue(ciImage, forKey: kCIInputImageKey)
        guard let filteredImageData = filter.value(forKey: kCIOutputImageKey) as? CIImage else { return nil }
        return filteredImageData
    }