I’ve got a pretty happy relationship with AVFoundation, but the CoreMedia API’s can sometimes be inscrutable. Today, it’s time division. Given the existence of CMTimeMultiply, one might expect ‘CMTimeDivide’. Instead, we find CMTimeMultiplyByRatio

let time = CMTimeMakeWithSeconds(43804, 600)

let divided = CMTimeMultiplyByRatio(time, 1, 4)

(lldb) print divided
	 (CMTime) $R0 = {
	  value = 43804
	  timescale = 2400
	  flags = (rawValue = 1)
	  epoch = 0
	}

Notice that CoreMedia works smartly underneath the hood. Rather than divide the value, it simply scales the timescale, keeping the value evenly divisible by the timescale. What happens if we use CMTimeMultiplyByFloat64?

let dividedByFloat = CMTimeMultiplyByFloat64(time, 0.25)
(lldb) print dividedByFloat
(CMTime) $R0 = {
  value = 18251666667
  timescale = 1000000000
  flags = (rawValue = 3)
  epoch = 0
}

We can see that instead of scaling the timescale, the value and timescale have been expanded, as well as a new flag added.

// Inspect the flags from using CMTimeMultiplyByRatio
(lldb) print divided.flags.contains(.Valid)
(Bool) $R1 = true
(lldb) print divided.flags.contains(.HasBeenRounded)
(Bool) $R2 = false

// Inspect the flags from using CMTimeMultiplyByFloat64
(lldb) print dividedByFloat.flags.contains(.Valid)
(Bool) $R6 = true
(lldb) print dividedByFloat.flags.contains(.HasBeenRounded)
(Bool) $R7 = true

This tells us that CoreMedia has done some rounding in the CMTimeMultiplyByFloat64 calculation, while CMTimeMultiplyByRatio remained exact. The documentation sheds some more light on the exact behavior:

The exact rational value will be preserved, if possible without overflow. If an overflow would occur, a new timescale will be chosen so as to minimize the rounding error. Default rounding will be applied when converting the result to this timescale. If the result value still overflows when timescale == 1, then the result will be either positive or negative infinity, depending on the direction of the overflow.