这可能是最详细的CMTime教程

作者: 高浩浩浩浩浩浩 | 来源:发表于2017-08-09 20:28 被阅读919次

    最近在做视频开发,避不开就是会用到CMTime。根据网上之前的教程,CMTime的用法其实挺简单的,例如:

        Float64 seconds = 5; 
        int32_t preferredTimeScale = 600;
        CMTime inTime = CMTimeMakeWithSeconds(seconds, preferredTimeScale);
        CMTimeShow(inTime);
    

    然后告诉你seconds是时长,preferredTimeScale是帧率。

    int64_t value = 10000;
    int32_t preferredTimeScale = 600;
    CMTime inTime = CMTimeMake(value, preferredTimeScale);
    CMTimeShow(inTime);
    

    这里value表示视频的帧数,preferredTimeScale表示每秒的帧数。所以这里seconds是 10000/600 = 16.667

    OK,以上其实理解起来没问题,但是当我们在处理视频的时候,常常要把后面的timeScale写成600:

    let sTime = CMTime(seconds: starSeconds, preferredTimescale: 600)
    

    那么这里就有个问题:如果timeScale表示的帧率,这里的意思是视频每秒的帧率是600帧么??
    我们知道人眼可识别的帧率24帧就够了,iPhone手机拍摄帧率为60fps,部分安卓手机的帧率甚至只有30fps。那么这里为什么要设置为600呢?
    重新去查Apple的文档,看到里面这么解释:

    CMTime
    is a C structure that represents time as a rational number, with a numerator (an int64_t
    value), and a denominator (an int32_t
    timescale). Conceptually, the timescale specifies the fraction of a second each unit in the numerator occupies. Thus if the timescale is 4, each unit represents a quarter of a second; if the timescale is 10, each unit represents a tenth of a second, and so on. You frequently use a timescale of 600, because this is a multiple of several commonly used frame rates: 24 fps for film, 30 fps for NTSC (used for TV in North America and Japan), and 25 fps for PAL (used for TV in Europe). Using a timescale of 600, you can exactly represent any number of frames in these systems.

    这里的意思是使用600帧,可以兼容各种视频帧率(24fps, 30fps, 25fps等),是这些帧率的最小公倍数。不过这并不能解释之前的困惑,设置成600以后,视频的帧率真的达到600fps了么?这样子GPU在处理照片的时候不会出现问题吗?

    那么我们再来重新认识下这个CMTime吧!

    假设我们需要在视频文件中精确地指定一个时刻,比如35:06。通常的方法是把时间表示为一个双精度的浮点数据,比如:NSTimeInterval t = 2106.0; 那这个方法在大多数情况下是没有问题的,但是当我们把非常长的时间段划分成非常小的切片时,就会出现问题。不直接进行浮点类型的运算,而是把一个double类型可以容纳大约16位有效数字(十进制)的8个字节的内存空间(在其他通用平台上,sizeof(NSTimeInterval) == sizeof(Float64) == sizeof(double) == 8)。 再次普及double浮点型数据的换算过程和推算原理

    浮点数存在一个大问题:重复操作(加法,乘法等)导致不精确的积累,于是在视频时长很长的时候这个差异会被无限放大,从而在同步多个媒体流时可能导致错误。

    这里举个栗子。一百万个0.000001相加,结果约为1.0000000000079181。该错误是由于1e-6不能以我们使用的double类型精确的表示,所以我们改为使用二进制近似位,它的低有效位不同。这并不是一个大问题,但是当你在运行一个HTTP流服务器的时候,那么你可能会无限期的每秒去积累这种不精确度。

    这就促使我们去找到一种更精确表达时间的方式,通过消除double类型和他们固有的不精确性(不说他们的硬编码舍入行为)。

    CMTime

    虽然Apple已经有很多数据结构来表示Mac和iOS平台上的时间,但是在iOS4和Mac OS X 10.7 推出的时候,加上了CMTime和CMTimeRange。CMTime的类型定义如下:

      typedef struct
      {
         CMTimeValue    value;        
         CMTimeScale    timescale;    
         CMTimeFlags    flags;        
         CMTimeEpoch    epoch;        
       } CMTime;
    
      public typealias CMTimeValue = Int64
      public typealias CMTimeScale = Int32
    

    显然,CMTime定义是一个C语言的结构体,CMTime是以分数的形式表示时间,value表示分子,timescale表示分母,flags是位掩码,表示时间的指定状态。

    这里value,timescale是分别以64位32位整数来存储的,我们从上文已经知道,这样可以避免double类型带来的精度丢失。另外,通过用64位整数来表示分子,我们可以为每个timescale表示90亿个不同的正值,最多19位唯一的十进制数字。

    timescale

    那么timescale又是什么? 它表示每秒分割的“切片”数。CMTime的整体精度就是受到这个限制的。比如:
    如果timescale为1,则不能有对象表示小于1秒的时间戳,并且时间戳以1秒为增量。类似的,如果timescale是1000,则每秒被分割成1000个,并且该value表示我们要显示的毫秒数。

    所以当你试图表示0.5秒的时候,你千万不能这么写:

    CMTime interval = CMTimeMakeWithSeconds(0.5, 1);
    

    这里interval实际上是0 而不是0.5。
    所以为了能让你选择合理的时间尺度确保不被截断,Apple建议我们使用600。如果你需要对音频文件进行更精确的所以,你可以把timescale设为60,000或更高。这里64位 value的好处就是,你仍然可以用这种方式来明确的表示580万年的增量,即1/60,000秒。

    所以,这里可以得出结论:

    timescale只是为了保证时间精度而设置的帧率,并不一定是视频最后实际的播放帧率。

    相关资料:
    Understanding CMTime

    相关文章

      网友评论

      • Weartist:又是标题党..服
      • 823d9dbb4818:公约数?应该是公倍数吧?
        高浩浩浩浩浩浩:是的,我改下
      • 桔子听:有能力的还是去看原文吧,翻译加工过来的实在难以理解!
        高浩浩浩浩浩浩:@桔子听 这里的意思是不要直接对double类型进行运算,而是在不同平台进行等价的格式转换吧
        桔子听:@高浩浩浩浩浩浩 首先感谢回复!我看不懂的地方是,“没有保证浮点格式的机制,这样一个double类型可以容纳大约16个(十进制)有效数字的8个字节sizeof(NSTimeInterval) == sizeof(Float64) == sizeof(double) == 8”
        高浩浩浩浩浩浩:@桔子听 哪里看不懂

      本文标题:这可能是最详细的CMTime教程

      本文链接:https://www.haomeiwen.com/subject/skqqrxtx.html