iosavfoundationavassetwriteravassetwriterinput

Making a real slow motion video that are all slow motion


I am creating this app of mine and shooting videos at 120 and 240 fps.

When I watch these videos on my mac I see these markers below the timeline.

enter image description here

These markers are editable and represent the area that is in slow motion. So, the video starts at the normal frame rate, enters on slow motion and returns to normal frame rate at the end. I do not put those markers there, iOS did. In that case I wonder if there are a way to remove them and make the videos entirely slow motion.

I am just initializing AVAssetWriter normally as I would for a video non-slow motion.

Also, I have noticed that these "slow motion" videos are not really slow motion but they are "recipes" for slow motion that just play correctly on iOS devices and Macs using QuickTime X. Not even QuickTime 7 plays them correctly.

Anyway to make this thing a real slow motion that can be played on any player, any computer?


Solution

  • Your "slow motion" video files are actually just video files with high framerates. iOS is lowering the playback rate to show off the extra frames in the form of slow motion. The problem is that other players play at a playback rate of 1, so to make the effect portable you need to modify the frame presentation timestamps instead.

    You can probably do this with an AVMutableComposition but I prefer to use the more wysiwyg AVAssetReader/AVAssetWriter pair. Something like this for every frame in the input file:

    if let inSampleBuffer = readerOutput.copyNextSampleBuffer() {
        let inTimeStamp = CMSampleBufferGetPresentationTimeStamp(inSampleBuffer)
        let outTimeStamp = CMTimeMultiplyByFloat64(inTimeStamp, 240.0/30)  // slow 240 fps down to 30fps (8x slowmo)
        var outSampleBuffer: CMSampleBuffer?
        var outTimingInfo = CMSampleTimingInfo(duration: kCMTimeInvalid, presentationTimeStamp: outTimeStamp, decodeTimeStamp: kCMTimeInvalid)
    
        if CMSampleBufferCreateCopyWithNewTiming(kCFAllocatorDefault, inSampleBuffer, 1, &outTimingInfo, &outSampleBuffer) == noErr {
            writerInput.appendSampleBuffer(outSampleBuffer!)
        }
    } else {
        // finished
    }