What's causing AVMutableComposition to increase the size of video drastically? - iOS, Swift, AVFoundation

Viewed 1349

Assuming that we have two video assets (AVAsset objects), let's call them blank and main, where main is a video of random limited length, let's say 2-5 minutes, and blank is always a 4 second video, we want to merge the videos in the following order:

blank - main - blank

    // Create AVMutableComposition Object.This object will hold our multiple AVMutableCompositionTrack.

    let mixComposition = AVMutableComposition()

    let assets = [blank, main, blank]
    var totalTime : CMTime = CMTimeMake(0, 0)
    var atTimeM: CMTime = CMTimeMake(0, 0)

    Utils.log([blank.duration, main.duration])

    // VIDEO TRACK
    let videoTrack = mixComposition.addMutableTrack(withMediaType: AVMediaTypeVideo, preferredTrackID: Int32(kCMPersistentTrackID_Invalid))

    for (index,asset) in assets.enumerated() {

        do {

            if index == 0 {
                atTimeM = kCMTimeZero
            } else {
                atTimeM = totalTime // <-- Use the total time for all the videos seen so far.
            }

            try videoTrack.insertTimeRange(CMTimeRangeMake(kCMTimeZero, asset.duration), of: asset.tracks(withMediaType: AVMediaTypeVideo)[0], at: atTimeM)

        } catch let error as NSError {
            Utils.log("error: \(error)")
        }

        totalTime = CMTimeAdd(totalTime, asset.duration)
    }

    // AUDIO TRACK
    let audioTrack = mixComposition.addMutableTrack(withMediaType: AVMediaTypeAudio, preferredTrackID: kCMPersistentTrackID_Invalid)
    do {
        try audioTrack.insertTimeRange(CMTimeRangeMake(kCMTimeZero, main.duration), of: main.tracks(withMediaType: AVMediaTypeAudio)[0], at: blank.duration)
    } catch _ {
        completionHandler(nil, ErrorType(rawValue: "Unable to add audio in composition."))
        return
    }

    let outputURL = mainVideoObject.getDirectoryURL()?.appendingPathComponent("video-with-blank.mp4")

    guard let exporter = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPreset1280x720) else {
        completionHandler(nil, ErrorType(rawValue: "Unable to create export session."))
        return
    }

    let mainInstruction = AVMutableVideoCompositionInstruction()

    mainInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, CMTimeAdd(blank.duration, CMTimeAdd(main.duration, blank.duration)))

    // Fixing orientation
    let firstLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)
    let firstAssetTrack = blank.tracks(withMediaType: AVMediaTypeVideo)[0]
    firstLayerInstruction.setTransform(firstAssetTrack.preferredTransform, at: kCMTimeZero)
    firstLayerInstruction.setOpacity(0.0, at: blank.duration)

    let secondLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)
    let secondAssetTrack = main.tracks(withMediaType: AVMediaTypeVideo)[0]
    var isSecondAssetPortrait = false
    let secondTransform = secondAssetTrack.preferredTransform
    if (secondTransform.a == 0 && secondTransform.b == 1.0 && secondTransform.c == -1.0 && secondTransform.d == 0) {
        isSecondAssetPortrait = true
    }
    if (secondTransform.a == 0 && secondTransform.b == -1.0 && secondTransform.c == 1.0 && secondTransform.d == 0) {
        isSecondAssetPortrait = true
    }
    secondLayerInstruction.setTransform(secondAssetTrack.preferredTransform, at: blank.duration)
    secondLayerInstruction.setOpacity(0.0, at: CMTimeAdd(blank.duration, main.duration))

    let thirdLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)
    let thirdAssetTrack = blank.tracks(withMediaType: AVMediaTypeVideo)[0]
    thirdLayerInstruction.setTransform(thirdAssetTrack.preferredTransform, at: CMTimeAdd(blank.duration, main.duration))

    mainInstruction.layerInstructions = [firstLayerInstruction, secondLayerInstruction, thirdLayerInstruction]

    var naturalSize = CGSize()
    if(isSecondAssetPortrait) {
        naturalSize = CGSize(width: secondAssetTrack.naturalSize.height, height: secondAssetTrack.naturalSize.width)
    } else {
        naturalSize = secondAssetTrack.naturalSize
    }

    let renderWidth = naturalSize.width
    let renderHeight = naturalSize.height

    let mainCompositionInst = AVMutableVideoComposition()
    mainCompositionInst.instructions = [mainInstruction]
    mainCompositionInst.frameDuration = CMTimeMake(1, 30)
    mainCompositionInst.renderSize = CGSize(width: renderWidth, height: renderHeight)

    exporter.outputURL = outputURL
    exporter.outputFileType = AVFileTypeMPEG4
    exporter.videoComposition = mainCompositionInst
    //exporter.shouldOptimizeForNetworkUse = true

    exporter.exportAsynchronously {
        if exporter.status == .completed {
            completionHandler(AVAsset(url: outputURL!), nil)
        } else {
            completionHandler(nil, ErrorType(rawValue: "Unable to export video."))
            if let error = exporter.error {
                Utils.log("Unable to export video. \(error)")
            }
        }
    }

Assuming that the original video recorder for 5 minutes at 720p quality takes around 200MB of space, adding the 4s blank video on begining and end of the main video should not drastically change the size, and should finish processing very fast.

The result however is a video that's 2 to 2.5x the size of the original video (so 400 - 500 MB) and takes too long to process.

Please advise,

Thanks

1 Answers
Related