Search code examples
iosswiftavvideocompositionavcomposition

Equal-Power Crossfade in AVMutableVideoCompositionLayerInstruction


How can I get an equal-power crossfade working in an AVVideoComposition? I'm using something like the following to fade between video tracks, but when looping the same video over and over there is a very noticeable brightness dip during the transition due to whatever curve is being used internally in setOpacityRamp.

let videoInstruction = AVMutableVideoCompositionInstruction()

let fromLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: fromCompositionTrack)
fromLayerInstruction.setOpacityRamp(fromStartOpacity: 1, toEndOpacity: 0, timeRange: timeRange)

let toLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: toCompositionTrack)
toLayerInstruction.setOpacityRamp(fromStartOpacity: 0, toEndOpacity: 1, timeRange: timeRange)

videoInstruction.timeRange = timeRange
videoInstruction.layerInstructions = [fromLayerInstruction, toLayerInstruction]

Solution

  • This isn’t possible. The setOpacityRamp methods for AVMutableVideoCompositionLayerInstruction lists the following description:

    During an opacity ramp, opacity is computed using a linear interpolation.

    There is an easier solution, though, which I found in the book Learning AV Foundation: A Hands-on Guide to Mastering the AV Foundation — fade out the current video track, but don’t fade in the new one. The book refers to this as a plain dissolve rather than a cross dissolve.

    let videoInstruction = AVMutableVideoCompositionInstruction()
    
    let fromLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: fromCompositionTrack)
    fromLayerInstruction.setOpacityRamp(fromStartOpacity: 1, toEndOpacity: 0, timeRange: timeRange)
    
    let toLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: toCompositionTrack)
    // toLayerInstruction.setOpacityRamp(fromStartOpacity: 0, toEndOpacity: 1, timeRange: timeRange)
    
    videoInstruction.timeRange = timeRange
    videoInstruction.layerInstructions = [fromLayerInstruction, toLayerInstruction]