视频 -> 帧 浅析
2016-07-15 19:28
246 查看
原创:转载请注明出处
关于帧率首先以下几个概念必须弄清楚
1.一个帧就是一个画面
2.视频有无数个帧组成
3.表达时间的量CMTime的定义:
4.每帧图片所占时间=粒度=帧率=1/timeScale
5.时间粒度=帧率(FramesperSecond,FPS)
转换成绝对时间可以用value/timescale=seconds这个公式来计算
6.每秒钟多少帧也就是多少个画面=timeScale
先贴如何获取第一帧的代码:
这是很不求甚解的做法,有好多问题都没有考虑到。
一般来说,如果我们打算求第x秒看如上代码,想都不用想的就去把
改成想要的时间了,但是,跑一下就会发现差强人意。
为什么呢?
我们先要说CMTime是什么东西。
他有两个构造函数:*CMTimeMake*CMTimeMakeWithSeconds
这两个的区别是*CMTimeMake(a,b)a当前第几帧,b每秒钟多少帧.当前播放时间a/b*CMTimeMakeWithSeconds(a,b)a当前时间,b每秒钟多少帧.
我们引用例子来说明它:
CMTimeMakeWithSeconds
OUTPUT:{3000/600=5.000}
代表当前时间为5s,视频一共有3000帧,一秒钟600帧
CMTimeMake
OUTPUT:{10000/600=16.667}
代表时间为16.667s,视频一共1000帧,每秒600帧
其实,在我们这里,我们关心的只有最后那个总时间。换句话说,我们把那个(0,600)换成(x,600)是没问题的…==!
返回的actualTime输出一下
就会发现时间差的很远。这是为什么呢。
首先他的actualTime使用的fps*1000当每秒的帧率。顺路普及下fps的获取方法
然后我们来思考为什么要有requestTime和actualTime呢?开始我对这个api很困惑:为什么我request的时间不等于actual
后来查了一下文档。
当你想要一个时间点的某一帧的时候,他会在一个范围内找,如果有缓存,或者有在索引内的关键帧,就直接返回,从而优化性能。
这个定义范围的API就是requestedTimeToleranceAfter和requestedTimeToleranceBefore
如果我们要精确时间,那么只需要
大家都知道一段视频是有无数个帧组成的,一个帧也就是一个画面。我们利用AVFoundation中的AVAssetWriter合成一段视频,而手中的素材可以是一些图片也可以是实时摄像头截取的照片,怎么把这些图片输出成为视频?视频的长度多少?每个图片所占的时间多长?都要通过CMTime这个结构体设置的参数来确定。
从名字可以推测CMTime是个表达时间的量,它的定义为
我们需要关心的是前两个成员,value,timescale。第一个是数值,第二个是数值的范围。还是从视频的角度来理解吧,假设现在有一个画面,它出现在第二秒,那么我们即可以用value=2,timescale=1来表达也可以用value=1,timescale=2来表达,区别是第一种情况表示的时间粒度是1秒,而后者是两秒。时间粒度就是我们所说的帧率(FramesperSecond,FPS)。要转换成绝对时间可以用value/timescale=seconds这个公式来计算。CMTime有如下两个快捷生成方法:
那么如何利用这个参数来定义一个视频的画面?其实第一次使用这个参数,我是错误的。在往下继续探讨之前,先介绍个方法。
简单来说这个方法把图片的像素缓冲输入给视频合成工具AssetWriter中,而该缓冲在视频中出现的时间由(CMTime)presentationTime这个参数设定。
先说个最简单的例子,我有5张图片,现在我要生成5秒视频,那么很容易推导出每个图片所占的粒度也就是帧率为1秒,那么
i分别从0到4生成presentationTime再调用appendPixelBuffer就可以实现我们的例子。这个是最简单的。来个稍微有点挑战性的,现在有2张图片,要合成3秒视频,要求是每张图片在视频中出现的时间等长,那怎么办?
咋看不算难,但是我们注意到,CMTimeMake中两个参数都是整型的,不可能出现2/3这样的分数或者小数形式的值。
解决方法相信都能想到就是把时间粒度做得更细,假设帧率是2,那么第一张图片就从CMTimeMake(0,2)持续到CMTimeMake(2,2)。第二张图片从CMTimeMake(3,2)持续到CMTimeMake(5,2)。如图1:
那么实际当中,我们要把这两张图生成三秒的视频,是不是就要把3秒里的6个粒度都填满图片像素?答案是否定的!这个问题非常tricky。假设我们用appendPixelBuffer方法填充了第一个时间粒度CMTimeMake(0,2),一般理解也就是第一个时间粒度被填满了。这样理解即正确也不正确,第一个粒度确实被填满了,但是后面的粒度也有可能被这个buffer所覆盖,假设我们的第二个像素缓冲填到CMTimeMake(3,2),那么前面的(1,2),(2,2)两个粒度就不是空的了,而是被第一个buffer覆盖。如图2:
但是这种粒度扩展的情况也只是存在于后面有新的buffer进行补充,如果到后面视频就结束了,那么该buffer还是只维持在当前的一个粒度里。所以我们调用-(void)endSessionAtSourceTime:(CMTime)endTime这个方法来结束视频时,即使endTime=CMTimeMake(1000,2),生成的视频也不会是500秒,但是也不是3秒!而是两秒!正如我前面的解释,视频停留在CMTimeMake(3,2)这个粒度就结束了。取巧的办法就是追加最后一个粒度,也就是CMTimeMake(5,2),那么CMTimeMake(4,2)也就被自动填充了。
另外,还有个特点就是这个粒度其实是可以变的,也就是第二张图片插入时timescale可以不是2。利用这个特点,我们可以轻松实现任何两个图片的过度效果。只需要在两个图片之间以更细化的粒度插入经过处理的过度图片。实现代码如下:
简单解释一下,第一步计算出过度效果的粒度。
第二步,得到过度效果开始的时间,其中CMTimeAdd为把当前时间和过度效果的粒度时间相加,注意到相加的两个CMTime的时间粒度是不一样的,出来的结果的timescale为二者粒度的最小公倍数。
最后,循环增加逐渐过度的效果图片,这里我演示的是通过改变alpha实现的渐变过度。代码很简单:
把firstImage画到context上,然后覆盖一个透明层,不断调整alpha值把secondImage画上去。
参考链接:
1.http://blog.rpplusplus.me/blog/2013/09/04/ios-avassertimagegenerator/
2.https://zwo28.wordpress.com/2015/03/06/%E8%A7%86%E9%A2%91%E5%90%88%E6%88%90%E4%B8%ADcmtime%E7%9A%84%E7%90%86%E8%A7%A3%EF%BC%8C%E4%BB%A5%E5%8F%8A%E5%88%A9%E7%94%A8cmtime%E5%AE%9E%E7%8E%B0%E8%BF%87%E6%B8%A1%E6%95%88%E6%9E%9C/
关于帧率首先以下几个概念必须弄清楚
1.一个帧就是一个画面
2.视频有无数个帧组成
3.表达时间的量CMTime的定义:
5.时间粒度=帧率(FramesperSecond,FPS)
转换成绝对时间可以用value/timescale=seconds这个公式来计算
6.每秒钟多少帧也就是多少个画面=timeScale
先贴如何获取第一帧的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | -(UIImage*)getVideoPreViewImage |
一般来说,如果我们打算求第x秒看如上代码,想都不用想的就去把
1 | CMTimetime=CMTimeMakeWithSeconds(0.0,600); |
为什么呢?
我们先要说CMTime是什么东西。
CMTime
CMTime是一个用来描述视频时间的结构体。他有两个构造函数:*CMTimeMake*CMTimeMakeWithSeconds
这两个的区别是*CMTimeMake(a,b)a当前第几帧,b每秒钟多少帧.当前播放时间a/b*CMTimeMakeWithSeconds(a,b)a当前时间,b每秒钟多少帧.
我们引用例子来说明它:
CMTimeMakeWithSeconds
12 | Float64seconds=5; |
代表当前时间为5s,视频一共有3000帧,一秒钟600帧
CMTimeMake
12 | int64_tvalue=10000; |
代表时间为16.667s,视频一共1000帧,每秒600帧
其实,在我们这里,我们关心的只有最后那个总时间。换句话说,我们把那个(0,600)换成(x,600)是没问题的…==!
requestedTimeTolerance
那么为什么,效果差了这么多呢?我们可以把12 | CGImageRefimage=[gencopyCGImageAtTime:time |
1 | CMTimeShow(actualTime) |
首先他的actualTime使用的fps*1000当每秒的帧率。顺路普及下fps的获取方法
1 | floatfps=[[[assettracksWithMediaType:AVMediaTypeVideo]objectAtIndex:0]nominalFrameRate]; |
后来查了一下文档。
当你想要一个时间点的某一帧的时候,他会在一个范围内找,如果有缓存,或者有在索引内的关键帧,就直接返回,从而优化性能。
这个定义范围的API就是requestedTimeToleranceAfter和requestedTimeToleranceBefore
如果我们要精确时间,那么只需要
12 | gen.requestedTimeToleranceAfter=kCMTimeZero; |
从名字可以推测CMTime是个表达时间的量,它的定义为
先说个最简单的例子,我有5张图片,现在我要生成5秒视频,那么很容易推导出每个图片所占的粒度也就是帧率为1秒,那么
咋看不算难,但是我们注意到,CMTimeMake中两个参数都是整型的,不可能出现2/3这样的分数或者小数形式的值。
那么实际当中,我们要把这两张图生成三秒的视频,是不是就要把3秒里的6个粒度都填满图片像素?答案是否定的!这个问题非常tricky。假设我们用appendPixelBuffer方法填充了第一个时间粒度CMTimeMake(0,2),一般理解也就是第一个时间粒度被填满了。这样理解即正确也不正确,第一个粒度确实被填满了,但是后面的粒度也有可能被这个buffer所覆盖,假设我们的第二个像素缓冲填到CMTimeMake(3,2),那么前面的(1,2),(2,2)两个粒度就不是空的了,而是被第一个buffer覆盖。如图2:
但是这种粒度扩展的情况也只是存在于后面有新的buffer进行补充,如果到后面视频就结束了,那么该buffer还是只维持在当前的一个粒度里。所以我们调用-(void)endSessionAtSourceTime:(CMTime)endTime这个方法来结束视频时,即使endTime=CMTimeMake(1000,2),生成的视频也不会是500秒,但是也不是3秒!而是两秒!正如我前面的解释,视频停留在CMTimeMake(3,2)这个粒度就结束了。取巧的办法就是追加最后一个粒度,也就是CMTimeMake(5,2),那么CMTimeMake(4,2)也就被自动填充了。
另外,还有个特点就是这个粒度其实是可以变的,也就是第二张图片插入时timescale可以不是2。利用这个特点,我们可以轻松实现任何两个图片的过度效果。只需要在两个图片之间以更细化的粒度插入经过处理的过度图片。实现代码如下:
第二步,得到过度效果开始的时间,其中CMTimeAdd为把当前时间和过度效果的粒度时间相加,注意到相加的两个CMTime的时间粒度是不一样的,出来的结果的timescale为二者粒度的最小公倍数。
最后,循环增加逐渐过度的效果图片,这里我演示的是通过改变alpha实现的渐变过度。代码很简单:
参考链接:
1.http://blog.rpplusplus.me/blog/2013/09/04/ios-avassertimagegenerator/
2.https://zwo28.wordpress.com/2015/03/06/%E8%A7%86%E9%A2%91%E5%90%88%E6%88%90%E4%B8%ADcmtime%E7%9A%84%E7%90%86%E8%A7%A3%EF%BC%8C%E4%BB%A5%E5%8F%8A%E5%88%A9%E7%94%A8cmtime%E5%AE%9E%E7%8E%B0%E8%BF%87%E6%B8%A1%E6%95%88%E6%9E%9C/
相关文章推荐
- mongodb 分片集群中加入新的config servers
- Android AlarmManager的一些问题
- 黑苹果实战安装经历
- 基本数据类型总结
- hdu5655-BestCoder Round #78 (div.2)
- 线程与进程的区别
- c#——Winform PropertyGrid使用
- 依赖倒置原则详解--七大面向对象设计原则(3)
- 安卓手机测试常见BUG
- [Leetcode]303. Range Sum Query - Immutable
- 【poj2741】 Colored Cubes
- 【NOIP模拟】修路
- POJ 1062 昂贵的聘礼
- 【POJ3277】City Horizon,线段树
- JZOJ1732. 消息传播
- 《编程之法》1.3字符串的全排列,组合,重复排列,八皇后问题
- SAP各种冲销凭证
- Summer day 4
- Linked List Cycle II
- Android布局自我理解与总结