美文网首页AVFoundataon音视频
视频合成中CMTime的理解,以及利用CMTime实现过渡效果

视频合成中CMTime的理解,以及利用CMTime实现过渡效果

作者: reallychao | 来源:发表于2018-02-24 17:07 被阅读48次

大家都知道一段视频是有无数个帧组成的,一个帧也就是一个画面。我们利用AVFoundation中的AVAssetWriter合成一段视频,而手中的素材可以是一些图片也可以是实时摄像头截取的照片,怎么把这些图片输出成为视频?视频的长度多少?每个图片所占的时间多长?都要通过CMTime这个结构体设置的参数来确定。

从名字可以推测CMTime是个表达时间的量,它的定义为

typedef struct { CMTimeValue value; CMTimeScale timescale; CMTimeFlags flags; CMTimeEpoch epoch; } CMTime;

我们需要关心的是前两个成员,value,timescale。第一个是数值,第二个是数值的范围。还是从视频的角度来理解吧,假设现在有一个画面,它出现在第二秒,那么我们即可以用value=2,timescale=1来表达也可以用value=1,timescale=2来表达,区别是第一种情况表示的时间粒度是1秒,而后者是两秒。时间粒度就是我们所说的帧率(Frames per Second, FPS)。要转换成绝对时间可以用value/timescale = seconds这个公式来计算。CMTime有如下两个快捷生成方法:

CMTimeMake(a,b)    //a当前第几帧, b每秒钟多少帧.当前播放时间a/b
 
CMTimeMakeWithSeconds(a,b)    //a当前时间,b每秒钟多少帧.

那么如何利用这个参数来定义一个视频的画面?其实第一次使用这个参数,我是错误的。在往下继续探讨之前,先介绍个方法。

- (BOOL)appendPixelBuffer:(CVPixelBufferRef)pixelBuffer withPresentationTime:(CMTime)presentationTime

简单来说这个方法把图片的像素缓冲输入给视频合成工具AssetWriter中,而该缓冲在视频中出现的时间由(CMTime)presentationTime这个参数设定。

先说个最简单的例子,我有5张图片,现在我要生成5秒视频,那么很容易推导出每个图片所占的粒度也就是帧率为1秒,那么

CMTime presentationTime=CMTimeMake(i,1);

i分别从0到4生成presentationTime再调用appendPixelBuffer就可以实现我们的例子。这个是最简单的。来个稍微有点挑战性的,现在有2张图片,要合成3秒视频,要求是每张图片在视频中出现的时间等长,那怎么办?

咋看不算难,但是我们注意到,CMTimeMake中两个参数都是整型的,不可能出现2/3这样的分数或者小数形式的值。

CMTime CMTimeMake (
   int64_t value,
   int32_t timescale
);

解决方法相信都能想到就是把时间粒度做得更细,假设帧率是2,那么第一张图片就从CMTimeMake(0,2)持续到CMTimeMake(2,2)。第二张图片从CMTimeMake(3,2)持续到CMTimeMake(5,2)。如图1:


fig-1.jpg

那么实际当中,我们要把这两张图生成三秒的视频,是不是就要把3秒里的6个粒度都填满图片像素?答案是否定的!这个问题非常tricky。假设我们用appendPixelBuffer方法填充了第一个时间粒度CMTimeMake(0,2),一般理解也就是第一个时间粒度被填满了。这样理解即正确也不正确,第一个粒度确实被填满了,但是后面的粒度也有可能被这个buffer所覆盖,假设我们的第二个像素缓冲填到CMTimeMake(3,2),那么前面的(1,2),(2,2)两个粒度就不是空的了,而是被第一个buffer覆盖。如图2:

fig-2.jpg

但是这种粒度扩展的情况也只是存在于后面有新的buffer进行补充,如果到后面视频就结束了,那么该buffer还是只维持在当前的一个粒度里。所以我们调用- (void)endSessionAtSourceTime:(CMTime)endTime这个方法来结束视频时,即使endTime=CMTimeMake(1000,2),生成的视频也不会是500秒,但是也不是3秒!而是两秒!正如我前面的解释,视频停留在CMTimeMake(3,2)这个粒度就结束了。取巧的办法就是追加最后一个粒度,也就是CMTimeMake(5,2),那么CMTimeMake(4,2)也就被自动填充了。

另外,还有个特点就是这个粒度其实是可以变的,也就是第二张图片插入时timescale可以不是2。利用这个特点,我们可以轻松实现任何两个图片的过度效果。只需要在两个图片之间以更细化的粒度插入经过处理的过度图片。实现代码如下:

//1
CMTime fadeTime = CMTimeMake(1, fps*TransitionFrameCount);
//2
for (int b = 0; b < FramesToWaitBeforeTransition; b++) {
    presentTime = CMTimeAdd(presentTime, fadeTime);
}
//3
NSInteger framesToFadeCount = TransitionFrameCount - FramesToWaitBeforeTransition;
for (double j = 1; j < framesToFadeCount; j++) {
    buffer = [self crossFadeImage:[array[i] CGImage]
                                     toImage:[array[i + 1] CGImage]
                                      atSize:CGSizeMake(480, 320)
                                   withAlpha:j/framesToFadeCount];
     
    BOOL appendSuccess = [self appendToAdapter:adaptor
                                              pixelBuffer:buffer
                                                   atTime:presentTime
                                                withInput:writerInput];
    presentTime = CMTimeAdd(presentTime, fadeTime);
     
    NSAssert(appendSuccess, @"Failed to append");
}

简单解释一下,第一步计算出过度效果的粒度。
第二步,得到过度效果开始的时间,其中CMTimeAdd为把当前时间和过度效果的粒度时间相加,注意到相加的两个CMTime的时间粒度是不一样的,出来的结果的timescale为二者粒度的最小公倍数。
最后,循环增加逐渐过度的效果图片,这里我演示的是通过改变alpha实现的渐变过度。代码很简单:

CGContextDrawImage(context, drawRect, firstImage);
CGContextBeginTransparencyLayer(context, nil);
CGContextSetAlpha( context, alpha );
CGContextDrawImage(context, drawRect, secondImage);
CGContextEndTransparencyLayer(context);

把firstImage画到context上,然后覆盖一个透明层,不断调整alpha值把secondImage画上去。

相关文章

  • 视频合成中CMTime的理解,以及利用CMTime实现过渡效果

    大家都知道一段视频是有无数个帧组成的,一个帧也就是一个画面。我们利用AVFoundation中的AVAssetWr...

  • AVPlayer seek 位置不精确问题

    func seek(to time: CMTime, toleranceBefore: CMTime, toler...

  • 这可能是最详细的CMTime教程

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

  • 自定义一个视屏播放器

    AVPlayer可以自定义视屏播放器的样式其中主要是理解CMTime的用法,CMTime是一个结构体,它有2个属性...

  • 理解CMTime ( 翻译 )

    最近看到一篇关于CMTime的文章,感觉讲得通俗易懂,就想着翻译一下,我尽量在语义正确的情况下按照原著来翻译,原文...

  • AVFoundation视频处理的时间CMTime

    一、CMTime Core Media定义的一种时间数据类型 1、创建CMTime 1、CMTimeMake()C...

  • CMTime

    CMTime 一个用来描述视频时间的结构体。他有两个构造函数:\ * CMTimeMake\* CMTimeMak...

  • CMTime

    要求时间高精度时一般使用CMTime,比如音频视频。其他一般情况下一般使用NSTimeInterval,NSTim...

  • CMTime

    最近看到一篇关于CMTime的文章,感觉讲得通俗易懂,正好最近也在看相关的资料就顺便记录下来,以便自己今后回忆复习...

  • CMTime

    CMTime 其实是帧时间,取值为(0,1),单位是秒是用当前帧数除以帧率算出来的,表示当前帧在视频时长的哪个点 ...

网友评论

    本文标题:视频合成中CMTime的理解,以及利用CMTime实现过渡效果

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